More anomalies (#13766)

This commit is contained in:
Nemanja
2023-02-06 00:03:53 -05:00
committed by GitHub
parent 3656d3cc21
commit f450398df7
41 changed files with 855 additions and 47 deletions

View File

@@ -1,4 +1,5 @@
using Content.Shared.Anomaly; using Content.Client.Gravity;
using Content.Shared.Anomaly;
using Content.Shared.Anomaly.Components; using Content.Shared.Anomaly.Components;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -8,6 +9,7 @@ namespace Content.Client.Anomaly;
public sealed class AnomalySystem : SharedAnomalySystem public sealed class AnomalySystem : SharedAnomalySystem
{ {
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly FloatingVisualizerSystem _floating = default!;
/// <inheritdoc/> /// <inheritdoc/>
public override void Initialize() public override void Initialize()
@@ -15,6 +17,20 @@ public sealed class AnomalySystem : SharedAnomalySystem
base.Initialize(); base.Initialize();
SubscribeLocalEvent<AnomalyComponent, AppearanceChangeEvent>(OnAppearanceChanged); SubscribeLocalEvent<AnomalyComponent, AppearanceChangeEvent>(OnAppearanceChanged);
SubscribeLocalEvent<AnomalyComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<AnomalyComponent, AnimationCompletedEvent>(OnAnimationComplete);
}
private void OnStartup(EntityUid uid, AnomalyComponent component, ComponentStartup args)
{
_floating.FloatAnimation(uid, component.FloatingOffset, component.AnimationKey, component.AnimationTime);
}
private void OnAnimationComplete(EntityUid uid, AnomalyComponent component, AnimationCompletedEvent args)
{
if (args.Key != component.AnimationKey)
return;
_floating.FloatAnimation(uid, component.FloatingOffset, component.AnimationKey, component.AnimationTime);
} }
private void OnAppearanceChanged(EntityUid uid, AnomalyComponent component, ref AppearanceChangeEvent args) private void OnAppearanceChanged(EntityUid uid, AnomalyComponent component, ref AppearanceChangeEvent args)

View File

@@ -4,7 +4,10 @@ using Content.Server.Power.EntitySystems;
using Content.Shared.Anomaly; using Content.Shared.Anomaly;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Materials; using Content.Shared.Materials;
using Content.Shared.Physics;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
namespace Content.Server.Anomaly; namespace Content.Server.Anomaly;
@@ -91,12 +94,32 @@ public sealed partial class AnomalySystem
var randomY = Random.Next((int) gridBounds.Bottom, (int)gridBounds.Top); var randomY = Random.Next((int) gridBounds.Bottom, (int)gridBounds.Top);
var tile = new Vector2i(randomX, randomY); var tile = new Vector2i(randomX, randomY);
if (_atmosphere.IsTileSpace(grid, xform.MapUid, tile,
mapGridComp: gridComp) || _atmosphere.IsTileAirBlocked(grid, tile, mapGridComp: gridComp)) // no air-blocked areas.
if (_atmosphere.IsTileSpace(grid, xform.MapUid, tile, mapGridComp: gridComp) ||
_atmosphere.IsTileAirBlocked(grid, tile, mapGridComp: gridComp))
{ {
continue; continue;
} }
// don't spawn inside of solid objects
var physQuery = GetEntityQuery<PhysicsComponent>();
var valid = true;
foreach (var ent in gridComp.GetAnchoredEntities(tile))
{
if (!physQuery.TryGetComponent(ent, out var body))
continue;
if (body.BodyType != BodyType.Static ||
!body.Hard ||
(body.CollisionLayer & (int) CollisionGroup.Impassable) == 0)
continue;
valid = false;
break;
}
if (!valid)
continue;
targetCoords = gridComp.GridTileToLocal(tile); targetCoords = gridComp.GridTileToLocal(tile);
break; break;
} }

View File

@@ -23,7 +23,6 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
[Dependency] private readonly DoAfterSystem _doAfter = default!; [Dependency] private readonly DoAfterSystem _doAfter = default!;
[Dependency] private readonly ExplosionSystem _explosion = default!; [Dependency] private readonly ExplosionSystem _explosion = default!;
[Dependency] private readonly MaterialStorageSystem _material = default!; [Dependency] private readonly MaterialStorageSystem _material = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!;
public const float MinParticleVariation = 0.8f; public const float MinParticleVariation = 0.8f;
@@ -100,13 +99,13 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
var multiplier = 1f; var multiplier = 1f;
if (component.Stability > component.GrowthThreshold) if (component.Stability > component.GrowthThreshold)
multiplier = component.GrowingPointMultiplier; //more points for unstable multiplier = component.GrowingPointMultiplier; //more points for unstable
else if (component.Stability < component.DecayThreshold)
multiplier = component.DecayingPointMultiplier; //less points if it's dying
//penalty of up to 50% based on health //penalty of up to 50% based on health
multiplier *= MathF.Pow(1.5f, component.Health) - 0.5f; multiplier *= MathF.Pow(1.5f, component.Health) - 0.5f;
return (int) ((component.MaxPointsPerSecond - component.MinPointsPerSecond) * component.Severity * multiplier); var severityValue = 1 / (1 + MathF.Pow(MathF.E, -7 * (component.Severity - 0.5f)));
return (int) ((component.MaxPointsPerSecond - component.MinPointsPerSecond) * severityValue * multiplier) + component.MinPointsPerSecond;
} }
/// <summary> /// <summary>

