Anomalous infections (#31876)
* inner anomaly * anomaly pulse action * test anom mine * Update anomalies.yml * fix action cooldown * pyro_eyes * clientsystem * experiments * blya * some telegraphy * shock eyes! * shadow eyes * separate files * frosty eyes * fix * flora eyes * bluespace eyes * flesh eyes * redoing injction * auto add layers * пипяу * new injector component * stupid me * nice marker injectors * anomaly spawn on shutdown * gravity anom * dead anomaly spawning * add VOX states * sprite specific layers support * technology anom infection * auto detach anomalies that have moved away * Update anomaly_injections.yml * anomalyspawner integration * rock anomaly! * Update anomaly_injections.yml * fix crash bug * tag filter * fix anom dublication spawns * Update anomaly.yml * Update InnerBodyAnomalyComponent.cs * Update anomaly_injections.yml * dont spawn anomalies after decay * fix morb sprite, add end message * gravity resprite * admin logging, double injection fix * make flesh and living light mobs friendly to anomaly hosts * popups * severity feedback * sloth review * A * keep organs after gib * punpun host * sloth synchronization * Update arachnid.yml * increase infections spawnrate
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Anomaly.Components;
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
@@ -10,6 +11,7 @@ using Content.Shared.Popups;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Anomaly;
|
||||
|
||||
@@ -25,6 +27,7 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _power = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -40,6 +43,34 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
||||
SubscribeLocalEvent<AnomalyStabilityChangedEvent>(OnAnomalyStabilityChanged);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<AnomalySynchronizerComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var uid, out var sync, out var xform))
|
||||
{
|
||||
if (sync.ConnectedAnomaly is null)
|
||||
continue;
|
||||
|
||||
if (_timing.CurTime < sync.NextCheckTime)
|
||||
continue;
|
||||
sync.NextCheckTime += sync.CheckFrequency;
|
||||
|
||||
if (Transform(sync.ConnectedAnomaly.Value).MapUid != Transform(uid).MapUid)
|
||||
{
|
||||
DisconnectFromAnomaly((uid, sync), sync.ConnectedAnomaly.Value);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!xform.Coordinates.TryDistance(EntityManager, Transform(sync.ConnectedAnomaly.Value).Coordinates, out var distance))
|
||||
continue;
|
||||
|
||||
if (distance > sync.AttachRange)
|
||||
DisconnectFromAnomaly((uid, sync), sync.ConnectedAnomaly.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If powered, try to attach a nearby anomaly.
|
||||
/// </summary>
|
||||
@@ -73,10 +104,10 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
||||
if (args.Powered)
|
||||
return;
|
||||
|
||||
if (!TryComp<AnomalyComponent>(ent.Comp.ConnectedAnomaly, out var anomaly))
|
||||
if (ent.Comp.ConnectedAnomaly is null)
|
||||
return;
|
||||
|
||||
DisconnectFromAnomaly(ent, anomaly);
|
||||
DisconnectFromAnomaly(ent, ent.Comp.ConnectedAnomaly.Value);
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<AnomalySynchronizerComponent> ent, ref ExaminedEvent args)
|
||||
@@ -125,13 +156,16 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
||||
|
||||
//TODO: disconnection from the anomaly should also be triggered if the anomaly is far away from the synchronizer.
|
||||
//Currently only bluespace anomaly can do this, but for some reason it is the only one that cannot be connected to the synchronizer.
|
||||
private void DisconnectFromAnomaly(Entity<AnomalySynchronizerComponent> ent, AnomalyComponent anomaly)
|
||||
private void DisconnectFromAnomaly(Entity<AnomalySynchronizerComponent> ent, EntityUid other)
|
||||
{
|
||||
if (ent.Comp.ConnectedAnomaly == null)
|
||||
return;
|
||||
|
||||
if (ent.Comp.PulseOnDisconnect)
|
||||
_anomaly.DoAnomalyPulse(ent.Comp.ConnectedAnomaly.Value, anomaly);
|
||||
if (TryComp<AnomalyComponent>(other, out var anomaly))
|
||||
{
|
||||
if (ent.Comp.PulseOnDisconnect)
|
||||
_anomaly.DoAnomalyPulse(ent.Comp.ConnectedAnomaly.Value, anomaly);
|
||||
}
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("anomaly-sync-disconnected"), ent, PopupType.Large);
|
||||
_audio.PlayPvs(ent.Comp.ConnectedSound, ent);
|
||||
|
||||
@@ -55,6 +55,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
SubscribeLocalEvent<AnomalyComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<AnomalyComponent, StartCollideEvent>(OnStartCollide);
|
||||
|
||||
|
||||
InitializeGenerator();
|
||||
InitializeScanner();
|
||||
InitializeVessel();
|
||||
@@ -86,7 +87,10 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
|
||||
private void OnShutdown(Entity<AnomalyComponent> anomaly, ref ComponentShutdown args)
|
||||
{
|
||||
EndAnomaly(anomaly);
|
||||
if (anomaly.Comp.CurrentBehavior is not null)
|
||||
RemoveBehavior(anomaly, anomaly.Comp.CurrentBehavior.Value);
|
||||
|
||||
EndAnomaly(anomaly, spawnCore: false);
|
||||
}
|
||||
|
||||
private void OnStartCollide(Entity<AnomalyComponent> anomaly, ref StartCollideEvent args)
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Content.Server.Anomaly.Components;
|
||||
/// <summary>
|
||||
/// a device that allows you to translate anomaly activity into multitool signals.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(AnomalySynchronizerSystem))]
|
||||
[RegisterComponent, AutoGenerateComponentPause, Access(typeof(AnomalySynchronizerSystem))]
|
||||
public sealed partial class AnomalySynchronizerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
@@ -34,6 +34,15 @@ public sealed partial class AnomalySynchronizerComponent : Component
|
||||
[DataField]
|
||||
public float AttachRange = 0.4f;
|
||||
|
||||
/// <summary>
|
||||
/// Periodicheski checks to see if the anomaly has moved to disconnect it.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan CheckFrequency = TimeSpan.FromSeconds(1f);
|
||||
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan NextCheckTime = TimeSpan.Zero;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<SourcePortPrototype> DecayingPort = "Decaying";
|
||||
|
||||
|
||||
236
Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs
Normal file
236
Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs
Normal file
@@ -0,0 +1,236 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Jittering;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Anomaly;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Anomaly.Effects;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Anomaly.Effects;
|
||||
|
||||
public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly AnomalySystem _anomaly = default!;
|
||||
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly BodySystem _body = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
|
||||
[Dependency] private readonly JitteringSystem _jitter = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly StunSystem _stun = default!;
|
||||
|
||||
private readonly Color _messageColor = Color.FromSrgb(new Color(201, 22, 94));
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<InnerBodyAnomalyInjectorComponent, StartCollideEvent>(OnStartCollideInjector);
|
||||
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, ComponentShutdown>(OnCompShutdown);
|
||||
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, AnomalyPulseEvent>(OnAnomalyPulse);
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, AnomalyShutdownEvent>(OnAnomalyShutdown);
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, AnomalySupercriticalEvent>(OnAnomalySupercritical);
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, AnomalySeverityChangedEvent>(OnSeverityChanged);
|
||||
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
|
||||
SubscribeLocalEvent<AnomalyComponent, ActionAnomalyPulseEvent>(OnActionPulse);
|
||||
}
|
||||
|
||||
private void OnActionPulse(Entity<AnomalyComponent> ent, ref ActionAnomalyPulseEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
_anomaly.DoAnomalyPulse(ent, ent.Comp);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnStartCollideInjector(Entity<InnerBodyAnomalyInjectorComponent> ent, ref StartCollideEvent args)
|
||||
{
|
||||
if (ent.Comp.Whitelist is not null && !_whitelist.IsValid(ent.Comp.Whitelist, args.OtherEntity))
|
||||
return;
|
||||
if (TryComp<InnerBodyAnomalyComponent>(args.OtherEntity, out var innerAnom) && innerAnom.Injected)
|
||||
return;
|
||||
if (!_mind.TryGetMind(args.OtherEntity, out _, out var mindComponent))
|
||||
return;
|
||||
|
||||
EntityManager.AddComponents(args.OtherEntity, ent.Comp.InjectionComponents);
|
||||
QueueDel(ent);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<InnerBodyAnomalyComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
AddAnomalyToBody(ent);
|
||||
}
|
||||
|
||||
private void AddAnomalyToBody(Entity<InnerBodyAnomalyComponent> ent)
|
||||
{
|
||||
if (!_proto.TryIndex(ent.Comp.InjectionProto, out var injectedAnom))
|
||||
return;
|
||||
|
||||
if (ent.Comp.Injected)
|
||||
return;
|
||||
|
||||
ent.Comp.Injected = true;
|
||||
|
||||
EntityManager.AddComponents(ent, injectedAnom.Components);
|
||||
|
||||
_stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
|
||||
_jitter.DoJitter(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
|
||||
|
||||
if (ent.Comp.StartSound is not null)
|
||||
_audio.PlayPvs(ent.Comp.StartSound, ent);
|
||||
|
||||
if (ent.Comp.StartMessage is not null &&
|
||||
_mind.TryGetMind(ent, out _, out var mindComponent) &&
|
||||
mindComponent.Session != null)
|
||||
{
|
||||
var message = Loc.GetString(ent.Comp.StartMessage);
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
||||
_chat.ChatMessageToOne(ChatChannel.Server,
|
||||
message,
|
||||
wrappedMessage,
|
||||
default,
|
||||
false,
|
||||
mindComponent.Session.Channel,
|
||||
_messageColor);
|
||||
|
||||
_popup.PopupEntity(message, ent, ent, PopupType.MediumCaution);
|
||||
|
||||
_adminLog.Add(LogType.Anomaly,LogImpact.Extreme,$"{ToPrettyString(ent)} became anomaly host.");
|
||||
}
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
private void OnAnomalyPulse(Entity<InnerBodyAnomalyComponent> ent, ref AnomalyPulseEvent args)
|
||||
{
|
||||
_stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity), true);
|
||||
_jitter.DoJitter(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity), true);
|
||||
}
|
||||
|
||||
private void OnAnomalySupercritical(Entity<InnerBodyAnomalyComponent> ent, ref AnomalySupercriticalEvent args)
|
||||
{
|
||||
if (!TryComp<BodyComponent>(ent, out var body))
|
||||
return;
|
||||
|
||||
_body.GibBody(ent, true, body, splatModifier: 5f);
|
||||
}
|
||||
|
||||
private void OnSeverityChanged(Entity<InnerBodyAnomalyComponent> ent, ref AnomalySeverityChangedEvent args)
|
||||
{
|
||||
if (!_mind.TryGetMind(ent, out _, out var mindComponent) || mindComponent.Session == null)
|
||||
return;
|
||||
|
||||
var message = string.Empty;
|
||||
|
||||
if (args.Severity >= 0.5 && ent.Comp.LastSeverityInformed < 0.5)
|
||||
{
|
||||
ent.Comp.LastSeverityInformed = 0.5f;
|
||||
message = Loc.GetString("inner-anomaly-severity-info-50");
|
||||
}
|
||||
if (args.Severity >= 0.75 && ent.Comp.LastSeverityInformed < 0.75)
|
||||
{
|
||||
ent.Comp.LastSeverityInformed = 0.75f;
|
||||
message = Loc.GetString("inner-anomaly-severity-info-75");
|
||||
}
|
||||
if (args.Severity >= 0.9 && ent.Comp.LastSeverityInformed < 0.9)
|
||||
{
|
||||
ent.Comp.LastSeverityInformed = 0.9f;
|
||||
message = Loc.GetString("inner-anomaly-severity-info-90");
|
||||
}
|
||||
if (args.Severity >= 1 && ent.Comp.LastSeverityInformed < 1)
|
||||
{
|
||||
ent.Comp.LastSeverityInformed = 1f;
|
||||
message = Loc.GetString("inner-anomaly-severity-info-100");
|
||||
}
|
||||
|
||||
if (message == string.Empty)
|
||||
return;
|
||||
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
||||
_chat.ChatMessageToOne(ChatChannel.Server,
|
||||
message,
|
||||
wrappedMessage,
|
||||
default,
|
||||
false,
|
||||
mindComponent.Session.Channel,
|
||||
_messageColor);
|
||||
|
||||
_popup.PopupEntity(message, ent, ent, PopupType.MediumCaution);
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(Entity<InnerBodyAnomalyComponent> ent, ref MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState != MobState.Dead)
|
||||
return;
|
||||
|
||||
_anomaly.ChangeAnomalyHealth(ent, -2); //Shutdown it
|
||||
}
|
||||
|
||||
private void OnAnomalyShutdown(Entity<InnerBodyAnomalyComponent> ent, ref AnomalyShutdownEvent args)
|
||||
{
|
||||
RemoveAnomalyFromBody(ent);
|
||||
RemCompDeferred<InnerBodyAnomalyComponent>(ent);
|
||||
}
|
||||
|
||||
private void OnCompShutdown(Entity<InnerBodyAnomalyComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
RemoveAnomalyFromBody(ent);
|
||||
}
|
||||
|
||||
private void RemoveAnomalyFromBody(Entity<InnerBodyAnomalyComponent> ent)
|
||||
{
|
||||
if (!ent.Comp.Injected)
|
||||
return;
|
||||
|
||||
if (_proto.TryIndex(ent.Comp.InjectionProto, out var injectedAnom))
|
||||
EntityManager.RemoveComponents(ent, injectedAnom.Components);
|
||||
|
||||
_stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
|
||||
|
||||
if (ent.Comp.EndMessage is not null &&
|
||||
_mind.TryGetMind(ent, out _, out var mindComponent) &&
|
||||
mindComponent.Session != null)
|
||||
{
|
||||
var message = Loc.GetString(ent.Comp.EndMessage);
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
||||
_chat.ChatMessageToOne(ChatChannel.Server,
|
||||
message,
|
||||
wrappedMessage,
|
||||
default,
|
||||
false,
|
||||
mindComponent.Session.Channel,
|
||||
_messageColor);
|
||||
|
||||
|
||||
_popup.PopupEntity(message, ent, ent, PopupType.MediumCaution);
|
||||
|
||||
_adminLog.Add(LogType.Anomaly, LogImpact.Medium,$"{ToPrettyString(ent)} is no longer a host for the anomaly.");
|
||||
}
|
||||
|
||||
ent.Comp.Injected = false;
|
||||
RemCompDeferred<AnomalyComponent>(ent);
|
||||
}
|
||||
}
|
||||
@@ -202,6 +202,7 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
private void HandleRattleTrigger(EntityUid uid, RattleComponent component, TriggerEvent args)
|
||||
{
|
||||
if (!TryComp<SubdermalImplantComponent>(uid, out var implanted))
|
||||
@@ -230,7 +231,7 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
private void OnTriggerCollide(EntityUid uid, TriggerOnCollideComponent component, ref StartCollideEvent args)
|
||||
{
|
||||
if (args.OurFixtureId == component.FixtureID && (!component.IgnoreOtherNonHard || args.OtherFixture.Hard))
|
||||
Trigger(uid);
|
||||
Trigger(uid, args.OtherEntity);
|
||||
}
|
||||
|
||||
private void OnSpawnTriggered(EntityUid uid, TriggerOnSpawnComponent component, MapInitEvent args)
|
||||
|
||||
Reference in New Issue
Block a user