diff --git a/Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs b/Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs new file mode 100644 index 0000000000..efb1a8d46e --- /dev/null +++ b/Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs @@ -0,0 +1,50 @@ +using Content.Shared.Anomaly.Components; +using Content.Shared.Anomaly.Effects; +using Content.Shared.Body.Components; +using Robust.Client.GameObjects; + +namespace Content.Client.Anomaly.Effects; + +public sealed class ClientInnerBodyAnomalySystem : SharedInnerBodyAnomalySystem +{ + public override void Initialize() + { + SubscribeLocalEvent(OnAfterHandleState); + SubscribeLocalEvent(OnCompShutdown); + } + + private void OnAfterHandleState(Entity ent, ref AfterAutoHandleStateEvent args) + { + if (!TryComp(ent, out var sprite)) + return; + + if (ent.Comp.FallbackSprite is null) + return; + + if (!sprite.LayerMapTryGet(ent.Comp.LayerMap, out var index)) + index = sprite.LayerMapReserveBlank(ent.Comp.LayerMap); + + if (TryComp(ent, out var body) && + body.Prototype is not null && + ent.Comp.SpeciesSprites.TryGetValue(body.Prototype.Value, out var speciesSprite)) + { + sprite.LayerSetSprite(index, speciesSprite); + } + else + { + sprite.LayerSetSprite(index, ent.Comp.FallbackSprite); + } + + sprite.LayerSetVisible(index, true); + sprite.LayerSetShader(index, "unshaded"); + } + + private void OnCompShutdown(Entity ent, ref ComponentShutdown args) + { + if (!TryComp(ent, out var sprite)) + return; + + var index = sprite.LayerMapGet(ent.Comp.LayerMap); + sprite.LayerSetVisible(index, false); + } +} diff --git a/Content.Server/Anomaly/AnomalySynchronizerSystem.cs b/Content.Server/Anomaly/AnomalySynchronizerSystem.cs index 59ef08402e..b1814c2741 100644 --- a/Content.Server/Anomaly/AnomalySynchronizerSystem.cs +++ b/Content.Server/Anomaly/AnomalySynchronizerSystem.cs @@ -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(OnAnomalyStabilityChanged); } + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + 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); + } + } + /// /// If powered, try to attach a nearby anomaly. /// @@ -73,10 +104,10 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem if (args.Powered) return; - if (!TryComp(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 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 ent, AnomalyComponent anomaly) + private void DisconnectFromAnomaly(Entity ent, EntityUid other) { if (ent.Comp.ConnectedAnomaly == null) return; - if (ent.Comp.PulseOnDisconnect) - _anomaly.DoAnomalyPulse(ent.Comp.ConnectedAnomaly.Value, anomaly); + if (TryComp(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); diff --git a/Content.Server/Anomaly/AnomalySystem.cs b/Content.Server/Anomaly/AnomalySystem.cs index 3e9760a056..b0de3de8f3 100644 --- a/Content.Server/Anomaly/AnomalySystem.cs +++ b/Content.Server/Anomaly/AnomalySystem.cs @@ -55,6 +55,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnStartCollide); + InitializeGenerator(); InitializeScanner(); InitializeVessel(); @@ -86,7 +87,10 @@ public sealed partial class AnomalySystem : SharedAnomalySystem private void OnShutdown(Entity 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 anomaly, ref StartCollideEvent args) diff --git a/Content.Server/Anomaly/Components/AnomalySynchronizerComponent.cs b/Content.Server/Anomaly/Components/AnomalySynchronizerComponent.cs index 235e740cf3..3127f091e5 100644 --- a/Content.Server/Anomaly/Components/AnomalySynchronizerComponent.cs +++ b/Content.Server/Anomaly/Components/AnomalySynchronizerComponent.cs @@ -7,7 +7,7 @@ namespace Content.Server.Anomaly.Components; /// /// a device that allows you to translate anomaly activity into multitool signals. /// -[RegisterComponent, Access(typeof(AnomalySynchronizerSystem))] +[RegisterComponent, AutoGenerateComponentPause, Access(typeof(AnomalySynchronizerSystem))] public sealed partial class AnomalySynchronizerComponent : Component { /// @@ -34,6 +34,15 @@ public sealed partial class AnomalySynchronizerComponent : Component [DataField] public float AttachRange = 0.4f; + /// + /// Periodicheski checks to see if the anomaly has moved to disconnect it. + /// + [DataField] + public TimeSpan CheckFrequency = TimeSpan.FromSeconds(1f); + + [DataField, AutoPausedField] + public TimeSpan NextCheckTime = TimeSpan.Zero; + [DataField] public ProtoId DecayingPort = "Decaying"; diff --git a/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs b/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs new file mode 100644 index 0000000000..38c4c51d87 --- /dev/null +++ b/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs @@ -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(OnStartCollideInjector); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnCompShutdown); + + SubscribeLocalEvent(OnAnomalyPulse); + SubscribeLocalEvent(OnAnomalyShutdown); + SubscribeLocalEvent(OnAnomalySupercritical); + SubscribeLocalEvent(OnSeverityChanged); + + SubscribeLocalEvent(OnMobStateChanged); + + SubscribeLocalEvent(OnActionPulse); + } + + private void OnActionPulse(Entity ent, ref ActionAnomalyPulseEvent args) + { + if (args.Handled) + return; + + _anomaly.DoAnomalyPulse(ent, ent.Comp); + + args.Handled = true; + } + + private void OnStartCollideInjector(Entity ent, ref StartCollideEvent args) + { + if (ent.Comp.Whitelist is not null && !_whitelist.IsValid(ent.Comp.Whitelist, args.OtherEntity)) + return; + if (TryComp(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 ent, ref MapInitEvent args) + { + AddAnomalyToBody(ent); + } + + private void AddAnomalyToBody(Entity 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 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 ent, ref AnomalySupercriticalEvent args) + { + if (!TryComp(ent, out var body)) + return; + + _body.GibBody(ent, true, body, splatModifier: 5f); + } + + private void OnSeverityChanged(Entity 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 ent, ref MobStateChangedEvent args) + { + if (args.NewMobState != MobState.Dead) + return; + + _anomaly.ChangeAnomalyHealth(ent, -2); //Shutdown it + } + + private void OnAnomalyShutdown(Entity ent, ref AnomalyShutdownEvent args) + { + RemoveAnomalyFromBody(ent); + RemCompDeferred(ent); + } + + private void OnCompShutdown(Entity ent, ref ComponentShutdown args) + { + RemoveAnomalyFromBody(ent); + } + + private void RemoveAnomalyFromBody(Entity 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(ent); + } +} diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs index 853b4fbc29..57847551aa 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs +++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs @@ -202,6 +202,7 @@ namespace Content.Server.Explosion.EntitySystems args.Handled = true; } + private void HandleRattleTrigger(EntityUid uid, RattleComponent component, TriggerEvent args) { if (!TryComp(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) diff --git a/Content.Shared/Anomaly/Components/AnomalyComponent.cs b/Content.Shared/Anomaly/Components/AnomalyComponent.cs index 724dfd38d2..e6228b5fb0 100644 --- a/Content.Shared/Anomaly/Components/AnomalyComponent.cs +++ b/Content.Shared/Anomaly/Components/AnomalyComponent.cs @@ -1,6 +1,6 @@ using System.Numerics; +using Content.Shared.Anomaly.Effects; using Content.Shared.Anomaly.Prototypes; -using Content.Shared.Damage; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -16,7 +16,7 @@ namespace Content.Shared.Anomaly.Components; /// Anomalies and their related components were designed here: https://hackmd.io/@ss14-design/r1sQbkJOs /// [RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] -[Access(typeof(SharedAnomalySystem))] +[Access(typeof(SharedAnomalySystem), typeof(SharedInnerBodyAnomalySystem))] public sealed partial class AnomalyComponent : Component { /// @@ -184,21 +184,21 @@ public sealed partial class AnomalyComponent : Component /// /// The minimum amount of research points generated per second /// - [DataField("minPointsPerSecond")] + [DataField] public int MinPointsPerSecond = 10; /// /// The maximum amount of research points generated per second /// This doesn't include the point bonus for being unstable. /// - [DataField("maxPointsPerSecond")] + [DataField] public int MaxPointsPerSecond = 70; /// /// The multiplier applied to the point value for the /// anomaly being above the /// - [DataField("growingPointMultiplier")] + [DataField] public float GrowingPointMultiplier = 1.5f; #endregion @@ -252,10 +252,13 @@ public sealed partial class AnomalyComponent : Component /// [ViewVariables(VVAccess.ReadWrite)] [DataField("offset")] - public Vector2 FloatingOffset = new(0, 0.15f); + public Vector2 FloatingOffset = new(0, 0); public readonly string AnimationKey = "anomalyfloat"; #endregion + + [DataField] + public bool DeleteEntity = true; } /// diff --git a/Content.Shared/Anomaly/Components/InnerBodyAnomalyComponent.cs b/Content.Shared/Anomaly/Components/InnerBodyAnomalyComponent.cs new file mode 100644 index 0000000000..e88cedb18e --- /dev/null +++ b/Content.Shared/Anomaly/Components/InnerBodyAnomalyComponent.cs @@ -0,0 +1,72 @@ +using Content.Shared.Anomaly.Effects; +using Content.Shared.Body.Prototypes; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.Anomaly.Components; + +/// +/// An anomaly within the body of a living being. Controls the ability to return to the standard state. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedInnerBodyAnomalySystem))] +public sealed partial class InnerBodyAnomalyComponent : Component +{ + [DataField] + public bool Injected; + + /// + /// A prototype of an entity whose components will be added to the anomaly host **AND** then removed at the right time + /// + [DataField(required: true)] + public EntProtoId? InjectionProto; + + /// + /// Duration of stun from the effect of the anomaly + /// + [DataField] + public float StunDuration = 4f; + + /// + /// A message sent in chat to a player who has become infected by an anomaly + /// + [DataField] + public LocId? StartMessage = null; + + /// + /// A message sent in chat to a player who has cleared an anomaly + /// + [DataField] + public LocId? EndMessage = "inner-anomaly-end-message"; + + /// + /// Sound, playing on becoming anomaly + /// + [DataField] + public SoundSpecifier? StartSound = new SoundPathSpecifier("/Audio/Effects/inneranomaly.ogg"); + + /// + /// Used to display messages to the player about their level of disease progression + /// + [DataField] + public float LastSeverityInformed = 0f; + + /// + /// The fallback sprite to be added on the original entity. Allows you to visually identify the feature and type of anomaly to other players + /// + [DataField, AutoNetworkedField] + public SpriteSpecifier? FallbackSprite = null; + + /// + /// Ability to use unique sprites for different body types + /// + [DataField, AutoNetworkedField] + public Dictionary, SpriteSpecifier> SpeciesSprites = new(); + + /// + /// The key of the entity layer into which the sprite will be inserted + /// + [DataField] + public string LayerMap = "inner_anomaly_layer"; +} diff --git a/Content.Shared/Anomaly/Components/InnerBodyAnomalyInjectorComponent.cs b/Content.Shared/Anomaly/Components/InnerBodyAnomalyInjectorComponent.cs new file mode 100644 index 0000000000..e4c398c9cc --- /dev/null +++ b/Content.Shared/Anomaly/Components/InnerBodyAnomalyInjectorComponent.cs @@ -0,0 +1,21 @@ +using Content.Shared.Anomaly.Effects; +using Content.Shared.Whitelist; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Anomaly.Components; + +/// +/// On contact with an entity, if it meets the conditions, it will transfer the specified components to it +/// +[RegisterComponent, Access(typeof(SharedInnerBodyAnomalySystem))] +public sealed partial class InnerBodyAnomalyInjectorComponent : Component +{ + [DataField] + public EntityWhitelist? Whitelist; + + /// + /// components that will be automatically removed after “curing” + /// + [DataField(required: true)] + public ComponentRegistry InjectionComponents = default!; +} diff --git a/Content.Shared/Anomaly/Effects/SharedInnerBodyAnomalySystem.cs b/Content.Shared/Anomaly/Effects/SharedInnerBodyAnomalySystem.cs new file mode 100644 index 0000000000..a1ec7cd397 --- /dev/null +++ b/Content.Shared/Anomaly/Effects/SharedInnerBodyAnomalySystem.cs @@ -0,0 +1,5 @@ +namespace Content.Shared.Anomaly.Effects; + +public abstract class SharedInnerBodyAnomalySystem : EntitySystem +{ +} diff --git a/Content.Shared/Anomaly/SharedAnomalySystem.cs b/Content.Shared/Anomaly/SharedAnomalySystem.cs index c3d6591b72..9a0cde2998 100644 --- a/Content.Shared/Anomaly/SharedAnomalySystem.cs +++ b/Content.Shared/Anomaly/SharedAnomalySystem.cs @@ -1,13 +1,10 @@ using Content.Shared.Administration.Logs; using Content.Shared.Anomaly.Components; using Content.Shared.Anomaly.Prototypes; -using Content.Shared.Damage; using Content.Shared.Database; -using Content.Shared.Interaction; using Content.Shared.Physics; using Content.Shared.Popups; using Content.Shared.Weapons.Melee.Components; -using Content.Shared.Weapons.Melee.Events; using Robust.Shared.Audio.Systems; using Robust.Shared.Map; using Robust.Shared.Map.Components; @@ -21,6 +18,7 @@ using Robust.Shared.Timing; using Robust.Shared.Utility; using System.Linq; using System.Numerics; +using Content.Shared.Actions; namespace Content.Shared.Anomaly; @@ -36,6 +34,7 @@ public abstract class SharedAnomalySystem : EntitySystem [Dependency] protected readonly SharedPopupSystem Popup = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; public override void Initialize() { @@ -145,7 +144,7 @@ public abstract class SharedAnomalySystem : EntitySystem if (!Timing.IsFirstTimePredicted) return; - Audio.PlayPvs(component.SupercriticalSound, uid); + Audio.PlayPvs(component.SupercriticalSound, Transform(uid).Coordinates); if (_net.IsServer) Log.Info($"Raising supercritical event. Entity: {ToPrettyString(uid)}"); @@ -169,7 +168,8 @@ public abstract class SharedAnomalySystem : EntitySystem /// The anomaly being shut down /// /// Whether or not the anomaly ended via supercritical event - public void EndAnomaly(EntityUid uid, AnomalyComponent? component = null, bool supercritical = false) + /// Create anomaly cores based on the result of completing an anomaly? + public void EndAnomaly(EntityUid uid, AnomalyComponent? component = null, bool supercritical = false, bool spawnCore = true) { // Logging before resolve, in case the anomaly has deleted itself. if (_net.IsServer) @@ -186,9 +186,14 @@ public abstract class SharedAnomalySystem : EntitySystem if (Terminating(uid) || _net.IsClient) return; - Spawn(supercritical ? component.CorePrototype : component.CoreInertPrototype, Transform(uid).Coordinates); + if (spawnCore) + { + var core = Spawn(supercritical ? component.CorePrototype : component.CoreInertPrototype, Transform(uid).Coordinates); + _transform.PlaceNextTo(core, uid); + } - QueueDel(uid); + if (component.DeleteEntity) + QueueDel(uid); } /// @@ -458,3 +463,5 @@ public partial record struct AnomalySpawnSettings() /// public bool SpawnOnSeverityChanged { get; set; } = false; } + +public sealed partial class ActionAnomalyPulseEvent : InstantActionEvent { } diff --git a/Resources/Audio/Effects/attributions.yml b/Resources/Audio/Effects/attributions.yml index 6f18510d17..75cf22aa4f 100644 --- a/Resources/Audio/Effects/attributions.yml +++ b/Resources/Audio/Effects/attributions.yml @@ -231,3 +231,8 @@ copyright: '"beep_landmine.ogg" by kaktuscsc of Discord for SS14' license: "CC-BY-SA-3.0" source: https://github.com/YuriyKiss/space-station-14/commit/971a135a9c83aed46e967aac9302ab5b35562b5f + +- files: [inneranomaly.ogg] + copyright: 'created by waveplaySFX on Freesound' + license: "CC0-1.0" + source: https://freesound.org/people/waveplaySFX/sounds/553744/ diff --git a/Resources/Audio/Effects/inneranomaly.ogg b/Resources/Audio/Effects/inneranomaly.ogg new file mode 100644 index 0000000000..43fc40357f Binary files /dev/null and b/Resources/Audio/Effects/inneranomaly.ogg differ diff --git a/Resources/Locale/en-US/anomaly/inner_anomaly.ftl b/Resources/Locale/en-US/anomaly/inner_anomaly.ftl new file mode 100644 index 0000000000..e55c4391e3 --- /dev/null +++ b/Resources/Locale/en-US/anomaly/inner_anomaly.ftl @@ -0,0 +1,17 @@ +inner-anomaly-start-message-pyro = You can feel the insane flame inside of you. You became the host of a pyroclastic anomaly. +inner-anomaly-start-message-shock = Lightning bolts quivering at your fingertips! You became the host of a electric anomaly. +inner-anomaly-start-message-shadow = There's an impenetrable darkness oozing out of you... You became the host of a shadow anomaly. +inner-anomaly-start-message-frost = The icy frost is binding your bones. You became the host of a ice anomaly. +inner-anomaly-start-message-flora = Leaves and flowers sprout through your skin! You became the host of a floral anomaly. +inner-anomaly-start-message-bluespace = Your thoughts are racing like mad! You became the host of a bluespace anomaly. +inner-anomaly-start-message-flesh = Your body is growing frantically. You became the host of a flesh anomaly. +inner-anomaly-start-message-grav = Everything becames unnaturally heavy and light at the same time... You became the host of a gravity anomaly. +inner-anomaly-start-message-tech = Your head is buzzing with the amount of chaotic information! You became the host of a tech anomaly. +inner-anomaly-start-message-rock = The crystals are growing through your bones! You became the host of a rock anomaly. + +inner-anomaly-end-message = The abnormal activity within you disappears without a trace.... + +inner-anomaly-severity-info-50 = You feel that the anomaly is taking over half your body. +inner-anomaly-severity-info-75 = You feel that the anomaly is taking over a large part of your body. +inner-anomaly-severity-info-90 = You feel that the anomaly has almost completely taken over your body. +inner-anomaly-severity-info-100 = The anomaly inside you is growing uncontrollably, causing immense pain, and tearing you apart! \ No newline at end of file diff --git a/Resources/Prototypes/Actions/anomaly.yml b/Resources/Prototypes/Actions/anomaly.yml new file mode 100644 index 0000000000..65c6ae164e --- /dev/null +++ b/Resources/Prototypes/Actions/anomaly.yml @@ -0,0 +1,9 @@ +- type: entity + id: ActionAnomalyPulse + name: Anomaly pulse + description: Release a pulse of energy of your abnormal nature + components: + - type: InstantAction + icon: Structures/Specific/anomaly.rsi/anom1.png + event: !type:ActionAnomalyPulseEvent + useDelay: 30 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml index 5a9360e652..4697606af9 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml @@ -9,6 +9,7 @@ - sprite: Structures/Specific/anomaly.rsi state: anom1 - type: RandomSpawner + chance: 1 prototypes: - AnomalyPyroclastic - AnomalyGravity @@ -21,7 +22,9 @@ - AnomalyFlora - AnomalyShadow - AnomalyTech - chance: 1 + rareChance: 0.3 + rarePrototypes: + - RandomAnomalyInjectorSpawner offset: 0.15 # not to put it higher. The anomaly sychnronizer looks for anomalies within this radius, and if the radius is higher, the anomaly can be attracted from a neighboring tile. - type: entity @@ -40,4 +43,28 @@ - AnomalyRockQuartz - AnomalyRockUranium chance: 1 - offset: 0.15 \ No newline at end of file + offset: 0.15 + +- type: entity + id: RandomAnomalyInjectorSpawner + parent: MarkerBase + components: + - type: Sprite + layers: + - state: red + - sprite: Structures/Specific/Anomalies/tech_anom.rsi + state: pulse + - type: RandomSpawner + prototypes: + - AnomalyTrapPyroclastic + - AnomalyTrapElectricity + - AnomalyTrapShadow + - AnomalyTrapIce + - AnomalyTrapFlora + - AnomalyTrapBluespace + - AnomalyTrapFlesh + - AnomalyTrapGravity + - AnomalyTrapTech + - AnomalyTrapRock + chance: 1 + \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 495f534553..8e05642c0e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -1316,6 +1316,7 @@ tags: - VimPilot - DoorBumpOpener + - AnomalyHost - type: Reactive groups: Flammable: [ Touch ] diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml b/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml index 06ab02dedc..a1bcd547a6 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml @@ -13,6 +13,11 @@ true NavSmash: !type:Bool true + - type: NPCImprintingOnSpawnBehaviour + spawnFriendsSearchRadius: 10 + whitelist: + components: + - Anomaly # Friendly to inner anomaly host - type: NpcFactionMember factions: - SimpleHostile @@ -354,4 +359,4 @@ 30: Dead - type: MovementSpeedModifier baseWalkSpeed: 2 - baseSprintSpeed: 2.5 + baseSprintSpeed: 2.5 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml b/Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml index 9b90d202f3..9500345e4f 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml @@ -16,6 +16,11 @@ - type: NpcFactionMember factions: - SimpleHostile + - type: NPCImprintingOnSpawnBehaviour + spawnFriendsSearchRadius: 10 + whitelist: + components: + - Anomaly # Friendly to inner anomaly host - type: MovementIgnoreGravity - type: MovementSpeedModifier baseWalkSpeed: 3.5 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index ebc1b80541..71336d9e63 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -809,6 +809,7 @@ - CannotSuicide - DoorBumpOpener - VimPilot + - AnomalyHost - type: Loadout prototypes: [ MobMonkeyGear ] - type: Grammar diff --git a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml index 70c516e788..0d7f771758 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml @@ -23,6 +23,7 @@ - FootstepSound - DoorBumpOpener - SpiderCraft + - AnomalyHost - type: Butcherable butcheringType: Spike spawned: diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 2349eddd3a..5cebd2cf85 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -212,6 +212,7 @@ - CanPilot - FootstepSound - DoorBumpOpener + - AnomalyHost - type: entity save: false diff --git a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml index 140f3c9442..f675b2d2c8 100644 --- a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml +++ b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml @@ -5,6 +5,7 @@ description: An impossible object. Should you be standing this close to it? components: - type: Anomaly + offset: 0, 0.15 pulseSound: collection: RadiationPulse params: @@ -934,4 +935,4 @@ lifetime: 2.3 - type: Tag tags: - - HideContextMenu \ No newline at end of file + - HideContextMenu diff --git a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injections.yml b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injections.yml new file mode 100644 index 0000000000..f1e535f15b --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injections.yml @@ -0,0 +1,353 @@ +- type: entity + id: AnomalyInjectionBase + abstract: true + components: + - type: PointLight + radius: 1.3 + energy: 2.5 + castShadows: false + - type: ActionGrant + actions: + - ActionAnomalyPulse + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionPyroclastic + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#E25822" + - type: PyroclasticAnomaly + maximumIgnitionRadius: 3 + - type: TempAffectingAnomaly + tempChangePerSecond: 10 + hotspotExposeTemperature: 500 + - type: GasProducerAnomaly + releasedGas: 3 + releaseOnMaxSeverity: true + spawnRadius: 4 + tileCount: 5 + tempChange: 420 + - type: ProjectileAnomaly + projectilePrototype: ProjectileAnomalyFireball + projectileSpeed: 0.5 + minProjectiles: 1 + maxProjectiles: 3 + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionElectric + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#ffffaa" + - type: ElectricityAnomaly + minBoltCount: 1 + maxBoltCount: 3 + maxElectrocuteRange: 4 + maxElectrocuteDamage: 10 + maxElectrocuteDuration: 4 + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionShadow + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#793a80" + - type: EntitySpawnAnomaly + entries: + - settings: + spawnOnPulse: true + spawnOnSuperCritical: true + minAmount: 5 + maxAmount: 10 + maxRange: 2 + spawns: + - ShadowKudzuWeak + - settings: + spawnOnSuperCritical: true + minAmount: 15 + maxAmount: 20 + maxRange: 25 + spawns: + - ShadowKudzu + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionIce + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#befaff" + - type: ExplosionAnomaly + supercriticalExplosion: Cryo + explosionTotalIntensity: 150 + explosionDropoff: 2 + explosionMaxTileIntensity: 20 + - type: ProjectileAnomaly + projectilePrototype: ProjectileIcicle + minProjectiles: 1 + maxProjectiles: 4 + - type: EntitySpawnAnomaly + entries: + - settings: + spawnOnStabilityChanged: true + minAmount: 3 + maxAmount: 8 + maxRange: 2 + spawns: + - IceCrust + - type: TempAffectingAnomaly + tempChangePerSecond: -10 + hotspotExposeTemperature: -500 + - type: GasProducerAnomaly + releasedGas: 8 # Frezon. Please replace if there is a better way to specify this + releaseOnMaxSeverity: true + spawnRadius: 0 + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionFlora + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#6270bb" + - type: TileSpawnAnomaly + entries: + - settings: + spawnOnPulse: true + minAmount: 2 + maxAmount: 5 + maxRange: 2 + floor: FloorAstroGrass + - settings: + spawnOnSuperCritical: true + minAmount: 5 + maxAmount: 15 + maxRange: 7 + floor: FloorAstroGrass + - type: EntitySpawnAnomaly + entries: + - settings: + spawnOnPulse: true + minAmount: 1 + maxAmount: 3 + maxRange: 1 + spawns: + - KudzuFlowerFriendly + - settings: + spawnOnSuperCritical: true + minAmount: 2 + maxAmount: 6 + maxRange: 3 + spawns: + - KudzuFlowerAngry + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionBluespace + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#00ccff" + - type: BluespaceAnomaly + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionFlesh + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#cb5b7e" + - type: TileSpawnAnomaly + entries: + - settings: + spawnOnPulse: true + spawnOnStabilityChanged: true + minAmount: 1 + maxAmount: 3 + maxRange: 2 + floor: FloorFlesh + - settings: + spawnOnSuperCritical: true + minAmount: 5 + maxAmount: 15 + maxRange: 5 + floor: FloorFlesh + - type: EntitySpawnAnomaly + entries: + - settings: + spawnOnPulse: true + minAmount: 1 + maxAmount: 2 + minRange: 1.5 + maxRange: 2.5 + spawns: + - FleshBlocker + - settings: + spawnOnPulse: true + maxAmount: 1 + minRange: 2.5 + maxRange: 4.5 + spawns: + - MobFleshJared + - MobFleshGolem + - MobFleshClamp + - MobFleshLover + - settings: + spawnOnSuperCritical: true + minAmount: 5 + maxAmount: 8 + minRange: 5 + maxRange: 15 + spawns: + - FleshBlocker + - settings: + spawnOnSuperCritical: true + minAmount: 2 + maxAmount: 5 + maxRange: 8 + spawns: + - MobFleshJared + - MobFleshGolem + - MobFleshClamp + - MobFleshLover + - settings: + spawnOnSuperCritical: true + minAmount: 2 + maxAmount: 4 + maxRange: 10 + spawns: + - FleshKudzu + - settings: + spawnOnShutdown: true + maxAmount: 2 + maxRange: 1 + spawns: + - MobFleshJared + - MobFleshGolem + - MobFleshClamp + - MobFleshLover + - FleshKudzu + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionGravity + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#1e070e" + - type: GravityAnomaly + maxGravityWellRange: 4 + maxThrowRange: 3 + maxThrowStrength: 5 + spaceRange: 1 + - type: GravityWell + maxRange: 0.7 + - type: SingularityDistortion + intensity: 100 + falloffPower: 2.7 + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionTech + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#56c1e8" + - type: TechAnomaly + linkRadius: + min: 2 + max: 5 + linkCountPerPulse: + min: 1 + max: 4 + linkCountSupercritical: 15 + - type: DeviceLinkSource + ports: + - Pulse + - Timer + - type: WirelessNetworkConnection + range: 10 + - type: DeviceNetwork + deviceNetId: Wireless + receiveFrequencyId: BasicDevice + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionRock + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#5ca8cb" + - type: TileSpawnAnomaly + entries: + - settings: + spawnOnPulse: true + minAmount: 7 + maxAmount: 10 + maxRange: 4 + floor: FloorAsteroidTile + - settings: + spawnOnSuperCritical: true + minAmount: 15 + maxAmount: 25 + maxRange: 6 + floor: FloorAsteroidTile + - type: EntitySpawnAnomaly + entries: + - settings: + spawnOnPulse: true + minAmount: 4 + maxAmount: 8 + minRange: 1 + maxRange: 3 + spawns: + - WallSpawnAsteroid + - WallSpawnAsteroid + - WallSpawnAsteroid + - WallSpawnAsteroid + - WallSpawnAsteroid + - WallSpawnAsteroidSilver + - WallSpawnAsteroidSilverCrab + - WallSpawnAsteroidIron + - WallSpawnAsteroidIronCrab + - WallSpawnAsteroidQuartz + - WallSpawnAsteroidQuartzCrab + - settings: + spawnOnPulse: true + maxAmount: 3 + minRange: 2.5 + maxRange: 4.5 + spawns: + - CrystalPink + - CrystalCyan + - settings: + spawnOnSuperCritical: true + minAmount: 15 + maxAmount: 20 + minRange: 2 + maxRange: 7 + spawns: + - CrystalPink + - CrystalCyan + - WallSpawnAsteroid + - WallSpawnAsteroid + - WallSpawnAsteroid + - WallSpawnAsteroid + - WallSpawnAsteroidSilver + - WallSpawnAsteroidSilverCrab + - WallSpawnAsteroidIron + - WallSpawnAsteroidIronCrab + - WallSpawnAsteroidQuartz + - WallSpawnAsteroidQuartzCrab + - settings: + spawnOnSuperCritical: true + minAmount: 3 + maxAmount: 5 + maxRange: 3 + spawns: + - MobSpawnCrabSilver + - MobSpawnCrabIron + - MobSpawnCrabQuartz \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injectors.yml b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injectors.yml new file mode 100644 index 0000000000..24a76dfb62 --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injectors.yml @@ -0,0 +1,320 @@ +- type: entity + name: anomaly injector + parent: MarkerBase + id: BaseAnomalyInjector + abstract: true + components: + - type: Physics + bodyType: Static + fixedRotation: true + - type: AmbientSound + range: 5 + volume: -5 + sound: + path: /Audio/Ambience/anomaly_drone.ogg + - type: Fixtures + fixtures: + anom: + shape: + !type:PhysShapeCircle + radius: 2.27 # i love 27 + hard: false + mask: + - MobMask + layer: + - MobLayer + - type: InnerBodyAnomalyInjector + whitelist: + tags: + - AnomalyHost + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapPyroclastic + suffix: Pyroclastic + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/Anomalies/pyro_anom.rsi + state: pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCorePyroclastic + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionPyroclastic + startMessage: inner-anomaly-start-message-pyro + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: fire + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: fire_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapElectricity + suffix: Electricity + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/anomaly.rsi + state: anom3-pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreElectricity + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionElectric + startMessage: inner-anomaly-start-message-shock + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: shock + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: shock_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapShadow + suffix: Shadow + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/Anomalies/shadow_anom.rsi + state: pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreShadow + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionShadow + startMessage: inner-anomaly-start-message-shadow + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: shadow + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: shadow_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapIce + suffix: Ice + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/Anomalies/ice_anom.rsi + state: pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreIce + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionIce + startMessage: inner-anomaly-start-message-frost + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: frost + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: frost_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapFlora + suffix: Flora + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/Anomalies/flora_anom.rsi + state: pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreFlora + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionFlora + startMessage: inner-anomaly-start-message-flora + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: flora + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: flora_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapBluespace + suffix: Bluespace + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/anomaly.rsi + state: anom4-pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreBluespace + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionBluespace + startMessage: inner-anomaly-start-message-bluespace + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: bluespace + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: bluespace_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapFlesh + suffix: Flesh + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/anomaly.rsi + state: anom5-pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreFlesh + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionFlesh + startMessage: inner-anomaly-start-message-flesh + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: flesh + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: flesh_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapGravity + suffix: Gravity + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/anomaly.rsi + state: anom2-pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreGravity + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionGravity + startMessage: inner-anomaly-start-message-grav + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: grav + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: grav_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapTech + suffix: Tech + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/Anomalies/tech_anom.rsi + state: pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreTech + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionTech + startMessage: inner-anomaly-start-message-tech + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: tech + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: tech_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapRock + suffix: Rock + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/anomaly.rsi + state: anom6-pulse + color: "#5ca8cb" + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreRock + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionRock + startMessage: inner-anomaly-start-message-rock + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: rock + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: rock_VOX \ No newline at end of file diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 8787dd7340..1ddee10e63 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -15,6 +15,9 @@ - type: Tag id: Ambrosia +- type: Tag + id: AnomalyHost + - type: Tag id: AppraisalTool diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/bluespace.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/bluespace.png new file mode 100644 index 0000000000..5e9311e064 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/bluespace.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/bluespace_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/bluespace_VOX.png new file mode 100644 index 0000000000..b594cac2a9 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/bluespace_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/fire.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/fire.png new file mode 100644 index 0000000000..a6fcde7a18 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/fire.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/fire_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/fire_VOX.png new file mode 100644 index 0000000000..91eb395704 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/fire_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flesh.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flesh.png new file mode 100644 index 0000000000..6e178307c3 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flesh.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flesh_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flesh_VOX.png new file mode 100644 index 0000000000..b1a1c43da0 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flesh_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora.png new file mode 100644 index 0000000000..70e369cb02 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora_VOX.png new file mode 100644 index 0000000000..97969116a9 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost.png new file mode 100644 index 0000000000..4823cb5a6f Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost_VOX.png new file mode 100644 index 0000000000..346cad09b1 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/grav.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/grav.png new file mode 100644 index 0000000000..c0775c0fb9 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/grav.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/grav_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/grav_VOX.png new file mode 100644 index 0000000000..04bfb8a0bf Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/grav_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/meta.json b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/meta.json new file mode 100644 index 0000000000..4e069d0cd9 --- /dev/null +++ b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/meta.json @@ -0,0 +1,643 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Created by TheShuEd (github) for Space Station 14", + "states": [ + { + "name": "bluespace", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "bluespace_VOX", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "fire", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "fire_VOX", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "flesh", + "directions": 4, + "delays": [ + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ] + ] + }, + { + "name": "flesh_VOX", + "directions": 4, + "delays": [ + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ] + ] + }, + { + "name": "flora", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "flora_VOX", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "frost", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "frost_VOX", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "grav", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "grav_VOX", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "rock", + "directions": 4, + "delays": [ + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "rock_VOX", + "directions": 4, + "delays": [ + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "shadow", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "shadow_VOX", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "shock", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "shock_VOX", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "tech", + "directions": 4, + "delays": [ + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ] + ] + }, + { + "name": "tech_VOX", + "directions": 4, + "delays": [ + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ] + ] + } + ] +} diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock.png new file mode 100644 index 0000000000..770e4b8d2f Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock_VOX.png new file mode 100644 index 0000000000..b11a9182fb Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shadow.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shadow.png new file mode 100644 index 0000000000..345248bc53 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shadow.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shadow_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shadow_VOX.png new file mode 100644 index 0000000000..6efb246ac1 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shadow_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shock.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shock.png new file mode 100644 index 0000000000..acdc87a291 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shock.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shock_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shock_VOX.png new file mode 100644 index 0000000000..d1f011c44d Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shock_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech.png new file mode 100644 index 0000000000..0611fa8a2a Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech_VOX.png new file mode 100644 index 0000000000..1787cdd4e0 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/anomaly.rsi/anom5-pulse.png b/Resources/Textures/Structures/Specific/anomaly.rsi/anom5-pulse.png index c0e547f8dd..d8b6f14c11 100644 Binary files a/Resources/Textures/Structures/Specific/anomaly.rsi/anom5-pulse.png and b/Resources/Textures/Structures/Specific/anomaly.rsi/anom5-pulse.png differ diff --git a/Resources/Textures/Structures/Specific/anomaly.rsi/anom5.png b/Resources/Textures/Structures/Specific/anomaly.rsi/anom5.png index 6117776649..a36dd2b64a 100644 Binary files a/Resources/Textures/Structures/Specific/anomaly.rsi/anom5.png and b/Resources/Textures/Structures/Specific/anomaly.rsi/anom5.png differ