View File

@@ -6,11 +6,13 @@ using Content.Shared.Anomaly.Effects.Components;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Content.Shared.StatusEffect; using Content.Shared.StatusEffect;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Anomaly.Effects; namespace Content.Server.Anomaly.Effects;
public sealed class ElectricityAnomalySystem : EntitySystem public sealed class ElectricityAnomalySystem : EntitySystem
{ {
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly LightningSystem _lightning = default!; [Dependency] private readonly LightningSystem _lightning = default!;
[Dependency] private readonly ElectrocutionSystem _electrocution = default!; [Dependency] private readonly ElectrocutionSystem _electrocution = default!;
@@ -25,16 +27,12 @@ public sealed class ElectricityAnomalySystem : EntitySystem
private void OnPulse(EntityUid uid, ElectricityAnomalyComponent component, ref AnomalyPulseEvent args) private void OnPulse(EntityUid uid, ElectricityAnomalyComponent component, ref AnomalyPulseEvent args)
{ {
var range = component.MaxElectrocuteRange * args.Stabiltiy; var range = component.MaxElectrocuteRange * args.Stability;
var damage = (int) (component.MaxElectrocuteDamage * args.Severity);
var duration = component.MaxElectrocuteDuration * args.Severity;
var xform = Transform(uid); var xform = Transform(uid);
foreach (var comp in _lookup.GetComponentsInRange<StatusEffectsComponent>(xform.MapPosition, range)) foreach (var comp in _lookup.GetComponentsInRange<MobStateComponent>(xform.MapPosition, range))
{ {
var ent = comp.Owner; var ent = comp.Owner;
_lightning.ShootLightning(uid, ent);
_electrocution.TryDoElectrocution(ent, uid, damage, duration, true, statusEffects: comp, ignoreInsulation: true);
} }
} }
@@ -48,7 +46,7 @@ public sealed class ElectricityAnomalySystem : EntitySystem
if (mobQuery.HasComponent(ent)) if (mobQuery.HasComponent(ent))
validEnts.Add(ent); validEnts.Add(ent);
if (_random.Prob(0.1f) && poweredQuery.HasComponent(ent)) if (_random.Prob(0.2f) && poweredQuery.HasComponent(ent))
validEnts.Add(ent); validEnts.Add(ent);
} }
@@ -58,4 +56,32 @@ public sealed class ElectricityAnomalySystem : EntitySystem
_lightning.ShootLightning(uid, ent); _lightning.ShootLightning(uid, ent);
} }
} }
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var (elec, anom, xform) in EntityQuery<ElectricityAnomalyComponent, AnomalyComponent, TransformComponent>())
{
if (_timing.CurTime < elec.NextSecond)
continue;
elec.NextSecond = _timing.CurTime + TimeSpan.FromSeconds(1);
var owner = xform.Owner;
if (!_random.Prob(elec.PassiveElectrocutionChance * anom.Stability))
continue;
var range = elec.MaxElectrocuteRange * anom.Stability;
var damage = (int) (elec.MaxElectrocuteDamage * anom.Severity);
var duration = elec.MaxElectrocuteDuration * anom.Severity;
foreach (var comp in _lookup.GetComponentsInRange<StatusEffectsComponent>(xform.MapPosition, range))
{
var ent = comp.Owner;
_electrocution.TryDoElectrocution(ent, owner, damage, duration, true, statusEffects: comp, ignoreInsulation: true);
}
}
}
} }

View File

@@ -0,0 +1,97 @@
using System.Linq;
using Content.Server.Maps;
using Content.Shared.Anomaly.Components;
using Content.Shared.Anomaly.Effects.Components;
using Content.Shared.Maps;
using Content.Shared.Physics;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Random;
namespace Content.Server.Anomaly.Effects;
public sealed class FleshAnomalySystem : EntitySystem
{
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ITileDefinitionManager _tiledef = default!;
[Dependency] private readonly TileSystem _tile = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<FleshAnomalyComponent, AnomalyPulseEvent>(OnPulse);
SubscribeLocalEvent<FleshAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical);
SubscribeLocalEvent<FleshAnomalyComponent, AnomalyStabilityChangedEvent>(OnSeverityChanged);
}
private void OnPulse(EntityUid uid, FleshAnomalyComponent component, ref AnomalyPulseEvent args)
{
var range = component.SpawnRange * args.Stability;
var amount = (int) (component.MaxSpawnAmount * args.Severity + 0.5f);
var xform = Transform(uid);
SpawnMonstersOnOpenTiles(component, xform, amount, range);
}
private void OnSupercritical(EntityUid uid, FleshAnomalyComponent component, ref AnomalySupercriticalEvent args)
{
var xform = Transform(uid);
SpawnMonstersOnOpenTiles(component, xform, component.MaxSpawnAmount, component.SpawnRange);
Spawn(component.SupercriticalSpawn, xform.Coordinates);
}
private void OnSeverityChanged(EntityUid uid, FleshAnomalyComponent component, ref AnomalyStabilityChangedEvent args)
{
var xform = Transform(uid);
if (!_map.TryGetGrid(xform.GridUid, out var grid))
return;
var radius = component.SpawnRange * args.Stability;
var fleshTile = (ContentTileDefinition) _tiledef[component.FleshTileId];
var localpos = xform.Coordinates.Position;
var tilerefs = grid.GetLocalTilesIntersecting(
new Box2(localpos + (-radius, -radius), localpos + (radius, radius)));
foreach (var tileref in tilerefs)
{
if (!_random.Prob(0.33f))
continue;
_tile.ReplaceTile(tileref, fleshTile);
}
}
private void SpawnMonstersOnOpenTiles(FleshAnomalyComponent component, TransformComponent xform, int amount, float radius)
{
if (!_map.TryGetGrid(xform.GridUid, out var grid))
return;
var localpos = xform.Coordinates.Position;
var tilerefs = grid.GetLocalTilesIntersecting(
new Box2(localpos + (-radius, -radius), localpos + (radius, radius))).ToArray();
_random.Shuffle(tilerefs);
var physQuery = GetEntityQuery<PhysicsComponent>();
var amountCounter = 0;
foreach (var tileref in tilerefs)
{
var valid = true;
foreach (var ent in grid.GetAnchoredEntities(tileref.GridIndices))
{
if (!physQuery.TryGetComponent(ent, out var body))
continue;
if (body.BodyType != BodyType.Static ||
!body.Hard ||
(body.CollisionLayer & (int) CollisionGroup.Impassable) == 0)
continue;
valid = false;
break;
}
if (!valid)
continue;
amountCounter++;
Spawn(_random.Pick(component.Spawns), tileref.GridIndices.ToEntityCoordinates(xform.GridUid.Value, _map));
if (amountCounter >= amount)
return;
}
}
}

View File

@@ -29,7 +29,7 @@ public sealed class PyroclasticAnomalySystem : EntitySystem
private void OnPulse(EntityUid uid, PyroclasticAnomalyComponent component, ref AnomalyPulseEvent args) private void OnPulse(EntityUid uid, PyroclasticAnomalyComponent component, ref AnomalyPulseEvent args)
{ {
var xform = Transform(uid); var xform = Transform(uid);
var ignitionRadius = component.MaximumIgnitionRadius * args.Stabiltiy; var ignitionRadius = component.MaximumIgnitionRadius * args.Stability;
IgniteNearby(xform.Coordinates, args.Severity, ignitionRadius); IgniteNearby(xform.Coordinates, args.Severity, ignitionRadius);
} }

View File

@@ -3,6 +3,7 @@ using Content.Server.Decals;
using Content.Shared.Decals; using Content.Shared.Decals;
using Content.Shared.Maps; using Content.Shared.Maps;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Random; using Robust.Shared.Random;
namespace Content.Server.Maps; namespace Content.Server.Maps;
@@ -54,6 +55,28 @@ public sealed class TileSystem : EntitySystem
return DeconstructTile(tileRef); return DeconstructTile(tileRef);
} }
public bool ReplaceTile(TileRef tileref, ContentTileDefinition replacementTile)
{
if (!TryComp<MapGridComponent>(tileref.GridUid, out var grid))
return false;
return ReplaceTile(tileref, replacementTile, tileref.GridUid, grid);
}
public bool ReplaceTile(TileRef tileref, ContentTileDefinition replacementTile, EntityUid grid, MapGridComponent? component = null)
{
if (!Resolve(grid, ref component))
return false;
var variant = _robustRandom.Pick(replacementTile.PlacementVariants);
var decals = _decal.GetDecalsInRange(tileref.GridUid, tileref.GridPosition().SnapToGrid(EntityManager, _mapManager).Position, 0.5f);
foreach (var (id, _) in decals)
{
_decal.RemoveDecal(tileref.GridUid, id);
}
component.SetTile(tileref.GridIndices, new Tile(replacementTile.TileId, 0, variant));
return true;
}
private bool DeconstructTile(TileRef tileRef) private bool DeconstructTile(TileRef tileRef)
{ {
var indices = tileRef.GridIndices; var indices = tileRef.GridIndices;

View File

@@ -62,7 +62,7 @@ public sealed class AnomalyComponent : Component
/// The amount of health lost when the stability is below the <see cref="DecayThreshold"/> /// The amount of health lost when the stability is below the <see cref="DecayThreshold"/>
/// </summary> /// </summary>
[DataField("healthChangePerSecond"), ViewVariables(VVAccess.ReadWrite)] [DataField("healthChangePerSecond"), ViewVariables(VVAccess.ReadWrite)]
public float HealthChangePerSecond = -0.05f; public float HealthChangePerSecond = -0.01f;
#endregion #endregion
#region Growth #region Growth
@@ -208,21 +208,14 @@ public sealed class AnomalyComponent : Component
/// This doesn't include the point bonus for being unstable. /// This doesn't include the point bonus for being unstable.
/// </summary> /// </summary>
[DataField("maxPointsPerSecond")] [DataField("maxPointsPerSecond")]
public int MaxPointsPerSecond = 65; public int MaxPointsPerSecond = 100;
/// <summary> /// <summary>
/// The multiplier applied to the point value for the /// The multiplier applied to the point value for the
/// anomaly being above the <see cref="GrowthThreshold"/> /// anomaly being above the <see cref="GrowthThreshold"/>
/// </summary> /// </summary>
[DataField("growingPointMultiplier")] [DataField("growingPointMultiplier")]
public float GrowingPointMultiplier = 1.2f; public float GrowingPointMultiplier = 1.5f;
/// <summary>
/// The multiplier applied to the point value for the
/// anomaly being below the <see cref="DecayThreshold"/>
/// </summary>
[DataField("decayingPointMultiplier")]
public float DecayingPointMultiplier = 0.75f;
#endregion #endregion
/// <summary> /// <summary>
@@ -238,6 +231,24 @@ public sealed class AnomalyComponent : Component
/// </summary> /// </summary>
[DataField("anomalyContactDamageSound")] [DataField("anomalyContactDamageSound")]
public SoundSpecifier AnomalyContactDamageSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg"); public SoundSpecifier AnomalyContactDamageSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg");
#region Floating Animation
/// <summary>
/// How long it takes to go from the bottom of the animation to the top.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("animationTime")]
public readonly float AnimationTime = 2f;
/// <summary>
/// How far it goes in any direction.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("offset")]
public readonly Vector2 FloatingOffset = (0, 0.15f);
public readonly string AnimationKey = "anomalyfloat";
#endregion
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]
@@ -260,14 +271,10 @@ public sealed class AnomalyComponentState : ComponentState
/// <summary> /// <summary>
/// Event raised at regular intervals on an anomaly to do whatever its effect is. /// Event raised at regular intervals on an anomaly to do whatever its effect is.
/// </summary> /// </summary>
/// <param name="Stabiltiy"></param> /// <param name="Stability"></param>
/// <param name="Severity"></param> /// <param name="Severity"></param>
[ByRefEvent] [ByRefEvent]
public readonly record struct AnomalyPulseEvent(float Stabiltiy, float Severity) public readonly record struct AnomalyPulseEvent(float Stability, float Severity);
{
public readonly float Stabiltiy = Stabiltiy;
public readonly float Severity = Severity;
}
/// <summary> /// <summary>
/// Event raised on an anomaly when it reaches a supercritical point. /// Event raised on an anomaly when it reaches a supercritical point.

View File

@@ -0,0 +1,73 @@
using System.Linq;
using Content.Shared.Anomaly.Components;
using Content.Shared.Anomaly.Effects.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Teleportation.Components;
using Robust.Shared.Map;
using Robust.Shared.Random;
namespace Content.Shared.Anomaly.Effects;
public sealed class BluespaceAnomalySystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<BluespaceAnomalyComponent, AnomalyPulseEvent>(OnPulse);
SubscribeLocalEvent<BluespaceAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical);
SubscribeLocalEvent<BluespaceAnomalyComponent, AnomalySeverityChangedEvent>(OnSeverityChanged);
}
private void OnPulse(EntityUid uid, BluespaceAnomalyComponent component, ref AnomalyPulseEvent args)
{
var xform = Transform(uid);
var range = component.MaxShuffleRadius * args.Severity;
var allEnts = _lookup.GetComponentsInRange<MobStateComponent>(xform.Coordinates, range)
.Select(x => x.Owner).ToList();
allEnts.Add(uid);
var xformQuery = GetEntityQuery<TransformComponent>();
var coords = new List<EntityCoordinates>();
foreach (var ent in allEnts)
{
if (xformQuery.TryGetComponent(ent, out var xf))
coords.Add(xf.Coordinates);
}
_random.Shuffle(coords);
for (var i = 0; i < allEnts.Count; i++)
{
_xform.SetCoordinates(allEnts[i], coords[i]);
}
}
private void OnSupercritical(EntityUid uid, BluespaceAnomalyComponent component, ref AnomalySupercriticalEvent args)
{
var xform = Transform(uid);
var mapPos = _xform.GetWorldPosition(xform);
var radius = component.SupercriticalTeleportRadius;
var gridBounds = new Box2(mapPos - (radius, radius), mapPos + (radius, radius));
foreach (var comp in _lookup.GetComponentsInRange<MobStateComponent>(xform.Coordinates, component.MaxShuffleRadius))
{
var ent = comp.Owner;
var randomX = _random.NextFloat(gridBounds.Left, gridBounds.Right);
var randomY = _random.NextFloat(gridBounds.Bottom, gridBounds.Top);
var pos = new Vector2(randomX, randomY);
_xform.SetWorldPosition(ent, pos);
_audio.PlayPvs(component.TeleportSound, ent);
}
}
private void OnSeverityChanged(EntityUid uid, BluespaceAnomalyComponent component, ref AnomalySeverityChangedEvent args)
{
if (!TryComp<PortalComponent>(uid, out var portal))
return;
portal.MaxRandomRadius = (component.MaxPortalRadius - component.MinPortalRadius) * args.Severity + component.MinPortalRadius;
}
}

View File

@@ -0,0 +1,40 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
namespace Content.Shared.Anomaly.Effects.Components;
[RegisterComponent, NetworkedComponent]
[Access(typeof(BluespaceAnomalySystem))]
public sealed class BluespaceAnomalyComponent : Component
{
/// <summary>
/// The maximum radius that the shuffle effect will extend for
/// scales with stability
/// </summary>
[DataField("maxShuffleRadius"), ViewVariables(VVAccess.ReadWrite)]
public float MaxShuffleRadius = 10;
/// <summary>
/// The maximum MAX distance the portal this anomaly is tied to can teleport you.
/// </summary>
[DataField("maxPortalRadius"), ViewVariables(VVAccess.ReadWrite)]
public float MaxPortalRadius = 25;
/// <summary>
/// The minimum MAX distance the portal this anomaly is tied to can teleport you.
/// </summary>
[DataField("minPortalRadius"), ViewVariables(VVAccess.ReadWrite)]
public float MinPortalRadius = 10;
/// <summary>
/// How far the supercritical event can teleport you
/// </summary>
[DataField("superCriticalTeleportRadius"), ViewVariables(VVAccess.ReadWrite)]
public float SupercriticalTeleportRadius = 50f;
/// <summary>
/// The sound played after players are shuffled/teleported around
/// </summary>
[DataField("teleportSound"), ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg");
}

View File

@@ -1,14 +1,41 @@
namespace Content.Shared.Anomaly.Effects.Components; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Anomaly.Effects.Components;
[RegisterComponent] [RegisterComponent]
public sealed class ElectricityAnomalyComponent : Component public sealed class ElectricityAnomalyComponent : Component
{ {
/// <summary>
/// The maximum radius of the passive electrocution effect
/// scales with stability
/// </summary>
[DataField("maxElectrocutionRange"), ViewVariables(VVAccess.ReadWrite)] [DataField("maxElectrocutionRange"), ViewVariables(VVAccess.ReadWrite)]
public float MaxElectrocuteRange = 6f; public float MaxElectrocuteRange = 7f;
/// <summary>
/// The maximum amount of damage the electrocution can do
/// scales with severity
/// </summary>
[DataField("maxElectrocuteDamage"), ViewVariables(VVAccess.ReadWrite)] [DataField("maxElectrocuteDamage"), ViewVariables(VVAccess.ReadWrite)]
public float MaxElectrocuteDamage = 20f; public float MaxElectrocuteDamage = 20f;
/// <summary>
/// The maximum amount of time the electrocution lasts
/// scales with severity
/// </summary>
[DataField("maxElectrocuteDuration"), ViewVariables(VVAccess.ReadWrite)] [DataField("maxElectrocuteDuration"), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan MaxElectrocuteDuration = TimeSpan.FromSeconds(8); public TimeSpan MaxElectrocuteDuration = TimeSpan.FromSeconds(8);
/// <summary>
/// The maximum chance that each second, when in range of the anomaly, you will be electrocuted.
/// scales with stability
/// </summary>
[DataField("passiveElectrocutionChance"), ViewVariables(VVAccess.ReadWrite)]
public float PassiveElectrocutionChance = 0.05f;
/// <summary>
/// Used for tracking seconds, so that we can shock people in a non-tick-dependent way.
/// </summary>
[DataField("nextSecond", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan NextSecond = TimeSpan.Zero;
} }

View File

@@ -0,0 +1,43 @@
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Anomaly.Effects.Components;
[RegisterComponent]
public sealed class FleshAnomalyComponent : Component
{
/// <summary>
/// A list of entities that are random picked to be spawned on each pulse
/// </summary>
[DataField("spawns", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>)), ViewVariables(VVAccess.ReadWrite)]
public List<string> Spawns = new();
/// <summary>
/// The maximum number of entities that spawn per pulse
/// scales with severity.
/// </summary>
[DataField("maxSpawnAmount"), ViewVariables(VVAccess.ReadWrite)]
public int MaxSpawnAmount = 8;
/// <summary>
/// The maximum radius the entities will spawn in.
/// Also governs the maximum reach of flesh tiles
/// scales with stability
/// </summary>
[DataField("spawnRange"), ViewVariables(VVAccess.ReadWrite)]
public float SpawnRange = 4f;
/// <summary>
/// The tile that is spawned by the anomaly's effect
/// </summary>
[DataField("fleshTileId", customTypeSerializer: typeof(PrototypeIdSerializer<ContentTileDefinition>)), ViewVariables(VVAccess.ReadWrite)]
public string FleshTileId = "FloorFlesh";
/// <summary>
/// The entity spawned when the anomaly goes supercritical
/// </summary>
[DataField("superCriticalSpawn", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>)), ViewVariables(VVAccess.ReadWrite)]
public string SupercriticalSpawn = "FleshKudzu";
}

View File

@@ -1,6 +1,8 @@
namespace Content.Shared.Anomaly.Effects.Components; using Robust.Shared.GameStates;
[RegisterComponent] namespace Content.Shared.Anomaly.Effects.Components;
[RegisterComponent, NetworkedComponent]
public sealed class GravityAnomalyComponent : Component public sealed class GravityAnomalyComponent : Component
{ {
/// <summary> /// <summary>

View File

@@ -13,7 +13,7 @@ public sealed class PyroclasticAnomalyComponent : Component
/// I have no clue if this is balanced. /// I have no clue if this is balanced.
/// </remarks> /// </remarks>
[DataField("heatPerSecond")] [DataField("heatPerSecond")]
public float HeatPerSecond = 50; public float HeatPerSecond = 25;
/// <summary> /// <summary>
/// The maximum distance from which you can be ignited by the anomaly. /// The maximum distance from which you can be ignited by the anomaly.
@@ -50,5 +50,5 @@ public sealed class PyroclasticAnomalyComponent : Component
/// The amount of gas released when the anomaly goes supercritical /// The amount of gas released when the anomaly goes supercritical
/// </summary> /// </summary>
[DataField("supercriticalMoleAmount")] [DataField("supercriticalMoleAmount")]
public float SupercriticalMoleAmount = 50f; public float SupercriticalMoleAmount = 75f;
} }

View File

@@ -29,9 +29,8 @@ public abstract class SharedGravityAnomalySystem : EntitySystem
foreach (var ent in lookup) foreach (var ent in lookup)
{ {
var tempXform = Transform(ent); var tempXform = Transform(ent);
var foo = tempXform.MapPosition.Position - xform.MapPosition.Position; var foo = tempXform.MapPosition.Position - xform.MapPosition.Position;
_throwing.TryThrow(ent, foo.Normalized * 10, strength, uid, 0); _throwing.TryThrow(ent, foo * 10, strength, uid, 0);
} }
} }
@@ -54,7 +53,6 @@ public abstract class SharedGravityAnomalySystem : EntitySystem
var tempXform = Transform(ent); var tempXform = Transform(ent);
var foo = tempXform.MapPosition.Position - xform.MapPosition.Position; 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); _throwing.TryThrow(ent, foo * 5, strength, uid, 0);
} }
} }

View File

@@ -119,6 +119,9 @@ public abstract class SharedAnomalySystem : EntitySystem
if (!Resolve(uid, ref component)) if (!Resolve(uid, ref component))
return; return;
if (!Timing.IsFirstTimePredicted)
return;
DebugTools.Assert(component.MinPulseLength > TimeSpan.FromSeconds(3)); // this is just to prevent lagspikes mispredicting pulses DebugTools.Assert(component.MinPulseLength > TimeSpan.FromSeconds(3)); // this is just to prevent lagspikes mispredicting pulses
var variation = Random.NextFloat(-component.PulseVariation, component.PulseVariation) + 1; var variation = Random.NextFloat(-component.PulseVariation, component.PulseVariation) + 1;
component.NextPulseTime = Timing.CurTime + GetPulseLength(component) * variation; component.NextPulseTime = Timing.CurTime + GetPulseLength(component) * variation;
@@ -173,6 +176,10 @@ public abstract class SharedAnomalySystem : EntitySystem
{ {
if (!Resolve(uid, ref component)) if (!Resolve(uid, ref component))
return; return;
if (!Timing.IsFirstTimePredicted)
return;
Audio.PlayPvs(component.SupercriticalSound, uid); Audio.PlayPvs(component.SupercriticalSound, uid);
var ev = new AnomalySupercriticalEvent(); var ev = new AnomalySupercriticalEvent();

View File

@@ -82,6 +82,7 @@ tiles-asteroid-ironsand-pebbles = asteroid ironsand pebbles
tiles-asteroid-ironsand-rock = asteroid ironsand rock tiles-asteroid-ironsand-rock = asteroid ironsand rock
tiles-cave = cave tiles-cave = cave
tiles-cave-drought = cave drought tiles-cave-drought = cave drought
tiles-flesh-floor = flesh floor
tiles-techmaint3-floor = grated maintenance floor tiles-techmaint3-floor = grated maintenance floor
tiles-techmaint2-floor = steel maintenance floor tiles-techmaint2-floor = steel maintenance floor
tiles-wood2 = wood pattern floor tiles-wood2 = wood pattern floor

View File

@@ -13,4 +13,6 @@
- AnomalyPyroclastic - AnomalyPyroclastic
- AnomalyGravity - AnomalyGravity
- AnomalyElectricity - AnomalyElectricity
- AnomalyFlesh
- AnomalyBluespace
chance: 1 chance: 1

View File

@@ -0,0 +1,160 @@
- type: entity
parent: SimpleMobBase
id: BaseMobFlesh
name: aberrant flesh
description: A shambling mass of flesh, animated through anomalous energy.
abstract: true
components:
- type: HTN
rootTask: SimpleHostileCompound
- type: Faction
factions:
- SimpleHostile
- type: Tag
tags:
- DoorBumpOpener
- Flesh
- type: Sprite
drawdepth: Mobs
sprite: Mobs/Aliens/flesh.rsi
- type: MovementAlwaysTouching
- type: MovementSpeedModifier
baseWalkSpeed: 1
baseSprintSpeed: 1.5
- type: MobState
allowedStates:
- Alive
- Dead
- type: MobThresholds
thresholds:
0: Alive
75: Dead
- type: Stamina
excess: 50
- type: Appearance
- type: Butcherable
spawned:
- id: FoodMeat
amount: 1
- type: Bloodstream
bloodMaxVolume: 500
- type: CombatMode
disarmAction:
enabled: false
autoPopulate: false
name: action-name-disarm
- type: MeleeWeapon
hidden: true
soundHit:
path: /Audio/Weapons/Xeno/alien_claw_flesh3.ogg
angle: 0
animation: WeaponArcClaw
damage:
types:
Slash: 3
- type: ReplacementAccent
accent: genericAggressive
- type: entity
parent: BaseMobFlesh
id: MobFleshJared
components:
- type: Sprite
layers:
- map: [ "enum.DamageStateVisualLayers.Base" ]
state: jared
- type: DamageStateVisuals
states:
Alive:
Base: jared
Critical:
Base: dead
Dead:
Base: dead
- type: MeleeWeapon
hidden: true
soundHit:
path: /Audio/Weapons/Xeno/alien_claw_flesh3.ogg
angle: 0
animation: WeaponArcClaw
damage:
types:
Slash: 5
- type: entity
parent: BaseMobFlesh
id: MobFleshGolem
components:
- type: Sprite
layers:
- map: [ "enum.DamageStateVisualLayers.Base" ]
state: golem
- type: DamageStateVisuals
states:
Alive:
Base: golem
Critical:
Base: dead
Dead:
Base: dead
- type: MobThresholds
thresholds:
0: Alive
50: Dead
- type: MeleeWeapon
hidden: true
soundHit:
path: /Audio/Weapons/Xeno/alien_claw_flesh3.ogg
angle: 0
animation: WeaponArcClaw
damage:
types:
Slash: 5
- type: entity
parent: BaseMobFlesh
id: MobFleshClamp
components:
- type: Sprite
layers:
- map: [ "enum.DamageStateVisualLayers.Base" ]
state: clamp
- type: DamageStateVisuals
states:
Alive:
Base: clamp
Critical:
Base: dead
Dead:
Base: dead
- type: MobThresholds
thresholds:
0: Alive
30: Dead
- type: MovementSpeedModifier
baseWalkSpeed: 2
baseSprintSpeed: 2.5
- type: entity
parent: BaseMobFlesh
id: MobFleshLover
components:
- type: Sprite
layers:
- map: [ "enum.DamageStateVisualLayers.Base" ]
state: lover
- type: DamageStateVisuals
states:
Alive:
Base: lover
Critical:
Base: dead
Dead:
Base: dead
- type: MobThresholds
thresholds:
0: Alive
30: Dead
- type: MovementSpeedModifier
baseWalkSpeed: 2
baseSprintSpeed: 2.5

View File

@@ -14,6 +14,7 @@
"/Audio/Weapons/slash.ogg" "/Audio/Weapons/slash.ogg"
- type: Sprite - type: Sprite
sprite: Objects/Misc/kudzu.rsi sprite: Objects/Misc/kudzu.rsi
color: "#ff0000"
state: kudzu_11 state: kudzu_11
drawdepth: Overdoors drawdepth: Overdoors
netsync: false netsync: false
@@ -77,3 +78,55 @@
- type: SlowContacts - type: SlowContacts
walkSpeedModifier: 0.2 walkSpeedModifier: 0.2
sprintSpeedModifier: 0.2 sprintSpeedModifier: 0.2
- type: entity
id: FleshKudzu
name: tendons
description: A rapidly growing cluster of meaty tendons. WHY ARE YOU STOPPING TO LOOK AT IT?!
placement:
mode: SnapgridCenter
snap:
- Wall
components:
- type: MeleeSound
soundGroups:
Brute:
path:
"/Audio/Weapons/slash.ogg"
- type: Sprite
sprite: Objects/Misc/fleshkudzu.rsi
state: base
drawdepth: Overdoors
netsync: false
- type: Appearance
- type: Clickable
- type: Transform
anchored: true
- type: Physics
- type: Fixtures
fixtures:
- hard: false
density: 7
shape:
!type:PhysShapeAabb
bounds: "-0.5,-0.5,0.5,0.5"
layer:
- MidImpassable
- type: Damageable
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 10
behaviors:
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: Spreader
growthResult: FleshKudzu
chance: 1
- type: SlowContacts
walkSpeedModifier: 0.2
sprintSpeedModifier: 0.2
ignoreWhitelist:
tags:
- Flesh

View File

@@ -0,0 +1,40 @@
- type: entity
id: FleshBlocker
parent: BaseStructure
name: flesh clump
description: An annoying clump of flesh.
components:
- type: InteractionOutline
- type: Sprite
noRot: true
sprite: Structures/Decoration/flesh_decoration.rsi
layers:
- state: closed
map: [ "enum.DamageStateVisualLayers.Base" ]
- type: Fixtures
fixtures:
- shape:
!type:PhysShapeCircle
radius: 0.3
density: 190
mask:
- MachineMask
layer:
- Impassable
- type: RandomSprite
available:
- enum.DamageStateVisualLayers.Base:
closed: ""
- enum.DamageStateVisualLayers.Base:
ajar: ""
- enum.DamageStateVisualLayers.Base:
open: ""
- type: Damageable
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 25
behaviors:
- !type:DoActsBehavior
acts: [ "Destruction" ]

View File

@@ -33,12 +33,14 @@
- MobLayer - MobLayer
- type: Sprite - type: Sprite
netsync: false netsync: false
drawdepth: Items noRot: true
drawdepth: Effects #it needs to draw over stuff.
sprite: Structures/Specific/anomaly.rsi sprite: Structures/Specific/anomaly.rsi
- type: InteractionOutline - type: InteractionOutline
- type: Clickable - type: Clickable
- type: Damageable - type: Damageable
- type: Appearance - type: Appearance
- type: AnimationPlayer
- type: GuideHelp - type: GuideHelp
guides: guides:
- AnomalousResearch - AnomalousResearch
@@ -71,7 +73,6 @@
suffix: Gravity suffix: Gravity
components: components:
- type: Sprite - type: Sprite
drawdepth: Effects #it needs to draw over stuff.
layers: layers:
- state: anom2 - state: anom2
map: ["enum.AnomalyVisualLayers.Base"] map: ["enum.AnomalyVisualLayers.Base"]
@@ -106,3 +107,75 @@
castShadows: false castShadows: false
- type: ElectricityAnomaly - type: ElectricityAnomaly
- type: Electrified - type: Electrified
- type: entity
id: AnomalyFlesh
parent: BaseAnomaly
suffix: Flesh
components:
- type: Sprite
layers:
- state: anom5
map: ["enum.AnomalyVisualLayers.Base"]
- state: anom5-pulse
map: ["enum.AnomalyVisualLayers.Animated"]
visible: false
- type: PointLight
radius: 2.0
energy: 7.5
color: "#cb5b7e"
castShadows: false
- type: FleshAnomaly
spawns:
- MobFleshJared
- MobFleshGolem
- MobFleshClamp
- MobFleshLover
- FleshBlocker
- type: entity
id: AnomalyBluespace
parent: BaseAnomaly
suffix: Bluespace
components:
- type: Sprite
layers:
- state: anom4
map: ["enum.AnomalyVisualLayers.Base"]
- state: anom4-pulse
map: ["enum.AnomalyVisualLayers.Animated"]
visible: false
- type: PointLight
radius: 2.0
energy: 7.5
color: "#00ccff"
castShadows: false
- type: BluespaceAnomaly
- type: Portal
- type: Fixtures
fixtures:
- shape:
!type:PhysShapeCircle
radius: 0.35
density: 50
mask:
- MobMask
layer:
- MobLayer
- id: portalFixture
shape:
!type:PhysShapeAabb
bounds: "-0.25,-0.48,0.25,0.48"
mask:
- FullTileMask
layer:
- WallLayer
hard: false
- type: Anomaly
pulseSound:
collection: RadiationPulse
params:
volume: 5
anomalyContactDamage:
types:
Radiation: 10

View File

@@ -1367,6 +1367,22 @@
thermalConductivity: 0.04 thermalConductivity: 0.04
heatCapacity: 10000 heatCapacity: 10000
- type: tile
id: FloorFlesh
name: tiles-flesh-floor
sprite: /Textures/Tiles/meat.png
variants: 4
placementVariants: [0, 1, 2, 3]
baseTurfs:
- Plating
isSubfloor: false
canCrowbar: true
footstepSounds:
collection: BarestepCarpet
friction: 0.20 #slippy
thermalConductivity: 0.04
heatCapacity: 10000
- type: tile - type: tile
id: FloorTechMaint2 id: FloorTechMaint2
name: tiles-techmaint2-floor name: tiles-techmaint2-floor

View File

@@ -216,6 +216,9 @@
- type: Tag - type: Tag
id: FireAxe id: FireAxe
- type: Tag
id: Flesh
- type: Tag - type: Tag
id: WhitelistChameleon id: WhitelistChameleon

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

View File

@@ -0,0 +1,27 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "Created by EmoGarbage404 (github) for space-station-14, credit to Aleksh#7552 (discord) for original concepts and designs",
"states": [
{
"name": "clamp"
},
{
"name": "dead"
},
{
"name": "golem",
"directions": 4
},
{
"name": "jared"
},
{
"name": "lover"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,14 @@
{
"version": 1,
"license": "CC0-1.0",
"copyright": "Created by EmoGarbage404 (github) for space-station-14",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "base"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

View File

@@ -0,0 +1,20 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "Created by Aleksh#7552 (discord) for space-station-14",
"states": [
{
"name": "ajar"
},
{
"name": "closed"
},
{
"name": "open"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

View File

@@ -1,7 +1,7 @@
{ {
"version": 1, "version": 1,
"license": "CC0-1.0", "license": "CC0-1.0",
"copyright": "Created by EmoGarbage; anom3, anom3-pulse, anom4, anom4-pulse are CC-BY-SA-3.0 at https://github.com/ParadiseSS13/Paradise/blob/master/icons/effects/effects.dmi", "copyright": "Created by EmoGarbage; anom3, anom3-pulse, anom4, anom4-pulse are CC-BY-SA-3.0 at https://github.com/ParadiseSS13/Paradise/blob/master/icons/effects/effects.dmi; anom5, anom5-pulse are CC-BY-SA-3.0 by Aleksh#7552 (discord) for space-station-14",
"size": { "size": {
"x": 32, "x": 32,
"y": 32 "y": 32
@@ -92,6 +92,20 @@
0.15 0.15
] ]
] ]
},
{
"name": "anom5"
},
{
"name": "anom5-pulse",
"delays": [
[
0.25,
0.25,
0.25,
0.25
]
]
} }
] ]
} }

View File

@@ -56,3 +56,7 @@
copyright: "by brainfood for space-station-14, ." copyright: "by brainfood for space-station-14, ."
source: "https://github.com/space-wizards/space-station-14/pull/12193" source: "https://github.com/space-wizards/space-station-14/pull/12193"
- files: ["meat.png"]
license: "CC0-1.0"
copyright: "Created by EmoGarbage404 (github) for space-station-14."
source: "https://github.com/space-wizards/space-station-14/pull/13766"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB