diff --git a/Content.Client/_Stalker/Anomaly/STAnomalySystem.cs b/Content.Client/_Stalker/Anomaly/STAnomalySystem.cs new file mode 100644 index 00000000000..4b4b9dbb281 --- /dev/null +++ b/Content.Client/_Stalker/Anomaly/STAnomalySystem.cs @@ -0,0 +1,5 @@ +namespace Content.Client._Stalker.Anomaly; + +public sealed class STAnomalySystem : EntitySystem +{ +} diff --git a/Content.Client/_Stalker/Anomaly/STAnomalyTipsOverlay.cs b/Content.Client/_Stalker/Anomaly/STAnomalyTipsOverlay.cs new file mode 100644 index 00000000000..7aa3e5f1bcc --- /dev/null +++ b/Content.Client/_Stalker/Anomaly/STAnomalyTipsOverlay.cs @@ -0,0 +1,83 @@ +using System.Numerics; +using Content.Shared._Stalker.Anomaly; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.Physics; + +namespace Content.Client._Stalker.Anomaly; + +public sealed class STAnomalyTipsOverlay : Overlay +{ + [Dependency] private readonly IEntityManager _entity = default!; + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + + private readonly SpriteSystem _sprite; + private readonly TransformSystem _transform; + private readonly EntityLookupSystem _lookup; + + public STAnomalyTipsOverlay() + { + IoCManager.InjectDependencies(this); + + _sprite = _entity.System(); + _transform = _entity.System(); + _lookup = _entity.System(); + } + + protected override void Draw(in OverlayDrawArgs args) + { + var eye = args.Viewport.Eye; + var rotation = args.Viewport.Eye?.Rotation ?? Angle.Zero; + var viewport = args.WorldAABB; + + var handle = args.WorldHandle; + + const float scale = 1f; + + var scaleMatrix = Matrix3x2.CreateScale(new Vector2(scale, scale)); + var rotationMatrix = Matrix3x2.CreateRotation(-(float)rotation.Theta); + + var entities = _entity.EntityQueryEnumerator(); + while (entities.MoveNext(out _, out var tips, out var xform, out var fixtures)) + { + if (xform.MapID != eye?.Position.MapId) + continue; + + var worldPosition = _transform.GetWorldPosition(xform); + var worldMatrix = Matrix3x2.CreateTranslation(worldPosition); + + var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix); + var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld); + + handle.SetTransform(matty); + + var size = fixtures.Fixtures.TryGetValue("fix1", out var fixture) + ? (int) MathF.Round(MathF.Max(0, fixture.Shape.Radius - 0.5f)) + : 0; + + var texture = _sprite.GetFrame(tips.Icon, TimeSpan.Zero); + var color = Color.White.WithAlpha(tips.Visibility); + + if (size == 0) + { + handle.DrawTexture(texture, tips.Offset, color); + continue; + } + + for (var x = -size; x <= size; x++) + { + for (var y = -size; y <= size; y++) + { + if (!args.WorldAABB.Contains(worldPosition + new Vector2(x, y) + tips.Offset)) + continue; + + handle.DrawTexture(texture, new Vector2(x, y) + tips.Offset, color); + } + } + } + + handle.SetTransform(Matrix3x2.Identity); + } +} diff --git a/Content.Client/_Stalker/Anomaly/STAnomalyTipsSystem.cs b/Content.Client/_Stalker/Anomaly/STAnomalyTipsSystem.cs new file mode 100644 index 00000000000..733b578fbef --- /dev/null +++ b/Content.Client/_Stalker/Anomaly/STAnomalyTipsSystem.cs @@ -0,0 +1,80 @@ +using Content.Shared._Stalker.Anomaly; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Player; + +namespace Content.Client._Stalker.Anomaly; + +public sealed class STAnomalyTipsSystem : EntitySystem +{ + [Dependency] private readonly IOverlayManager _overlayMan = default!; + [Dependency] private readonly IPlayerManager _player = default!; + + private STAnomalyTipsOverlay? _overlay; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnAttached); + SubscribeLocalEvent(OnDetached); + } + + private void OnInit(Entity entity, ref ComponentInit args) + { + if (_player.LocalEntity is null) + return; + + if (_player.LocalEntity != entity) + return; + + AddOverlay(); + } + + private void OnShutdown(Entity entity, ref ComponentShutdown args) + { + if (_player.LocalEntity is null) + return; + + if (_player.LocalEntity != entity) + return; + + RemoveOverlay(); + } + + private void OnAttached(Entity entity, ref LocalPlayerAttachedEvent args) + { + AddOverlay(); + } + + private void OnDetached(Entity entity, ref LocalPlayerDetachedEvent args) + { + RemoveOverlay(); + } + + private void AddOverlay() + { + if (_overlay != null) + return; + + _overlay = new STAnomalyTipsOverlay(); + _overlayMan.AddOverlay(_overlay); + } + + private void RemoveOverlay() + { + if (_overlay == null) + return; + + _overlayMan.RemoveOverlay(_overlay); + _overlay = null; + } + + public override void Shutdown() + { + base.Shutdown(); + _overlayMan.RemoveOverlay(); + } +} diff --git a/Content.Client/_Stalker/ZoneAnomaly/Audio/ZoneAnomalyProximitySoundSystem.cs b/Content.Client/_Stalker/ZoneAnomaly/Audio/ZoneAnomalyProximitySoundSystem.cs new file mode 100644 index 00000000000..46ea1c21fff --- /dev/null +++ b/Content.Client/_Stalker/ZoneAnomaly/Audio/ZoneAnomalyProximitySoundSystem.cs @@ -0,0 +1,163 @@ +using Content.Shared._Stalker.ZoneAnomaly.Audio; +using Robust.Client.GameObjects; +using Robust.Client.Player; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Player; +using Robust.Shared.Timing; + +namespace Content.Client._Stalker.ZoneAnomaly.Audio; + +/// +/// Handles distance-based volume scaling for zone anomaly proximity sounds. +/// Uses cooldown-based updates for performance efficiency. +/// +public sealed class ZoneAnomalyProximitySoundSystem : EntitySystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + private static readonly AudioParams BaseParams = AudioParams.Default + .WithLoop(true) + .WithVolume(0f); // Start at 0, we'll control volume ourselves + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnShutdown); + } + + private void OnStartup(EntityUid uid, ZoneAnomalyProximitySoundComponent component, ComponentStartup args) + { + // Start playing the sound immediately (at initial calculated volume) + var audioParams = BaseParams.WithMaxDistance(component.MaxRange); + + var stream = _audio.PlayEntity(component.Sound, Filter.Local(), uid, false, audioParams); + if (stream != null) + { + component.PlayingStream = stream.Value.Entity; + component.CurrentVolume = component.MinVolume; + + // Set initial volume based on distance + UpdateVolumeForEntity(uid, component); + } + } + + private void OnShutdown(EntityUid uid, ZoneAnomalyProximitySoundComponent component, ComponentShutdown args) + { + if (component.PlayingStream != null) + { + _audio.Stop(component.PlayingStream); + component.PlayingStream = null; + } + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (!_timing.IsFirstTimePredicted) + return; + + var curTime = _timing.CurTime; + + // Get player position once per update + var playerEntity = _player.LocalEntity; + if (playerEntity == null || !TryComp(playerEntity, out var playerXform)) + return; + + var playerPos = _transform.GetWorldPosition(playerXform); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp, out var xform)) + { + // Skip if not time to update yet + if (curTime < comp.NextUpdate) + continue; + + comp.NextUpdate = curTime + TimeSpan.FromSeconds(comp.UpdateCooldown); + + // Skip if no stream playing + if (comp.PlayingStream == null) + continue; + + // Skip if on different map + if (xform.MapID != playerXform.MapID) + { + // Mute if on different map + if (comp.CurrentVolume > 0) + { + comp.CurrentVolume = 0; + _audio.SetVolume(comp.PlayingStream, SharedAudioSystem.GainToVolume(0f)); + } + continue; + } + + // Calculate distance + var entityPos = _transform.GetWorldPosition(xform); + var distance = (playerPos - entityPos).Length(); + + // Calculate target volume based on distance + float targetVolume; + if (distance >= comp.MaxRange) + { + targetVolume = 0f; // Out of range, silent + } + else + { + // Linear interpolation: MaxVolume at center, MinVolume at MaxRange + var t = 1f - (distance / comp.MaxRange); + targetVolume = comp.MinVolume + (comp.MaxVolume - comp.MinVolume) * t; + } + + // Apply volume if changed significantly (avoid unnecessary audio calls) + if (MathF.Abs(targetVolume - comp.CurrentVolume) > 0.01f) + { + comp.CurrentVolume = targetVolume; + // Convert our 0-1 volume to the audio system's gain/volume + _audio.SetVolume(comp.PlayingStream, SharedAudioSystem.GainToVolume(targetVolume)); + } + } + } + + private void UpdateVolumeForEntity(EntityUid uid, ZoneAnomalyProximitySoundComponent component) + { + if (component.PlayingStream == null) + return; + + var playerEntity = _player.LocalEntity; + if (playerEntity == null || !TryComp(playerEntity, out var playerXform)) + return; + + if (!TryComp(uid, out var xform)) + return; + + if (xform.MapID != playerXform.MapID) + { + _audio.SetVolume(component.PlayingStream, SharedAudioSystem.GainToVolume(0f)); + return; + } + + var playerPos = _transform.GetWorldPosition(playerXform); + var entityPos = _transform.GetWorldPosition(xform); + var distance = (playerPos - entityPos).Length(); + + float targetVolume; + if (distance >= component.MaxRange) + { + targetVolume = 0f; + } + else + { + var t = 1f - (distance / component.MaxRange); + targetVolume = component.MinVolume + (component.MaxVolume - component.MinVolume) * t; + } + + component.CurrentVolume = targetVolume; + _audio.SetVolume(component.PlayingStream, SharedAudioSystem.GainToVolume(targetVolume)); + } +} diff --git a/Content.Client/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyStealthVisualsSystem.cs b/Content.Client/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyStealthVisualsSystem.cs new file mode 100644 index 00000000000..8898c6f6cf9 --- /dev/null +++ b/Content.Client/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyStealthVisualsSystem.cs @@ -0,0 +1,34 @@ +using Content.Client.Stealth; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Content.Shared.Stealth; +using Content.Shared.Stealth.Components; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; + +namespace Content.Client._Stalker.ZoneAnomaly.Effects.Systems; + +public sealed class ZoneAnomalyStealthVisualsSystem : EntitySystem +{ + [Dependency] private readonly SharedStealthSystem _stealth = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnShaderRender, after: [typeof(StealthSystem)]); + } + + private void OnShaderRender(EntityUid uid, ZoneAnomalyEffectStealthComponent component, BeforePostShaderRenderEvent args) + { + // Override the blue tint from the standard stealth system + // Use grayscale instead so anomalies fade without blue hue + if (!TryComp(uid, out var stealth)) + return; + + var visibility = _stealth.GetVisibility(uid, stealth); + visibility = Math.Clamp(visibility, -1f, 1f); + visibility = MathF.Max(0, visibility); + + // Set all channels to visibility (grayscale) instead of blue tint + args.Sprite.Color = new Color(visibility, visibility, visibility, 1); + } +} diff --git a/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectDamageComponent.cs b/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectDamageComponent.cs new file mode 100644 index 00000000000..845510c907e --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectDamageComponent.cs @@ -0,0 +1,20 @@ +using Content.Shared.Damage; + +namespace Content.Server._Stalker.Anomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class STAnomalyEffectDamageComponent : Component +{ + [DataField] + public Dictionary Options = new(); +} + +[Serializable, DataDefinition] +public partial struct STAnomalyDamageEffectOptions +{ + [DataField] + public DamageSpecifier Damage; + + [DataField] + public float Range = 1f; +} diff --git a/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectDamageSoundConditionalComponent.cs b/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectDamageSoundConditionalComponent.cs new file mode 100644 index 00000000000..3633ad02fee --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectDamageSoundConditionalComponent.cs @@ -0,0 +1,58 @@ +using Robust.Shared.Audio; + +namespace Content.Server._Stalker.Anomaly.Effects.Components; + +/// +/// Plays different sounds based on damage type and target weight. +/// +[RegisterComponent] +public sealed partial class STAnomalyEffectDamageSoundConditionalComponent : Component +{ + /// + /// Sound to play whenever walking through the anomaly (always plays on trigger). + /// + [DataField] + public SoundSpecifier? PassthroughSound; + + /// + /// Weight threshold for weight bonus sound (kg). + /// + [DataField] + public float WeightThreshold = 100f; + + /// + /// Sound to play for base damage. + /// + [DataField] + public SoundSpecifier? BaseDamageSound; + + /// + /// Sound to play for double damage (no boots). + /// + [DataField] + public SoundSpecifier? DoubleDamageSound; + + /// + /// Sound to play for weight bonus damage. Replaces other sounds. + /// + [DataField] + public SoundSpecifier? WeightBonusSound; + + /// + /// Trigger group name for base damage. + /// + [DataField] + public string BaseDamageGroup = "StateActiveBase"; + + /// + /// Trigger group name for double damage. + /// + [DataField] + public string DoubleDamageGroup = "StateActiveDouble"; + + /// + /// Range to search for entities. + /// + [DataField] + public float Range = 0.5f; +} diff --git a/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectDamageWeightBonusComponent.cs b/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectDamageWeightBonusComponent.cs new file mode 100644 index 00000000000..713d7755a33 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectDamageWeightBonusComponent.cs @@ -0,0 +1,60 @@ +using Content.Shared.Damage; + +namespace Content.Server._Stalker.Anomaly.Effects.Components; + +/// +/// Applies additional damage based on entity weight. +/// Damage scales from WeightThreshold to WeightCap. +/// +[RegisterComponent] +public sealed partial class STAnomalyEffectDamageWeightBonusComponent : Component +{ + /// + /// Weight threshold where bonus damage starts (kg). + /// + [DataField] + public float WeightThreshold = 100f; + + /// + /// Weight where bonus damage caps (kg). + /// + [DataField] + public float WeightCap = 190f; + + /// + /// Bonus multiplier per 10kg over threshold (0.1 = 10%). + /// + [DataField] + public float BonusPerTenKg = 0.1f; + + /// + /// Maximum bonus multiplier (1.0 = 100% extra damage). + /// + [DataField] + public float MaxBonus = 1.0f; + + /// + /// Options mapping trigger groups to base damage for weight bonus calculation. + /// + [DataField] + public Dictionary Options = new(); +} + +/// +/// Configuration for weight-based damage bonus per trigger group. +/// +[Serializable, DataDefinition] +public partial struct STAnomalyDamageWeightBonusOptions +{ + /// + /// Base damage to apply weight bonus to. + /// + [DataField] + public DamageSpecifier Damage; + + /// + /// Range to search for entities. + /// + [DataField] + public float Range = 1f; +} diff --git a/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectExplosionComponent.cs b/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectExplosionComponent.cs new file mode 100644 index 00000000000..4129a9a3657 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectExplosionComponent.cs @@ -0,0 +1,27 @@ +using Content.Shared.Explosion; +using Robust.Shared.Prototypes; + +namespace Content.Server._Stalker.Anomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class STAnomalyEffectExplosionComponent : Component +{ + [DataField] + public Dictionary Options = new(); + + [Serializable, DataDefinition] + public partial struct Option + { + [DataField] + public ProtoId Id = "Son"; + + [DataField] + public float Intensity = 500f; + + [DataField] + public float Slope = 4f; + + [DataField] + public float MaxIntensity = 1000f; + } +} diff --git a/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectSoundComponent.cs b/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectSoundComponent.cs new file mode 100644 index 00000000000..d86edb87a2a --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectSoundComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.Audio; + +namespace Content.Server._Stalker.Anomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class STAnomalyEffectSoundComponent : Component +{ + [DataField] + public Dictionary Options = new(); +} + +[Serializable, DataDefinition] +public partial struct STAnomalyEffectSoundOptions +{ + [DataField] + public SoundSpecifier? Sound; +} diff --git a/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectSpawnComponent.cs b/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectSpawnComponent.cs new file mode 100644 index 00000000000..052e9813ba3 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectSpawnComponent.cs @@ -0,0 +1,27 @@ +using System.Numerics; +using Robust.Shared.Prototypes; + +namespace Content.Server._Stalker.Anomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class STAnomalyEffectSpawnComponent : Component +{ + [DataField] + public Dictionary Options = new(); + + [Serializable, DataDefinition] + public partial struct Option + { + [DataField] + public EntProtoId Id; + + [DataField] + public ComponentRegistry Components; + + [DataField] + public float Range; + + [DataField] + public Vector2 Offset; + } +} diff --git a/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectThrowComponent.cs b/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectThrowComponent.cs new file mode 100644 index 00000000000..27188952831 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Effects/Components/STAnomalyEffectThrowComponent.cs @@ -0,0 +1,32 @@ +namespace Content.Server._Stalker.Anomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class STAnomalyEffectThrowComponent : Component +{ + [DataField] + public Dictionary Options = new(); +} + +[Serializable, DataDefinition] +public partial struct STAnomalyEffectThrowOptions +{ + [DataField] + public float Range = 1f; + + [DataField] + public float Force = 5f; + + [DataField] + public float Distance = 5f; + + [DataField] + public STAnomalyEffectThrowType Type = STAnomalyEffectThrowType.RandomDirection; +} + +[Serializable] +public enum STAnomalyEffectThrowType +{ + RandomDirection, + ToAnomaly, + FromAnomaly, +} diff --git a/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectDamageSoundConditionalSystem.cs b/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectDamageSoundConditionalSystem.cs new file mode 100644 index 00000000000..303537db237 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectDamageSoundConditionalSystem.cs @@ -0,0 +1,64 @@ +using Content.Server._Stalker.Anomaly.Effects.Components; +using Content.Shared._Stalker.Anomaly.Triggers.Events; +using Content.Shared._Stalker.Weight; +using Robust.Server.Audio; +using Robust.Shared.Physics; + +namespace Content.Server._Stalker.Anomaly.Effects.Systems; + +/// +/// Plays different sounds based on damage type and target weight. +/// Weight bonus sound has highest priority and replaces other sounds. +/// Only plays when damage is actually being dealt (StateActiveBase or StateActiveDouble). +/// +public sealed class STAnomalyEffectDamageSoundConditionalSystem : EntitySystem +{ + [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly EntityLookupSystem _entityLookup = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTriggered); + } + + private void OnTriggered( + Entity effect, + ref STAnomalyTriggerEvent args) + { + var comp = effect.Comp; + var coords = Transform(effect).Coordinates; + + // Always play passthrough sound when walking through + if (comp.PassthroughSound != null) + _audio.PlayPredicted(comp.PassthroughSound, coords, effect); + + // Check if this is a damage-dealing trigger (must have one of the damage groups) + var isDouble = args.Groups.Contains(comp.DoubleDamageGroup); + var isBase = args.Groups.Contains(comp.BaseDamageGroup); + + // No damage groups present - don't play any hit sound + if (!isDouble && !isBase) + return; + + // Find entities in range that might be damaged + var entities = _entityLookup.GetEntitiesInRange(coords, comp.Range, LookupFlags.Uncontained); + + // Check if any entity is heavy enough for weight bonus sound + foreach (var entity in entities) + { + if (entity.Comp.Total >= comp.WeightThreshold && comp.WeightBonusSound != null) + { + _audio.PlayPredicted(comp.WeightBonusSound, coords, effect); + return; + } + } + + // No heavy entity - play sound based on damage type + var sound = isDouble ? comp.DoubleDamageSound : comp.BaseDamageSound; + + if (sound != null) + _audio.PlayPredicted(sound, coords, effect); + } +} diff --git a/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectDamageSystem.cs b/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectDamageSystem.cs new file mode 100644 index 00000000000..71193c19a14 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectDamageSystem.cs @@ -0,0 +1,36 @@ +using Content.Server._Stalker.Anomaly.Effects.Components; +using Content.Shared._Stalker.Anomaly.Triggers.Events; +using Content.Shared.Damage; +using Robust.Shared.Physics; + +namespace Content.Server._Stalker.Anomaly.Effects.Systems; + +public sealed class STAnomalyEffectDamageSystem : EntitySystem +{ + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly EntityLookupSystem _entityLookup = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTriggered); + } + + private void OnTriggered(Entity effect, ref STAnomalyTriggerEvent args) + { + foreach (var group in args.Groups) + { + if (!effect.Comp.Options.TryGetValue(group, out var options)) + continue; + + var entities = + _entityLookup.GetEntitiesInRange(Transform(effect).Coordinates, options.Range, LookupFlags.Uncontained); + + foreach (var entity in entities) + { + _damageable.TryChangeDamage(entity, options.Damage, damageable: entity.Comp); + } + } + } +} diff --git a/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectDamageWeightBonusSystem.cs b/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectDamageWeightBonusSystem.cs new file mode 100644 index 00000000000..489a0eba709 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectDamageWeightBonusSystem.cs @@ -0,0 +1,62 @@ +using Content.Server._Stalker.Anomaly.Effects.Components; +using Content.Shared._Stalker.Anomaly.Triggers.Events; +using Content.Shared._Stalker.Weight; +using Content.Shared.Damage; +using Robust.Shared.Physics; + +namespace Content.Server._Stalker.Anomaly.Effects.Systems; + +/// +/// Applies additional damage based on entity weight when anomaly triggers. +/// +public sealed class STAnomalyEffectDamageWeightBonusSystem : EntitySystem +{ + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly EntityLookupSystem _entityLookup = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTriggered); + } + + private void OnTriggered( + Entity effect, + ref STAnomalyTriggerEvent args) + { + foreach (var group in args.Groups) + { + if (!effect.Comp.Options.TryGetValue(group, out var options)) + continue; + + var entities = + _entityLookup.GetEntitiesInRange(Transform(effect).Coordinates, options.Range, LookupFlags.Uncontained); + + foreach (var entity in entities) + { + var bonus = CalculateWeightBonus(entity.Comp.Total, effect.Comp); + if (bonus <= 0) + continue; + + // Apply bonus damage (base damage * bonus multiplier) + var bonusDamage = options.Damage * bonus; + _damageable.TryChangeDamage(entity, bonusDamage); + } + } + } + + private float CalculateWeightBonus( + float totalWeight, + STAnomalyEffectDamageWeightBonusComponent comp) + { + if (totalWeight < comp.WeightThreshold) + return 0f; + + // Calculate bonus: (weight - threshold) / 10 * bonusPerTenKg + var overWeight = totalWeight - comp.WeightThreshold; + var bonus = overWeight / 10f * comp.BonusPerTenKg; + + return Math.Min(bonus, comp.MaxBonus); + } +} diff --git a/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectExplosionSystem.cs b/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectExplosionSystem.cs new file mode 100644 index 00000000000..5872054613d --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectExplosionSystem.cs @@ -0,0 +1,28 @@ +using Content.Server._Stalker.Anomaly.Effects.Components; +using Content.Server.Explosion.EntitySystems; +using Content.Shared._Stalker.Anomaly.Triggers.Events; + +namespace Content.Server._Stalker.Anomaly.Effects.Systems; + +public sealed class STAnomalyEffectExplosionSystem : EntitySystem +{ + [Dependency] private readonly ExplosionSystem _explosion = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTriggered); + } + + private void OnTriggered(Entity effect, ref STAnomalyTriggerEvent args) + { + foreach (var group in args.Groups) + { + if (!effect.Comp.Options.TryGetValue(group, out var option)) + continue; + + _explosion.QueueExplosion(effect, option.Id, option.Intensity, option.Slope, option.MaxIntensity, canCreateVacuum: false, addLog: false); + } + } +} diff --git a/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectSoundSystem.cs b/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectSoundSystem.cs new file mode 100644 index 00000000000..f4db0d83faa --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectSoundSystem.cs @@ -0,0 +1,28 @@ +using Content.Server._Stalker.Anomaly.Effects.Components; +using Content.Shared._Stalker.Anomaly.Triggers.Events; +using Robust.Server.Audio; + +namespace Content.Server._Stalker.Anomaly.Effects.Systems; + +public sealed class STAnomalyEffectSoundSystem : EntitySystem +{ + [Dependency] private readonly AudioSystem _audio = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTriggered); + } + + private void OnTriggered(Entity effect, ref STAnomalyTriggerEvent args) + { + foreach (var group in args.Groups) + { + if (!effect.Comp.Options.TryGetValue(group, out var options)) + continue; + + _audio.PlayPredicted(options.Sound, Transform(effect).Coordinates, effect); + } + } +} diff --git a/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectSpawnSystem.cs b/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectSpawnSystem.cs new file mode 100644 index 00000000000..747db42fecd --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectSpawnSystem.cs @@ -0,0 +1,33 @@ +using Content.Server._Stalker.Anomaly.Effects.Components; +using Content.Shared._Stalker.Anomaly.Triggers.Events; +using Robust.Shared.Random; + +namespace Content.Server._Stalker.Anomaly.Effects.Systems; + +public sealed class STAnomalyEffectSpawnSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTriggered); + } + + private void OnTriggered(Entity effect, ref STAnomalyTriggerEvent args) + { + foreach (var group in args.Groups) + { + if (!effect.Comp.Options.TryGetValue(group, out var option)) + continue; + + var position = Transform(effect).Coordinates; + if (option.Range != 0) + position = position.Offset(option.Offset).Offset(_random.NextVector2(option.Range)); + + var entity = Spawn(option.Id, position); + EntityManager.AddComponents(entity, option.Components); + } + } +} diff --git a/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectThrowSystem.cs b/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectThrowSystem.cs new file mode 100644 index 00000000000..f20f8e9a7d0 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Effects/Systems/STAnomalyEffectThrowSystem.cs @@ -0,0 +1,80 @@ +using System.Numerics; +using Content.Server._Stalker.Anomaly.Effects.Components; +using Content.Shared._Stalker.Anomaly.Triggers.Events; +using Content.Shared.Throwing; +using Robust.Server.GameObjects; +using Robust.Shared.Physics.Components; +using Robust.Shared.Random; + +namespace Content.Server._Stalker.Anomaly.Effects.Systems; + +public sealed class STAnomalyEffectThrowSystem : EntitySystem +{ + [Dependency] private readonly EntityLookupSystem _entityLookup = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly ThrowingSystem _throwing = default!; + [Dependency] private readonly TransformSystem _transform = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTriggered); + } + + private void OnTriggered(Entity effect, ref STAnomalyTriggerEvent args) + { + foreach (var group in args.Groups) + { + if (!effect.Comp.Options.TryGetValue(group, out var options)) + continue; + + var entities = + _entityLookup.GetEntitiesInRange(Transform(effect).Coordinates, options.Range); + + foreach (var entity in entities) + { + switch (options.Type) + { + case STAnomalyEffectThrowType.RandomDirection: + ThrowRandomDirection(effect, options, entity); + break; + + case STAnomalyEffectThrowType.FromAnomaly: + ThrowFromAnomaly(effect, options, entity); + break; + + case STAnomalyEffectThrowType.ToAnomaly: + ThrowToAnomaly(effect, options, entity); + break; + + default: + throw new ArgumentOutOfRangeException(); + } + } + } + } + + private void ThrowRandomDirection(Entity effect, STAnomalyEffectThrowOptions options, EntityUid target) + { + var direction = _random.NextAngle(360); + ThrowDirection(options, target, direction.ToVec()); + } + + private void ThrowFromAnomaly(Entity effect, STAnomalyEffectThrowOptions options, EntityUid target) + { + var direction = (_transform.GetWorldPosition(target) - _transform.GetWorldPosition(effect)).Normalized(); + ThrowDirection(options, target, direction); + } + + private void ThrowToAnomaly(Entity effect, STAnomalyEffectThrowOptions options, EntityUid target) + { + var direction = (_transform.GetWorldPosition(effect) - _transform.GetWorldPosition(target)).Normalized(); + ThrowDirection(options, target, direction); + } + + private void ThrowDirection(STAnomalyEffectThrowOptions options, EntityUid target, Vector2 direction) + { + _throwing.TryThrow(target, direction * options.Distance, options.Force, null, 0); + } +} diff --git a/Content.Server/_Stalker/Anomaly/Generation/Components/STAnomalyGeneratorComponent.cs b/Content.Server/_Stalker/Anomaly/Generation/Components/STAnomalyGeneratorComponent.cs new file mode 100644 index 00000000000..2a129824712 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Generation/Components/STAnomalyGeneratorComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.Map; + +namespace Content.Server._Stalker.Anomaly.Generation.Components; + +[RegisterComponent] +public sealed partial class STAnomalyGeneratorComponent : Component +{ + [ViewVariables] + public Dictionary> MapGeneratedAnomalies = new(); +} diff --git a/Content.Server/_Stalker/Anomaly/Generation/Components/STAnomalyGeneratorSpawnBlockerComponent.cs b/Content.Server/_Stalker/Anomaly/Generation/Components/STAnomalyGeneratorSpawnBlockerComponent.cs new file mode 100644 index 00000000000..b2ea1b977c2 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Generation/Components/STAnomalyGeneratorSpawnBlockerComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Server._Stalker.Anomaly.Generation.Components; + +[RegisterComponent] +public sealed partial class STAnomalyGeneratorSpawnBlockerComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public int Size = 5; +} diff --git a/Content.Server/_Stalker/Anomaly/Generation/Components/STAnomalyGeneratorTargetComponent.cs b/Content.Server/_Stalker/Anomaly/Generation/Components/STAnomalyGeneratorTargetComponent.cs new file mode 100644 index 00000000000..a17e408abf8 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Generation/Components/STAnomalyGeneratorTargetComponent.cs @@ -0,0 +1,11 @@ +using Content.Shared._Stalker.Anomaly.Prototypes; +using Robust.Shared.Prototypes; + +namespace Content.Server._Stalker.Anomaly.Generation.Components; + +[RegisterComponent] +public sealed partial class STAnomalyGeneratorTargetComponent : Component +{ + [DataField] + public ProtoId OptionsId; +} diff --git a/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationJob.Operations.cs b/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationJob.Operations.cs new file mode 100644 index 00000000000..634c82c2edb --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationJob.Operations.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; + +namespace Content.Server._Stalker.Anomaly.Generation.Jobs; + +public sealed partial class STAnomalyGenerationJob +{ + /// + /// Default value, for all operations in generation. + /// + private const int Operations = 500; + + /// + /// Used in method + /// to maintain the number of committed to suspend Job activity, + /// to maintain productivity. + /// + private int _operationCounter; + + private async Task MakeOperation(int operations = -1) + { + var maxOperations = operations == -1 ? Operations : operations; + + if (_operationCounter > maxOperations) + { + _operationCounter -= maxOperations; + await SuspendNow(); + } + + _operationCounter++; + } +} diff --git a/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationJob.Placing.cs b/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationJob.Placing.cs new file mode 100644 index 00000000000..5ee9bcf934e --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationJob.Placing.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; +using Content.Shared._Stalker.Anomaly.Data; +using Robust.Shared.Map.Components; + +namespace Content.Server._Stalker.Anomaly.Generation.Jobs; + +public sealed partial class STAnomalyGenerationJob +{ + private async Task TrySpawn(STAnomalyGeneratorAnomalyEntry anomalyEntry, Vector2i coords) + { + if (!_tileCoordinates.TryGetValue(coords, out var tileRef)) + return EntityUid.Invalid; + + if (!await PlaceFree(anomalyEntry, coords)) + return EntityUid.Invalid; + + var gridComp = _entityManager.EnsureComponent(tileRef.GridUid); + var targetCoords = _map.GridTileToWorld(tileRef.GridUid, gridComp, tileRef.GridIndices); + + return _entityManager.Spawn(anomalyEntry.ProtoId, targetCoords); + } + + private async Task PlaceFree(STAnomalyGeneratorAnomalyEntry anomalyEntry, Vector2i coords) + { + var tiles = GetAnomalyTiles(anomalyEntry, coords); + foreach (var tile in tiles) + { + await MakeOperation(); + + if (_tileCoordinates.ContainsKey(tile)) + continue; + + return false; + } + + return true; + } +} diff --git a/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationJob.Utils.cs b/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationJob.Utils.cs new file mode 100644 index 00000000000..37a1eb5f831 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationJob.Utils.cs @@ -0,0 +1,130 @@ +using System.Numerics; +using Content.Shared.Physics; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Physics; +using Content.Shared._Stalker.Anomaly.Data; + +namespace Content.Server._Stalker.Anomaly.Generation.Jobs; + +public sealed partial class STAnomalyGenerationJob +{ + #region Anomaly + + private HashSet GetAnomalyTiles(STAnomalyGeneratorAnomalyEntry anomalyEntry, Vector2i coords) + { + return GetBoxElements(coords, GetAnomalySize(anomalyEntry)); + } + + private int GetAnomalySize(STAnomalyGeneratorAnomalyEntry anomalyEntry) + { + return _anomalySizes[anomalyEntry.ProtoId]; + } + + #endregion + + #region Box2i + + private HashSet GetBoxElements(Vector2i coords, int radius) + { + if (radius == 0) + return new HashSet { coords }; + + var set = new HashSet(); + var box2 = new Box2i(coords - radius, coords + radius + 1); + + for (var x = box2.Left; x < box2.Right; x++) + { + for (var y = box2.Bottom; y < box2.Top; y++) + { + set.Add(new Vector2i(x, y)); + } + } + + return set; + } + + #endregion + + #region Turf + + private bool TileSolidAndNotBlocked(TileRef tile, Func? predicate = null) + { + return _turf.GetContentTileDefinition(tile).Sturdy && + !IsTileBlocked(tile, CollisionGroup.LowImpassable, predicate: predicate); + } + + private bool IsTileBlocked(TileRef turf, CollisionGroup mask, Func? predicate = null, float minIntersectionArea = 0.1f) + { + return IsTileBlocked(turf.GridUid, turf.GridIndices, mask, predicate: predicate, minIntersectionArea: minIntersectionArea); + } + + private bool IsTileBlocked(EntityUid gridUid, + Vector2i indices, + CollisionGroup mask, + Func? predicate = null, + MapGridComponent? grid = null, + TransformComponent? gridXform = null, + float minIntersectionArea = 0.1f) + { + if (!_entityManager.TryGetComponent(gridUid, out grid)) + return false; + + if (!_entityManager.TryGetComponent(gridUid, out gridXform)) + return false; + + var xformQuery = _entityManager.GetEntityQuery(); + var (gridPos, gridRot, matrix) = _transform.GetWorldPositionRotationMatrix(gridXform, xformQuery); + + var size = grid.TileSize; + var localPos = new Vector2(indices.X * size + (size / 2f), indices.Y * size + (size / 2f)); + var worldPos = Vector2.Transform(localPos, matrix); + + // This is scaled to 95 % so it doesn't encompass walls on other tiles. + var tileAabb = Box2.UnitCentered.Scale(0.95f * size); + var worldBox = new Box2Rotated(tileAabb.Translated(worldPos), gridRot, worldPos); + tileAabb = tileAabb.Translated(localPos); + + var intersectionArea = 0f; + var fixtureQuery = _entityManager.GetEntityQuery(); + + predicate ??= _ => false; + + foreach (var ent in _entityLookup.GetEntitiesIntersecting(gridUid, worldBox, LookupFlags.Dynamic | LookupFlags.Static)) + { + if (predicate.Invoke(ent)) + continue; + + if (!fixtureQuery.TryGetComponent(ent, out var fixtures)) + continue; + + // get grid local coordinates + var (pos, rot) = _transform.GetWorldPositionRotation(xformQuery.GetComponent(ent), xformQuery); + rot -= gridRot; + pos = (-gridRot).RotateVec(pos - gridPos); + + var xform = new Transform(pos, (float) rot.Theta); + + foreach (var fixture in fixtures.Fixtures.Values) + { + if (!fixture.Hard) + continue; + + if ((fixture.CollisionLayer & (int) mask) == 0) + continue; + + for (var i = 0; i < fixture.Shape.ChildCount; i++) + { + var intersection = fixture.Shape.ComputeAABB(xform, i).Intersect(tileAabb); + intersectionArea += intersection.Width * intersection.Height; + if (intersectionArea > minIntersectionArea) + return true; + } + } + } + + return false; + } + + #endregion +} diff --git a/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationJob.cs b/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationJob.cs new file mode 100644 index 00000000000..cc5635fd933 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationJob.cs @@ -0,0 +1,201 @@ +using Content.Server._Stalker.Anomaly.Generation.Components; +using Content.Shared._Stalker.Anomaly.Data; +using Content.Shared.Maps; +using Content.Shared.Physics; +using Content.Shared.Tag; +using Robust.Server.GameObjects; +using Robust.Shared.CPUJob.JobQueues; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using System.Collections.Frozen; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Content.Server._Stalker.Anomaly.Generation.Jobs; + +public sealed partial class STAnomalyGenerationJob : Job +{ + private static readonly ProtoId TagGenerationIntersectionSkip = "STAnomalyGenerationIntersectionSkip"; + + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + public readonly STAnomalyGenerationOptions Options; + + private readonly EntityLookupSystem _entityLookup; + private readonly TagSystem _tag; + private readonly TransformSystem _transform; + private readonly MapSystem _map; + private readonly TurfSystem _turf; + + private readonly FrozenDictionary _anomalySizes; + + private readonly Dictionary _tileCoordinates = new(); + private readonly Dictionary _tileCoordinatesSpawn = new(); + + public STAnomalyGenerationJob(STAnomalyGenerationOptions options, double maxTime, CancellationToken cancellation = default) : base(maxTime, cancellation) + { + Options = options; + + // Include IoC + IoCManager.InjectDependencies(this); + + // Include entity systems + _entityLookup = _entityManager.System(); + _tag = _entityManager.System(); + _transform = _entityManager.System(); + _map = _entityManager.System(); + _turf = _entityManager.System(); + + // Hashing + _anomalySizes = GetHashAnomalySize(); + } + + private FrozenDictionary GetHashAnomalySize() + { + var dictionary = new Dictionary(); + + foreach (var anomalyEntry in Options.AnomalyEntries) + { + var entityPrototype = _prototype.Index(anomalyEntry.ProtoId); + var componentRegistry = entityPrototype.Components; + + if (!componentRegistry.TryGetComponent("Fixtures", out var component)) + continue; + + if (component is not FixturesComponent fixturesComponent) + continue; + + if (!fixturesComponent.Fixtures.TryGetValue("fix1", out var fixture)) + continue; + + // Here we get the radius of the anomaly, we need to subtract 0.5, + // which would not take into account the skeleton of the anomaly itself, + // in fact we turn the volumetric object into an abstract point. + var size = (int)Math.Max(Math.Round(fixture.Shape.Radius) - 1, 0); + + dictionary.Add(anomalyEntry.ProtoId, size); + } + + return dictionary.ToFrozenDictionary(); + } + + protected override async Task Process() + { + var result = new STAnomalyGenerationJobData(); + + await LoadTiles(); + await RemoveByBlockers(); + + for (var i = 0; i < Options.TotalCount; i++) + { + var anomaly = GetRandomAnomalyEntry(Options, _random); + if (anomaly is null) + continue; + + for (var j = 0; j < 100; j++) + { + await MakeOperation(); + + var (coords, tile) = _random.Pick(_tileCoordinatesSpawn); + var entity = await TrySpawn(anomaly.Value, coords); + + if (entity == EntityUid.Invalid) + continue; + + // Remove spawn coords from maps + _tileCoordinatesSpawn.Remove(coords); + _tileCoordinates.Remove(coords); + + // Anomaly don't spawn in anomalies + foreach (var takenCoord in GetAnomalyTiles(anomaly.Value, coords)) + { + if (!_tileCoordinatesSpawn.ContainsKey(takenCoord)) + continue; + + _tileCoordinatesSpawn.Remove(takenCoord); + } + + result.SpawnedAnomalies.Add(entity); + break; + } + } + + return result; + } + + private async Task RemoveByBlockers() + { + var entities = _entityManager.EntityQueryEnumerator(); + while (entities.MoveNext(out _, out var blocker, out var transform)) + { + if (transform.MapID != Options.MapId) + continue; + + var position = _transform.GetWorldPosition(transform); + var coordinates = new Vector2i((int) position.X, (int) position.Y); + var size = blocker.Size; + var box2 = new Box2i(coordinates.X - size, coordinates.Y - size, coordinates.X + size + 1, coordinates.Y + size); + + for (var x = box2.Left; x < box2.Right; x++) + { + for (var y = box2.Bottom; y < box2.Top; y++) + { + await MakeOperation(); + if (!_tileCoordinates.TryGetValue(new Vector2i(x, y), out _)) + continue; + + _tileCoordinates.Remove(new Vector2i(x, y)); + } + } + } + } + + private async Task LoadTiles() + { + var grids = _mapManager.GetAllGrids(Options.MapId); + foreach (var grid in grids) + { + foreach (var tileRef in _map.GetAllTiles(grid, grid)) + { + await MakeOperation(); + if (TileSolidAndNotBlocked(tileRef)) + { + _tileCoordinates.TryAdd(tileRef.GridIndices, tileRef); + _tileCoordinatesSpawn.TryAdd(tileRef.GridIndices, new STAnomalyGenerationTile(tileRef)); + } + + if (TileSolidAndNotBlocked(tileRef, uid => _tag.HasTag(uid, TagGenerationIntersectionSkip))) + { + _tileCoordinates.TryAdd(tileRef.GridIndices, tileRef); + } + } + } + } + + private static STAnomalyGeneratorAnomalyEntry? GetRandomAnomalyEntry(STAnomalyGenerationOptions options, IRobustRandom random) + { + var sumRation = 0f; + foreach (var entry in options.AnomalyEntries) + { + sumRation += entry.Weight; + } + + var roll = random.NextFloat(0, sumRation); + + sumRation = 0f; + foreach (var entry in options.AnomalyEntries) + { + sumRation += entry.Weight; + if (roll <= sumRation) + return entry; + } + + return options.AnomalyEntries.LastOrDefault(); + } +} diff --git a/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationJobData.cs b/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationJobData.cs new file mode 100644 index 00000000000..3a67ab0250f --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationJobData.cs @@ -0,0 +1,7 @@ +namespace Content.Server._Stalker.Anomaly.Generation.Jobs; + +public sealed class STAnomalyGenerationJobData +{ + [DataField] + public HashSet SpawnedAnomalies = new(); +} diff --git a/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationTile.cs b/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationTile.cs new file mode 100644 index 00000000000..a6646ba5771 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Generation/Jobs/STAnomalyGenerationTile.cs @@ -0,0 +1,18 @@ +using Content.Shared._Stalker.Anomaly.Prototypes; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; + +namespace Content.Server._Stalker.Anomaly.Generation.Jobs; + +public sealed class STAnomalyGenerationTile +{ + public readonly TileRef TileRef; + + public float Weight = 1f; + public ProtoId Nature = string.Empty; + + public STAnomalyGenerationTile(TileRef tileRef) + { + TileRef = tileRef; + } +} diff --git a/Content.Server/_Stalker/Anomaly/Generation/Systems/STAnomalyGeneratorSystem.Commands.cs b/Content.Server/_Stalker/Anomaly/Generation/Systems/STAnomalyGeneratorSystem.Commands.cs new file mode 100644 index 00000000000..2a1e301a592 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Generation/Systems/STAnomalyGeneratorSystem.Commands.cs @@ -0,0 +1,131 @@ +using Content.Shared._Stalker.Anomaly.Prototypes; +using Content.Server.Administration; +using Content.Shared.Administration; +using Robust.Shared.Console; +using Robust.Shared.Map; + +namespace Content.Server._Stalker.Anomaly.Generation.Systems; + +public sealed partial class STAnomalyGeneratorSystem +{ + [Dependency] private readonly IConsoleHost _consoleHost = default!; + + private void InitializeCommands() + { + _consoleHost.RegisterCommand("st_anomaly_generation_get_data_uid", + Loc.GetString("st-anomaly-generation-get-data-uid"), + "st_anomaly_generation_get_active", + StartGenerationGetDataUidCallback); + + _consoleHost.RegisterCommand("st_anomaly_generation_get_active", + Loc.GetString("st-anomaly-generation-get-active"), + "st_anomaly_generation_get_active", + StartGenerationGetActiveCallback); + + _consoleHost.RegisterCommand("st_anomaly_generation_start", + Loc.GetString("st-anomaly-generation-start"), + "st_anomaly_generation_start ", + StartGenerationCallback, + StartGenerationCallbackHelper); + + _consoleHost.RegisterCommand("st_anomaly_generation_clear", + Loc.GetString("st-anomaly-generation-clear"), + "st_anomaly_generation_clear ", + StartGenerationClearCallback, + StartGenerationClearCallbackHelper); + } + + [AdminCommand(AdminFlags.MassBan)] + private void StartGenerationGetDataUidCallback(IConsoleShell shell, string argstr, string[] args) + { + shell.WriteLine($"Data entity: {ToPrettyString(Data.Owner)}"); + } + + [AdminCommand(AdminFlags.MassBan)] + private void StartGenerationGetActiveCallback(IConsoleShell shell, string argstr, string[] args) + { + var result = string.Empty; + foreach (var (job, _) in _jobs) + { + result += $"Job {job.AsTask.Id} for {job.Options.MapId}\r\n"; + } + + shell.WriteLine(result); + } + + [AdminCommand(AdminFlags.MassBan)] + private void StartGenerationCallback(IConsoleShell shell, string argstr, string[] args) + { + if (args.Length != 2) + { + shell.WriteError(Loc.GetString("shell-argument-count-must-be", ("value", 2))); + return; + } + + if (!int.TryParse(args[0], out var mapIndex)) + { + shell.WriteError(Loc.GetString("shell-invalid-int", ("value", args[0]))); + return; + } + + var mapId = new MapId(mapIndex); + if (!_map.MapExists(mapId)) + { + shell.WriteError($"Map {mapId} doesn't exist!"); + return; + } + + if (!_prototype.TryIndex(args[1], out var proto)) + { + shell.WriteError(Loc.GetString("shell-invalid-prototype", ("value", args[1]))); + return; + } + + var task = StartGeneration(mapId, proto.Options); + shell.WriteLine($"Create generation {task.Id}"); + } + + private CompletionResult StartGenerationCallbackHelper(IConsoleShell shell, string[] args) + { + return args.Length switch + { + 1 => CompletionResult.FromOptions(CompletionHelper.MapIds()), + 2 => CompletionResult.FromOptions(CompletionHelper.PrototypeIDs()), + _ => CompletionResult.Empty + }; + } + + [AdminCommand(AdminFlags.MassBan)] + private void StartGenerationClearCallback(IConsoleShell shell, string argstr, string[] args) + { + if (args.Length != 1) + { + shell.WriteError(Loc.GetString("shell-argument-count-must-be", ("value", 1))); + return; + } + + if (!int.TryParse(args[0], out var mapIndex)) + { + shell.WriteError(Loc.GetString("shell-invalid-int", ("value", args[0]))); + return; + } + + var mapId = new MapId(mapIndex); + if (!_map.MapExists(mapId)) + { + shell.WriteError($"Map {mapId} doesn't exist!"); + return; + } + + ClearGeneration(mapId); + } + + private CompletionResult StartGenerationClearCallbackHelper(IConsoleShell shell, string[] args) + { + return args.Length switch + { + 1 => CompletionResult.FromOptions(CompletionHelper.MapIds()), + _ => CompletionResult.Empty + }; + } +} diff --git a/Content.Server/_Stalker/Anomaly/Generation/Systems/STAnomalyGeneratorSystem.cs b/Content.Server/_Stalker/Anomaly/Generation/Systems/STAnomalyGeneratorSystem.cs new file mode 100644 index 00000000000..e82410e9510 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Generation/Systems/STAnomalyGeneratorSystem.cs @@ -0,0 +1,138 @@ +using Content.Server._Stalker.Anomaly.Generation.Components; +using Content.Server._Stalker.Anomaly.Generation.Jobs; +using Content.Server._NF.RoundNotifications.Events; +using Content.Shared._Stalker.Anomaly.Data; +using Content.Shared.GameTicking; +using Robust.Shared.CPUJob.JobQueues.Queues; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Prototypes; +using System.Threading; +using System.Threading.Tasks; + +namespace Content.Server._Stalker.Anomaly.Generation.Systems; + +public sealed partial class STAnomalyGeneratorSystem : EntitySystem +{ + [Dependency] private readonly IMapManager _map = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + + private const double JobTime = 0.005; + + private readonly JobQueue _jobQueue = new(JobTime); + private readonly Dictionary _jobs = new(); + + private Entity Data + { + get + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var component)) + { + return (uid, component); + } + + var newUid = Spawn(null, MapCoordinates.Nullspace); + var newComponent = EnsureComp(newUid); + return (newUid, newComponent); + } + } + + public override void Initialize() + { + base.Initialize(); + + InitializeCommands(); + + SubscribeLocalEvent(OnRoundCleanup); + SubscribeLocalEvent(OnRoundStart); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + _jobQueue.Process(); + } + + public override void Shutdown() + { + base.Shutdown(); + CleanJobs(); + } + + private void OnRoundCleanup(RoundRestartCleanupEvent ev) + { + CleanJobs(); + } + + private void OnRoundStart(RoundStartedEvent ev) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var entityUid, out var mapComponent, out var targetComponent)) + { + if (!_prototype.TryIndex(targetComponent.OptionsId, out var options)) + { + Log.Error($"Can't start generation on {ToPrettyString(entityUid)}!"); +#if DEBUG + throw new KeyNotFoundException(); +#endif + continue; + } + + StartGeneration(mapComponent.MapId, options.Options); + } + } + + private void CleanJobs() + { + foreach (var token in _jobs.Values) + { + token.Cancel(); + } + + _jobs.Clear(); + } + + private async Task StartGeneration(MapId mapId, STAnomalyGenerationOptions options) + { + var cancelToken = new CancellationTokenSource(); + var job = new STAnomalyGenerationJob(options with { MapId = mapId }, JobTime, cancelToken.Token); + + _jobs.Add(job, cancelToken); + _jobQueue.EnqueueJob(job); + + Log.Info($"Generation {job.AsTask.Id} for {mapId} started"); + + await job.AsTask; + + if (job.Exception is not null) + throw job.Exception; + + var count = job.Result!.SpawnedAnomalies.Count; + var total = options.TotalCount; + var percent = (float)Math.Round(count / (float)total * 100f, 2); + + Log.Info($"Generation {job.AsTask.Id} end, count: {count}\\{total} ({percent}%)"); + + Data.Comp.MapGeneratedAnomalies[mapId] = job.Result!.SpawnedAnomalies; + return job.Result!; + } + + private void ClearGeneration(MapId mapId) + { + if (!Data.Comp.MapGeneratedAnomalies.TryGetValue(mapId, out var anomalies)) + return; + + Log.Info($"Clearing for {mapId} started"); + + var count = 0; + foreach (var anomaly in anomalies) + { + QueueDel(anomaly); + count++; + } + + Log.Info($"Clearing for {mapId} ended, count: {count}"); + } +} diff --git a/Content.Server/_Stalker/Anomaly/STAnomalyComponent.cs b/Content.Server/_Stalker/Anomaly/STAnomalyComponent.cs new file mode 100644 index 00000000000..e51f1a3b7dd --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/STAnomalyComponent.cs @@ -0,0 +1,21 @@ +namespace Content.Server._Stalker.Anomaly; + +[RegisterComponent] +public sealed partial class STAnomalyComponent : Component +{ + [DataField, ViewVariables] + public string State; + + [DataField, ViewVariables] + public Dictionary> States = new(); +} + +[Serializable, DataDefinition] +public partial struct STAnomalyStateTransition +{ + [DataField, ViewVariables] + public string State; + + [DataField, ViewVariables] + public string Group; +} diff --git a/Content.Server/_Stalker/Anomaly/STAnomalySystem.cs b/Content.Server/_Stalker/Anomaly/STAnomalySystem.cs new file mode 100644 index 00000000000..dc5d3dea81d --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/STAnomalySystem.cs @@ -0,0 +1,43 @@ +using Content.Shared._Stalker.Anomaly.Triggers.Events; + +namespace Content.Server._Stalker.Anomaly; + +public sealed class STAnomalySystem : EntitySystem +{ + private EntityQuery _anomalyQuery; + + public override void Initialize() + { + base.Initialize(); + + _anomalyQuery = GetEntityQuery(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity anomaly, ref STAnomalyTriggerEvent args) + { + if (!args.StateChanger) + return; + + var transitions = anomaly.Comp.States[anomaly.Comp.State]; + + foreach (var transition in transitions) + { + if (!args.Groups.Contains(transition.Group)) + continue; + + SetState(anomaly, transition.State); + break; + } + } + + private void SetState(Entity anomaly, string state) + { + var previousState = anomaly.Comp.State; + anomaly.Comp.State = state; + + var ev = new STAnomalyChangedStateEvent(previousState, state); + RaiseLocalEvent(anomaly, ev); + } +} diff --git a/Content.Server/_Stalker/Anomaly/TargetGroups/STAnomalyAdditionalGroupsComponent.cs b/Content.Server/_Stalker/Anomaly/TargetGroups/STAnomalyAdditionalGroupsComponent.cs new file mode 100644 index 00000000000..6c34293c007 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/TargetGroups/STAnomalyAdditionalGroupsComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Server._Stalker.Anomaly.TargetGroups; + +[RegisterComponent] +public sealed partial class STAnomalyAdditionalGroupsComponent : Component +{ + [DataField] + public HashSet Groups = new(); +} diff --git a/Content.Server/_Stalker/Anomaly/TargetGroups/STAnomalyAdditionalGroupsSystem.cs b/Content.Server/_Stalker/Anomaly/TargetGroups/STAnomalyAdditionalGroupsSystem.cs new file mode 100644 index 00000000000..8017c7f53c7 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/TargetGroups/STAnomalyAdditionalGroupsSystem.cs @@ -0,0 +1,18 @@ +using Content.Shared._Stalker.Anomaly.Triggers.Events; + +namespace Content.Server._Stalker.Anomaly.TargetGroups; + +public sealed class STAnomalyAdditionalGroupsSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(GetAdditionalGroups); + } + + private void GetAdditionalGroups(Entity additionalGroups, ref STAnomalyTriggerStartCollideGetAdditionalGroupsEvent args) + { + args.Add(additionalGroups.Comp.Groups); + } +} diff --git a/Content.Server/_Stalker/Anomaly/Triggers/STAnomalyTriggerSystem.cs b/Content.Server/_Stalker/Anomaly/Triggers/STAnomalyTriggerSystem.cs new file mode 100644 index 00000000000..3a0eaa113ea --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Triggers/STAnomalyTriggerSystem.cs @@ -0,0 +1,30 @@ +using Content.Shared._Stalker.Anomaly.Triggers.Events; + +namespace Content.Server._Stalker.Anomaly.Triggers; + +public sealed class STAnomalyTriggerSystem : EntitySystem +{ + public void Trigger(EntityUid uid, string group, bool stateChanger = true) + { + Trigger(uid, new HashSet { group }, stateChanger); + } + + public void Trigger(EntityUid uid, HashSet groups, bool stateChanger = true) + { + if (!TryComp(uid, out var comp)) + return; + + Trigger((uid, comp), groups, stateChanger); + } + + public void Trigger(Entity anomaly, string group, bool stateChanger = true) + { + Trigger(anomaly, new HashSet { group }, stateChanger); + } + + public void Trigger(Entity anomaly, HashSet groups, bool stateChanger = true) + { + var ev = new STAnomalyTriggerEvent(groups, stateChanger); + RaiseLocalEvent(anomaly, ev); + } +} diff --git a/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideBootsComponent.cs b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideBootsComponent.cs new file mode 100644 index 00000000000..a001c65c335 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideBootsComponent.cs @@ -0,0 +1,27 @@ +namespace Content.Server._Stalker.Anomaly.Triggers.StartCollide; + +/// +/// Adds trigger groups based on whether the colliding entity has boots equipped. +/// Used by Glass Shards anomaly for boot-dependent damage. +/// +[RegisterComponent] +public sealed partial class STAnomalyTriggerStartCollideBootsComponent : Component +{ + /// + /// Group added when entity has no boots AND is sprinting (double damage). + /// + [DataField] + public string NoBootsSprintingGroup = "NoBootsSprinting"; + + /// + /// Group added when entity has no boots but is NOT sprinting (base damage). + /// + [DataField] + public string NoBootsWalkingGroup = "NoBootsWalking"; + + /// + /// Inventory slot to check for boots. + /// + [DataField] + public string SlotName = "shoes"; +} diff --git a/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideBootsSystem.cs b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideBootsSystem.cs new file mode 100644 index 00000000000..dcba8f7056a --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideBootsSystem.cs @@ -0,0 +1,40 @@ +using Content.Shared._Stalker.Anomaly.Triggers.Events; +using Content.Shared.Inventory; +using Content.Shared.Movement.Components; + +namespace Content.Server._Stalker.Anomaly.Triggers.StartCollide; + +/// +/// Adds trigger groups based on whether the colliding entity has boots equipped. +/// +public sealed class STAnomalyTriggerStartCollideBootsSystem : EntitySystem +{ + [Dependency] private readonly InventorySystem _inventory = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetAdditionalGroups); + } + + private void OnGetAdditionalGroups( + Entity trigger, + ref STAnomalyTriggerStartCollideGetAdditionalGroupsEvent args) + { + // Check if target has boots equipped + var hasBoots = _inventory.TryGetSlotEntity(args.Target, trigger.Comp.SlotName, out _); + + if (hasBoots) + return; // Has boots - let sprint system handle it separately + + // No boots - check if sprinting for double damage + var isSprinting = TryComp(args.Target, out var mover) && mover.Sprinting; + + if (isSprinting) + args.Add(trigger.Comp.NoBootsSprintingGroup); + else + args.Add(trigger.Comp.NoBootsWalkingGroup); + } +} diff --git a/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideComponent.cs b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideComponent.cs new file mode 100644 index 00000000000..e9f9e633ff1 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideComponent.cs @@ -0,0 +1,13 @@ +using Content.Shared.Whitelist; + +namespace Content.Server._Stalker.Anomaly.Triggers.StartCollide; + +[RegisterComponent] +public sealed partial class STAnomalyTriggerStartCollideComponent : Component +{ + [DataField] + public EntityWhitelist? Blacklist; + + [DataField] + public string MainTriggerGroup = "StartCollide"; +} diff --git a/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideGroupsComponent.cs b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideGroupsComponent.cs new file mode 100644 index 00000000000..7abdcdf4169 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideGroupsComponent.cs @@ -0,0 +1,10 @@ +using Content.Shared.Whitelist; + +namespace Content.Server._Stalker.Anomaly.Triggers.StartCollide; + +[RegisterComponent] +public sealed partial class STAnomalyTriggerStartCollideGroupsComponent : Component +{ + [DataField] + public Dictionary Groups = new(); +} diff --git a/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideGroupsSystem.cs b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideGroupsSystem.cs new file mode 100644 index 00000000000..47c76de683c --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideGroupsSystem.cs @@ -0,0 +1,27 @@ +using Content.Shared._Stalker.Anomaly.Triggers.Events; +using Content.Shared.Whitelist; + +namespace Content.Server._Stalker.Anomaly.Triggers.StartCollide; + +public sealed class STAnomalyTriggerStartCollideGroupsSystem : EntitySystem +{ + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(GetAdditionalGroups); + } + + private void GetAdditionalGroups(Entity trigger, ref STAnomalyTriggerStartCollideGetAdditionalGroupsEvent args) + { + foreach (var (whitelist, group) in trigger.Comp.Groups) + { + if (!_whitelist.IsBlacklistPass(whitelist, args.Target)) + continue; + + args.Add(group); + } + } +} diff --git a/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideSprintingComponent.cs b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideSprintingComponent.cs new file mode 100644 index 00000000000..50bb01cc6ce --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideSprintingComponent.cs @@ -0,0 +1,15 @@ +namespace Content.Server._Stalker.Anomaly.Triggers.StartCollide; + +/// +/// Adds a trigger group when the colliding entity is sprinting. +/// Used by Glass Shards anomaly to increase damage for running entities. +/// +[RegisterComponent] +public sealed partial class STAnomalyTriggerStartCollideSprintingComponent : Component +{ + /// + /// The trigger group to add when entity is sprinting. + /// + [DataField] + public string SprintingTriggerGroup = "Sprinting"; +} diff --git a/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideSprintingSystem.cs b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideSprintingSystem.cs new file mode 100644 index 00000000000..4e118b4c044 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideSprintingSystem.cs @@ -0,0 +1,31 @@ +using Content.Shared._Stalker.Anomaly.Triggers.Events; +using Content.Shared.Movement.Components; + +namespace Content.Server._Stalker.Anomaly.Triggers.StartCollide; + +/// +/// Adds a trigger group when the colliding entity is sprinting. +/// +public sealed class STAnomalyTriggerStartCollideSprintingSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetAdditionalGroups); + } + + private void OnGetAdditionalGroups( + Entity trigger, + ref STAnomalyTriggerStartCollideGetAdditionalGroupsEvent args) + { + // args.Target is the entity that collided with the anomaly + if (!TryComp(args.Target, out var mover)) + return; + + // In Stalker, MoveButtons.Walk flag means SPRINTING (inverted from vanilla SS14) + if (mover.Sprinting) + args.Add(trigger.Comp.SprintingTriggerGroup); + } +} diff --git a/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideSystem.cs b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideSystem.cs new file mode 100644 index 00000000000..16cd7d6aa6c --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideSystem.cs @@ -0,0 +1,35 @@ +using Content.Shared._Stalker.Anomaly.Triggers.Events; +using Content.Shared.Whitelist; +using Robust.Shared.Physics.Events; + +namespace Content.Server._Stalker.Anomaly.Triggers.StartCollide; + +public sealed class STAnomalyTriggerStartCollideSystem : EntitySystem +{ + [Dependency] private readonly STAnomalyTriggerSystem _anomalyTrigger = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartCollide); + } + + private void OnStartCollide(Entity trigger, ref StartCollideEvent args) + { + var targetUid = args.OtherEntity; + if (trigger.Comp.Blacklist is not null && _whitelistSystem.IsBlacklistPass(trigger.Comp.Blacklist, targetUid)) + return; + + var groups = new HashSet { trigger.Comp.MainTriggerGroup }; + + var ev = new STAnomalyTriggerStartCollideGetAdditionalGroupsEvent(targetUid); + RaiseLocalEvent(targetUid, ev); + RaiseLocalEvent(trigger, ev); + + groups.UnionWith(ev.Groups); + + _anomalyTrigger.Trigger(trigger, groups); + } +} diff --git a/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideWeightComponent.cs b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideWeightComponent.cs new file mode 100644 index 00000000000..cf216b0d037 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideWeightComponent.cs @@ -0,0 +1,12 @@ +namespace Content.Server._Stalker.Anomaly.Triggers.StartCollide; + +[RegisterComponent] +public sealed partial class STAnomalyTriggerStartCollideWeightComponent : Component +{ + [DataField] + public Dictionary WeightTriggerGroup = new() + { + { 10, "WeightSmall" }, + { 20, "WeightNormal" }, + }; +} diff --git a/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideWeightSystem.cs b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideWeightSystem.cs new file mode 100644 index 00000000000..42f88bd90e7 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Triggers/StartCollide/STAnomalyTriggerStartCollideWeightSystem.cs @@ -0,0 +1,34 @@ +using Content.Shared._Stalker.Anomaly.Triggers.Events; +using Content.Shared._Stalker.Weight; + +namespace Content.Server._Stalker.Anomaly.Triggers.StartCollide; + +public sealed class STAnomalyTriggerStartCollideWeightSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(GetAdditionalGroups); + } + + private void GetAdditionalGroups(Entity trigger, ref STAnomalyTriggerStartCollideGetAdditionalGroupsEvent args) + { + if (!TryComp(args.Target, out var weightComponent)) + return; + + var weightGroup = string.Empty; + var maxWeight = 0f; + + foreach (var (weight, group) in trigger.Comp.WeightTriggerGroup) + { + if (weightComponent.Total < weight || maxWeight > weight) + continue; + + weightGroup = group; + maxWeight = weight; + } + + args.Add(weightGroup); + } +} diff --git a/Content.Server/_Stalker/Anomaly/Triggers/StateTransition/STAnomalyTriggerGroupsStateTransitionComponent.cs b/Content.Server/_Stalker/Anomaly/Triggers/StateTransition/STAnomalyTriggerGroupsStateTransitionComponent.cs new file mode 100644 index 00000000000..7c2590c85d8 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Triggers/StateTransition/STAnomalyTriggerGroupsStateTransitionComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Server._Stalker.Anomaly.Triggers.StateTransition; + +[RegisterComponent] +public sealed partial class STAnomalyTriggerGroupsStateTransitionComponent : Component +{ + [DataField] + public string Prefix = "State"; +} diff --git a/Content.Server/_Stalker/Anomaly/Triggers/StateTransition/STAnomalyTriggerGroupsStateTransitionSystem.cs b/Content.Server/_Stalker/Anomaly/Triggers/StateTransition/STAnomalyTriggerGroupsStateTransitionSystem.cs new file mode 100644 index 00000000000..6b2deaa654e --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Triggers/StateTransition/STAnomalyTriggerGroupsStateTransitionSystem.cs @@ -0,0 +1,21 @@ +using Content.Shared._Stalker.Anomaly.Triggers.Events; + +namespace Content.Server._Stalker.Anomaly.Triggers.StateTransition; + +public sealed class STAnomalyTriggerGroupsStateTransitionSystem : EntitySystem +{ + [Dependency] private readonly STAnomalyTriggerSystem _trigger = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnChangedState); + } + + private void OnChangedState(Entity trigger, ref STAnomalyChangedStateEvent args) + { + var group = trigger.Comp.Prefix == string.Empty ? args.State : $"{trigger.Comp.Prefix}{args.State}"; + _trigger.Trigger(trigger, group, false); + } +} diff --git a/Content.Server/_Stalker/Anomaly/Triggers/TimeDelay/STAnomalyTriggerTimeDelayComponent.cs b/Content.Server/_Stalker/Anomaly/Triggers/TimeDelay/STAnomalyTriggerTimeDelayComponent.cs new file mode 100644 index 00000000000..22b7f1c3c6f --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Triggers/TimeDelay/STAnomalyTriggerTimeDelayComponent.cs @@ -0,0 +1,18 @@ +namespace Content.Server._Stalker.Anomaly.Triggers.TimeDelay; + +[RegisterComponent] +public sealed partial class STAnomalyTriggerTimeDelayComponent : Component +{ + [DataField] + public Dictionary Options; +} + +[Serializable, DataDefinition] +public partial struct STAnomalyTriggerTimeDelayOptions +{ + [DataField, ViewVariables] + public string Group; + + [DataField, ViewVariables] + public TimeSpan Delay = TimeSpan.FromSeconds(5f); +} diff --git a/Content.Server/_Stalker/Anomaly/Triggers/TimeDelay/STAnomalyTriggerTimeDelaySystem.cs b/Content.Server/_Stalker/Anomaly/Triggers/TimeDelay/STAnomalyTriggerTimeDelaySystem.cs new file mode 100644 index 00000000000..e24ab594164 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Triggers/TimeDelay/STAnomalyTriggerTimeDelaySystem.cs @@ -0,0 +1,55 @@ +using Content.Shared._Stalker.Anomaly.Triggers.Events; +using Robust.Shared.Timing; + +namespace Content.Server._Stalker.Anomaly.Triggers.TimeDelay; + +public sealed class STAnomalyTriggerTimeDelaySystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly STAnomalyTriggerSystem _anomalyTrigger = default!; + + private readonly List _activationQueue = new(); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnChangesState); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var removeFromQuery = new List(); + foreach (var element in _activationQueue) + { + if (_timing.CurTime < element.Time) + continue; + + removeFromQuery.Add(element); + _anomalyTrigger.Trigger(element.Anomaly, element.Options.Group); + } + + foreach (var element in removeFromQuery) + { + _activationQueue.Remove(element); + } + } + + private void OnChangesState(Entity trigger, ref STAnomalyChangedStateEvent args) + { + if (!trigger.Comp.Options.TryGetValue(args.State, out var options)) + return; + + var time = _timing.CurTime + options.Delay; + _activationQueue.Add(new ActivationQueueElement(trigger, time, options)); + } + + private struct ActivationQueueElement(EntityUid anomaly, TimeSpan time, STAnomalyTriggerTimeDelayOptions options) + { + public readonly EntityUid Anomaly = anomaly; + public readonly TimeSpan Time = time; + public readonly STAnomalyTriggerTimeDelayOptions Options = options; + } +} diff --git a/Content.Server/_Stalker/Anomaly/Visuals/STAnomalyVisualStateComponent.cs b/Content.Server/_Stalker/Anomaly/Visuals/STAnomalyVisualStateComponent.cs new file mode 100644 index 00000000000..911eb270f1a --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Visuals/STAnomalyVisualStateComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Server._Stalker.Anomaly.Visuals; + +/// +/// Marker component that enables visual state updates for STAnomaly entities. +/// When present, sprite changes based on STAnomaly state transitions. +/// +[RegisterComponent] +public sealed partial class STAnomalyVisualStateComponent : Component +{ +} diff --git a/Content.Server/_Stalker/Anomaly/Visuals/STAnomalyVisualStateSystem.cs b/Content.Server/_Stalker/Anomaly/Visuals/STAnomalyVisualStateSystem.cs new file mode 100644 index 00000000000..d8a0ed13963 --- /dev/null +++ b/Content.Server/_Stalker/Anomaly/Visuals/STAnomalyVisualStateSystem.cs @@ -0,0 +1,25 @@ +using Content.Shared._Stalker.Anomaly; +using Content.Shared._Stalker.Anomaly.Triggers.Events; +using Robust.Server.GameObjects; + +namespace Content.Server._Stalker.Anomaly.Visuals; + +/// +/// Updates appearance data when STAnomaly state changes. +/// Allows GenericVisualizer to change sprites based on anomaly state. +/// +public sealed class STAnomalyVisualStateSystem : EntitySystem +{ + [Dependency] private readonly AppearanceSystem _appearance = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnStateChanged); + } + + private void OnStateChanged(Entity entity, ref STAnomalyChangedStateEvent args) + { + _appearance.SetData(entity, STAnomalyVisuals.Layer, args.State); + } +} diff --git a/Content.Server/_Stalker/Map/STMapKeyComponent.cs b/Content.Server/_Stalker/Map/STMapKeyComponent.cs new file mode 100644 index 00000000000..80c6992a546 --- /dev/null +++ b/Content.Server/_Stalker/Map/STMapKeyComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Server._Stalker.Map; + +[RegisterComponent] +public sealed partial class STMapKeyComponent : Component +{ + [DataField] + public string Value; +} diff --git a/Content.Server/_Stalker/Map/STMapKeySystem.cs b/Content.Server/_Stalker/Map/STMapKeySystem.cs new file mode 100644 index 00000000000..bdf136c8aa4 --- /dev/null +++ b/Content.Server/_Stalker/Map/STMapKeySystem.cs @@ -0,0 +1,24 @@ +using System.Diagnostics.CodeAnalysis; +using Robust.Shared.Map.Components; + +namespace Content.Server._Stalker.Map; + +public sealed class STMapKeySystem : EntitySystem +{ + public bool TryGet(string key, [NotNullWhen(true)] out Entity? entity) + { + entity = null; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var mapComponent, out var mapKeyComponent)) + { + if (mapKeyComponent.Value != key) + continue; + + entity = (uid, mapComponent); + return true; + } + + return false; + } +} diff --git a/Content.Server/_Stalker/Utils/STUtilsMap.cs b/Content.Server/_Stalker/Utils/STUtilsMap.cs new file mode 100644 index 00000000000..6d7323f6fc3 --- /dev/null +++ b/Content.Server/_Stalker/Utils/STUtilsMap.cs @@ -0,0 +1,25 @@ +using Robust.Shared.Map.Components; + +namespace Content.Server._Stalker.Utils; + +public static class STUtilsMap +{ + public static bool InWorld(EntityUid entityUid, IEntityManager? entityManager = null) + { + return InWorld((entityUid, null), entityManager); + } + + public static bool InWorld(Entity entity, IEntityManager? entityManager = null) + { + entityManager ??= IoCManager.Resolve(); + + if (entity.Comp is null && !entityManager.TryGetComponent(entity, out entity.Comp)) + return false; + + var parent = entity.Comp.ParentUid; + if (parent == EntityUid.Invalid) + return false; + + return entityManager.HasComponent(parent) || entityManager.HasComponent(parent); + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Devices/ZoneAnomalyDestructorComponent.cs b/Content.Server/_Stalker/ZoneAnomaly/Devices/ZoneAnomalyDestructorComponent.cs new file mode 100644 index 00000000000..246bc8a704e --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Devices/ZoneAnomalyDestructorComponent.cs @@ -0,0 +1,18 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + + +namespace Content.Server._Stalker.ZoneAnomaly.Devices + + +{ + [RegisterComponent] + public sealed partial class ZoneAnomalyDestructorComponent : Component + { + [DataField] + public float Delay = 20f; + + [DataField] + public string TargetPrototype = "ZoneAnomalyBase"; // change to match your actual anomaly prototype ID + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Devices/ZoneAnomalyDestructorSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Devices/ZoneAnomalyDestructorSystem.cs new file mode 100644 index 00000000000..ef2c001f683 --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Devices/ZoneAnomalyDestructorSystem.cs @@ -0,0 +1,116 @@ +using Content.Server.DoAfter; +using Content.Server.Popups; +using Content.Server._Stalker.ZoneAnomaly.Devices; +using Content.Shared.DoAfter; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Content.Shared._Stalker.ZoneAnomaly; +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Server._Stalker.ZoneAnomaly.Devices; + +public sealed class ZoneAnomalyDestructorSystem : EntitySystem +{ + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly IPrototypeManager _protoManager = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent>(AddVerb); + SubscribeLocalEvent(OnAfterInteract); + SubscribeLocalEvent(OnDoAfter); + } + + public void AddVerb(EntityUid uid, ZoneAnomalyDestructorComponent component, GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + var argsDoAfter = new DoAfterArgs(EntityManager, args.User, component.Delay, new InteractionDoAfterEvent(), uid, uid) + { + NeedHand = true, + BreakOnMove = true, + CancelDuplicate = true + }; + + args.Verbs.Add(new InteractionVerb + { + Text = "Activate", + Act = () => _doAfter.TryStartDoAfter(argsDoAfter), + }); + } + + public void OnAfterInteract(EntityUid uid, ZoneAnomalyDestructorComponent component, AfterInteractEvent args) + { + if (args.Handled || !args.CanReach || args.Target is not { Valid: true }) + return; + + var argsDoAfter = new DoAfterArgs(EntityManager, args.User, component.Delay, new InteractionDoAfterEvent(), uid, uid) + { + NeedHand = true, + BreakOnMove = true, + CancelDuplicate = true + }; + + _doAfter.TryStartDoAfter(argsDoAfter); + args.Handled = true; + } + + public void OnDoAfter(EntityUid uid, ZoneAnomalyDestructorComponent component, InteractionDoAfterEvent args) + { + if (args.Cancelled || args.Handled || args.Args.Target == null) + return; + + var user = args.Args.User; + if (!Exists(user)) + return; + + //_popup.PopupEntity("It works", uid); + + //starting delete logic + var coords = _transform.GetMapCoordinates(Transform(uid)); + var deletedAny = false; + + // Destroy ALL anomalies in range � no filtering by type + foreach (var ent in _lookup.GetEntitiesInRange(coords, 1.3f)) + { + if (!HasComp(ent)) + continue; + + if (!TryComp(ent, out var meta) || meta.EntityPrototype == null) + continue; + + var proto = meta.EntityPrototype; + + if (proto.Parents == null || Array.IndexOf(proto.Parents, component.TargetPrototype) == -1) + continue; + + QueueDel(ent); + deletedAny = true; + } + + if(deletedAny) + { + _popup.PopupEntity("Anomaly neutralized.", uid); + // Might hate me for this but uh I don't want scientists going around destroying the whole map + QueueDel(uid); + } + else + _popup.PopupEntity("No anomalies nearby.", uid); + + //end of delete logic + + args.Handled = true; + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Devices/ZoneAnomalyDetectorSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Devices/ZoneAnomalyDetectorSystem.cs new file mode 100644 index 00000000000..5da8116a89b --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Devices/ZoneAnomalyDetectorSystem.cs @@ -0,0 +1,182 @@ +using Content.Server.Popups; +using Content.Shared._Stalker.ZoneAnomaly; +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared.Interaction.Events; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Server.Audio; +using Robust.Server.GameObjects; +using Robust.Shared.Timing; +using Robust.Shared.Audio; // AudioParams +using Robust.Shared.Player; // Filter + +namespace Content.Server._Stalker.ZoneAnomaly.Devices; + +public sealed class ZoneAnomalyDetectorSystem : SharedZoneAnomalyDetectorSystem +{ + [Dependency] private readonly EntityLookupSystem _entityLookup = default!; + [Dependency] private readonly AppearanceSystem _appearance = default!; + [Dependency] private readonly ZoneAnomalySystem _zoneAnomaly = default!; + [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly PopupSystem _popup = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent>(OnGetVerbs); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var detector)) + { + if (!detector.Enabled) + continue; + + if (_timing.CurTime < detector.NextBeepTime) + continue; + + UpdateBeep((uid, detector)); + } + } + + + private void OnUseInHand(Entity detector, ref UseInHandEvent args) + { + if (args.Handled) + return; + + args.Handled = TryToggle(detector, args.User); + } + + private bool TryToggle(Entity detector, EntityUid? user = null) + { + return detector.Comp.Enabled + ? TryDisable(detector) + : TryEnable(detector); + } + + private bool TryEnable(Entity detector) + { + if (detector.Comp.Enabled) + return false; + + detector.Comp.Enabled = true; + detector.Comp.NextBeepTime = _timing.CurTime; + + _appearance.SetData(detector, ZoneAnomalyDetectorVisuals.Enabled, true); + + UpdateBeep(detector, false); + return true; + } + + private bool TryDisable(Entity detector) + { + if (!detector.Comp.Enabled) + return false; + + detector.Comp.Enabled = false; + + _appearance.SetData(detector, ZoneAnomalyDetectorVisuals.Enabled, false); + + UpdateBeep(detector); + return true; + } + + private void UpdateBeep(Entity detector, bool playBeep = true) + { + if (!detector.Comp.Enabled) + { + detector.Comp.NextBeepTime += detector.Comp.MaxBeepInterval; + return; + } + + var xformQuery = GetEntityQuery(); + var xform = xformQuery.GetComponent(detector); + + float? closestDistance = null; + foreach (var ent in _entityLookup.GetEntitiesInRange(_transform.GetMapCoordinates(xform), detector.Comp.Distance)) + { + if (!ent.Comp.Detected || ent.Comp.DetectedLevel > detector.Comp.Level) + continue; + + var dist = (_transform.GetWorldPosition(xform, xformQuery) - _transform.GetWorldPosition(ent, xformQuery)).Length(); + if (dist >= (closestDistance ?? float.MaxValue)) + continue; + + closestDistance = dist; + } + + if (closestDistance is not { } distance) + return; + + if (playBeep) + { + const float hearRange = 10f; // hardcoded limit + var mapCoords = _transform.GetMapCoordinates(xform); + _audio.PlayEntity( + detector.Comp.BeepSound, + Filter.Empty().AddInRange(mapCoords, hearRange), + detector, + true, + AudioParams.Default + .WithMaxDistance(hearRange) + .WithReferenceDistance(1f) + .WithRolloffFactor(1f)); + } + + var scalingFactor = distance / detector.Comp.Distance; + var interval = (detector.Comp.MaxBeepInterval - detector.Comp.MinBeepInterval) * scalingFactor + detector.Comp.MinBeepInterval; + + detector.Comp.NextBeepTime += interval; + if (detector.Comp.NextBeepTime < _timing.CurTime) + detector.Comp.NextBeepTime = _timing.CurTime + interval; + } + + private void ActivateAnomalies(Entity activator, EntityUid user) + { + if (activator.Comp.NexActivationTime > _timing.CurTime) + { + _popup.PopupEntity("������� ��� �� �����!", user, user, PopupType.Medium); + return; + } + + var count = 0; + foreach (var ent in _entityLookup.GetEntitiesInRange(_transform.GetMapCoordinates(Transform(activator)), activator.Comp.Distance)) + { + if (ent.Comp.DetectedLevel > activator.Comp.Level) + continue; + + _zoneAnomaly.TryActivate((ent, ent.Comp)); + count++; + + if (count > activator.Comp.MaxCount && activator.Comp.MaxCount != 0) + break; + } + + _popup.PopupEntity("������ ����������� ���������", user, user, PopupType.Medium); + activator.Comp.NexActivationTime = _timing.CurTime + activator.Comp.ActivationDelay; + } + + private void OnGetVerbs(Entity activator, ref GetVerbsEvent args) + { + var user = args.User; + AlternativeVerb verb = new() + { + Text = "������� B", + Act = () => + { + ActivateAnomalies(activator, user); + }, + Message = "������������ ��������", + }; + args.Verbs.Add(verb); + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectBlastSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectBlastSystem.cs new file mode 100644 index 00000000000..a060f796ff9 --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectBlastSystem.cs @@ -0,0 +1,103 @@ +using System.Numerics; +using Content.Shared._Stalker.ZoneAnomaly; +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Content.Shared.Whitelist; +using Robust.Server.GameObjects; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Random; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +/// +/// Handles the blast effect that throws entities outward from anomaly center on charging. +/// +public sealed class ZoneAnomalyEffectBlastSystem : EntitySystem +{ + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly PhysicsSystem _physics = default!; + [Dependency] private readonly TransformSystem _transform = default!; + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var blast, out var anomaly, out var xform)) + { + var currentState = (int)anomaly.State; + + // Detect state transition - reset when entering charging + if (blast.LastState != currentState) + { + blast.LastState = currentState; + if (anomaly.State == ZoneAnomalyState.Charging) + { + blast.Triggered = false; + blast.AccumulatedTime = 0f; + } + } + + // Only process during charging state + if (anomaly.State != ZoneAnomalyState.Charging) + continue; + + // Already triggered this cycle + if (blast.Triggered) + continue; + + // Accumulate time and check delay + blast.AccumulatedTime += frameTime; + if (blast.AccumulatedTime < blast.Delay) + continue; + + // Execute blast + blast.Triggered = true; + BlastEntities(uid, blast, xform); + } + } + + private void BlastEntities(EntityUid uid, ZoneAnomalyEffectBlastComponent blast, TransformComponent xform) + { + var center = _transform.GetWorldPosition(xform); + var epicenter = _transform.GetMapCoordinates(uid); + var targets = _lookup.GetEntitiesInRange(epicenter, blast.ThrowRange); + + foreach (var target in targets) + { + // Check whitelist + if (blast.Whitelist is { } whitelist && !_whitelist.IsWhitelistPass(whitelist, target)) + continue; + + if (!TryComp(target, out var physics) || physics.BodyType == BodyType.Static) + continue; + + var targetPos = _transform.GetWorldPosition(target); + var direction = targetPos - center; + var distance = direction.Length(); + + Vector2 normalizedDir; + if (distance < 0.1f) + { + // Entity at center - random scatter direction + var angle = _random.NextDouble() * Math.PI * 2; + normalizedDir = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)); + distance = 0.1f; + } + else + { + normalizedDir = direction / distance; + } + + // Force scales inversely with distance (closer = stronger) + var forceMult = Math.Max(0.5f, 1f - (distance / blast.ThrowRange)); + var force = normalizedDir * blast.ThrowForce * forceMult * physics.Mass; + + _physics.ApplyLinearImpulse(target, force, body: physics); + } + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectDamageSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectDamageSystem.cs new file mode 100644 index 00000000000..f9f5e71b5bc --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectDamageSystem.cs @@ -0,0 +1,79 @@ +using Content.Server.Atmos.Components; +using Content.Server.Atmos.EntitySystems; +using Content.Server.Temperature.Systems; +using Content.Shared._Stalker.ZoneAnomaly; +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Content.Shared.Damage; +using Robust.Shared.Timing; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +public sealed class ZoneAnomalyEffectDamageSystem : EntitySystem +{ + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly FlammableSystem _flammable = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnActivate); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var anomaly, out var effect)) + { + if (!effect.DamageUpdate) + continue; + + if (anomaly.State != ZoneAnomalyState.Activated) + continue; + + if (effect.DamageUpdateTime > _timing.CurTime) + continue; + + if (effect.Damage is not { } damage) + continue; + + foreach (var target in anomaly.InAnomaly) + { + Damage((uid, effect), target); + } + + effect.DamageUpdateTime = _timing.CurTime + effect.DamageUpdateDelay; + } + } + + private void OnActivate(Entity effect, ref ZoneAnomalyActivateEvent args) + { + if (!TryComp(effect, out var anomaly)) + return; + + effect.Comp.DamageUpdateTime = TimeSpan.Zero; + + foreach (var trigger in anomaly.InAnomaly) + { + Damage(effect, trigger); + } + } + + private void Damage(Entity effect, EntityUid target) + { + if (!TryComp(target, out _)) + return; + if (effect.Comp.FireStacks > 0) + { + _flammable.AdjustFireStacks(target, effect.Comp.FireStacks); + _flammable.Ignite(target, effect); + } + + if (effect.Comp.Damage is not { } damage) + return; + + _damageable.TryChangeDamage(target, damage, effect.Comp.IgnoreResistances, effect.Comp.InterruptsDoAfters); + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectDestroySystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectDestroySystem.cs new file mode 100644 index 00000000000..5da7e243e1f --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectDestroySystem.cs @@ -0,0 +1,32 @@ +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Content.Shared.Whitelist; +using Robust.Shared.Spawners; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +public sealed class ZoneAnomalyEffectDestroySystem : EntitySystem +{ + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + public override void Initialize() + { + SubscribeLocalEvent(OnActivate); + } + + private void OnActivate(Entity effect, ref ZoneAnomalyActivateEvent args) + { + foreach (var trigger in args.Triggers) + { + if(_whitelistSystem.IsWhitelistFail(effect.Comp.Whitelist, trigger)) + continue; + + if (HasComp(trigger)) + continue; + + var comp = EnsureComp(trigger); + comp.Lifetime = effect.Comp.Lifetime; + + Dirty(trigger, comp); + } + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectDischargeSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectDischargeSystem.cs new file mode 100644 index 00000000000..e39eff41d82 --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectDischargeSystem.cs @@ -0,0 +1,90 @@ +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Shared._Stalker.ZoneAnomaly; +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Robust.Shared.Containers; +using Robust.Shared.Timing; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +public sealed class ZoneAnomalyEffectDischargeSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly BatterySystem _batterySystem = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnActivate); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var anomaly, out var effect)) + { + if (anomaly.State != ZoneAnomalyState.Activated) + continue; + + if (effect.DischargeUpdateTime > _timing.CurTime) + continue; + + foreach (var target in anomaly.InAnomaly) + { + Discharge((uid, effect), target); + } + effect.DischargeUpdateTime = _timing.CurTime + effect.DischargeUpdateDelay; + } + } + + private void OnActivate(Entity effect, ref ZoneAnomalyActivateEvent args) + { + if (!TryComp(effect, out var anomaly)) + return; + + effect.Comp.DischargeUpdateTime = TimeSpan.Zero; + foreach (var trigger in anomaly.InAnomaly) + { + Discharge(effect, trigger); + } + } + + private void Discharge(Entity effect, EntityUid target) + { + if (!TryComp(target, out var container)) + return; + var batteries = GetBatteries(target, container); + foreach(var (item, battery) in batteries) + { + if (battery != null) + { + _batterySystem.UseCharge(item, battery.CurrentCharge, battery); + } + } + } + + private List<(EntityUid entity, BatteryComponent battery)> GetBatteries(EntityUid uid, ContainerManagerComponent? managerComponent = null) + { + var result = new List<(EntityUid entity, BatteryComponent battery)>(); + if (!Resolve(uid, ref managerComponent)) + return new List<(EntityUid entity, BatteryComponent battery)>(); + + foreach (var container in managerComponent.Containers.Values) + { + foreach (var element in container.ContainedEntities) + { + if (TryComp(element, out var battery)) + { + result.Add((element, battery)); + } + if (TryComp(element, out var manager)) + { + result.AddRange(GetBatteries(element, manager)); + } + } + } + return result; + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectExplosionSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectExplosionSystem.cs new file mode 100644 index 00000000000..2f6ee8d80ec --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectExplosionSystem.cs @@ -0,0 +1,22 @@ +using Content.Server.Explosion.EntitySystems; +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +public sealed class ZoneAnomalyEffectExplosionSystem : EntitySystem +{ + [Dependency] private readonly ExplosionSystem _explosion = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnActivate); + } + + private void OnActivate(Entity effect, ref ZoneAnomalyActivateEvent args) + { + _explosion.QueueExplosion(effect, effect.Comp.ProtoId, effect.Comp.TotalIntensity, effect.Comp.Slope, effect.Comp.MaxTileIntensity, canCreateVacuum: false, addLog: false); + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectFlashSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectFlashSystem.cs new file mode 100644 index 00000000000..de4adec4011 --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectFlashSystem.cs @@ -0,0 +1,33 @@ +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Content.Server.Flash; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +public sealed class ZoneAnomalyEffectFlashSystem : EntitySystem +{ + [Dependency] private readonly FlashSystem _flashSystem = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnActivate); + } + + private void OnActivate(Entity effect, ref ZoneAnomalyActivateEvent args) + { + if (!TryComp(effect, out var anomaly)) + return; + + foreach (var trigger in anomaly.InAnomaly) + { + Flash(effect, trigger); + } + } + + private void Flash(Entity effect, EntityUid target) + { + if (!TryComp(effect, out var comp)) + return; + _flashSystem.FlashArea(effect.Owner, target, comp.Range, TimeSpan.FromSeconds(comp.Duration)); + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectFoamSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectFoamSystem.cs new file mode 100644 index 00000000000..e22c6c2b163 --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectFoamSystem.cs @@ -0,0 +1,29 @@ +using Content.Server.Fluids.EntitySystems; +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Content.Shared.Chemistry.Components; +using Robust.Shared.Prototypes; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +public sealed class ZoneAnomalyEffectFoamSystem : EntitySystem +{ + [Dependency] private readonly SmokeSystem _smoke = default!; + + private readonly EntProtoId _foamPrototypeId = "Foam"; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnActivate); + } + + private void OnActivate(Entity effect, ref ZoneAnomalyActivateEvent args) + { + var solution = new Solution(); + solution.AddReagent(effect.Comp.Reagent, effect.Comp.ReagentAmount); + + var foam = Spawn(_foamPrototypeId, Transform(effect).MapPosition); + _smoke.StartSmoke(foam, solution, effect.Comp.Duration, effect.Comp.Range); + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectGibSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectGibSystem.cs new file mode 100644 index 00000000000..34991b13c5c --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectGibSystem.cs @@ -0,0 +1,316 @@ +using System.Numerics; +using Content.Server.Body.Systems; +using Content.Shared._Stalker.ZoneAnomaly; +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Content.Shared.Body.Components; +using Content.Shared.Stunnable; +using Content.Shared.Whitelist; +using Robust.Server.GameObjects; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Random; +using Robust.Shared.Timing; +using ZoneAnomalySystem = Content.Shared._Stalker.ZoneAnomaly.ZoneAnomalySystem; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +/// +/// Handles the delayed gib effect for vortex-type anomalies. +/// +/// +/// When an entity reaches the anomaly's core radius, it becomes "doomed": +/// +/// Entity is paralyzed (stunned + knocked down) +/// Entity is teleported to the exact center +/// Entity is pinned at center until the gib timer expires +/// Entity is gibbed and nearby objects are thrown outward +/// +/// The gib timer continues processing even if the anomaly state changes, +/// ensuring doomed entities are always gibbed once marked. +/// +public sealed class ZoneAnomalyEffectGibSystem : EntitySystem +{ + [Dependency] private readonly AppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly BodySystem _body = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly PhysicsSystem _physics = default!; + [Dependency] private readonly SharedStunSystem _stun = default!; + [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly ZoneAnomalySystem _zoneAnomaly = default!; + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var gib, out var anomaly, out var xform)) + { + var anomalyPos = _transform.GetWorldPosition(xform); + var anyNewDoomed = false; + var anyGibbed = false; + + // Phase 1: Track entities in core (escapable doom timer) + // Entities can escape if they leave core radius before timer expires + if (anomaly.State == ZoneAnomalyState.Activated) + { + gib.PendingRemovalBuffer.Clear(); + + // Check all entities in anomaly range + foreach (var entityUid in anomaly.InAnomaly) + { + // Skip if already doomed (being gibbed) + if (gib.DoomedEntities.ContainsKey(entityUid)) + continue; + + // Check whitelist + if (gib.Whitelist is { } whitelist && + !_whitelistSystem.IsWhitelistPass(whitelist, entityUid)) + continue; + + // Must have a body to gib + if (!HasComp(entityUid)) + continue; + + // Check distance to core + var entityPos = _transform.GetWorldPosition(entityUid); + var distance = (anomalyPos - entityPos).Length(); + var inCore = distance <= gib.CoreRadius; + + if (inCore) + { + // Entity is in core - start or continue doom timer + if (!gib.PendingDoom.ContainsKey(entityUid)) + { + // Just entered core - start timer + gib.PendingDoom[entityUid] = _timing.CurTime + gib.GibDelay; + + // Visual feedback: show "doomed" sprite when first entity enters pending + if (gib.PendingDoom.Count == 1) + _appearance.SetData(uid, ZoneAnomalyGibVisuals.Doomed, true); + + // Audio feedback: play warning sound + if (gib.PendingDoomSound != null) + _audio.PlayPvs(gib.PendingDoomSound, uid); + } + else if (_timing.CurTime >= gib.PendingDoom[entityUid]) + { + // Timer expired while in core - DOOMED! + gib.DoomedEntities[entityUid] = _timing.CurTime; // Gib immediately + gib.PendingDoom.Remove(entityUid); + anyNewDoomed = true; + + // Knock down to prevent escape + var stunDuration = TimeSpan.FromSeconds(2); + _stun.TryKnockdown(entityUid, stunDuration, force: true); + } + // else: still waiting, keep pulling them + } + else + { + // Entity escaped core - remove from pending + if (gib.PendingDoom.ContainsKey(entityUid)) + { + gib.PendingRemovalBuffer.Add(entityUid); + } + } + } + + // Clean up escaped entities + foreach (var escaped in gib.PendingRemovalBuffer) + { + gib.PendingDoom.Remove(escaped); + } + + // Check if we should enter grace period after escapes + if (gib.PendingRemovalBuffer.Count > 0) + { + TryEnterGracePeriod(uid, gib, anomaly); + } + + // Check grace period even if no escapes occurred + // This handles the case where anomaly activated but no valid targets entered core + if (gib.PendingDoom.Count == 0 && gib.DoomedEntities.Count == 0) + { + TryEnterGracePeriod(uid, gib, anomaly); + } + } + + // Phase 2: ALWAYS process gib timers if there are doomed entities + // (even if anomaly state changed to Charging/Idle) + if (gib.DoomedEntities.Count > 0) + { + gib.GibRemovalBuffer.Clear(); + + foreach (var (entityUid, gibTime) in gib.DoomedEntities) + { + // Check if entity was deleted + if (Deleted(entityUid)) + { + gib.GibRemovalBuffer.Add(entityUid); + continue; + } + + // Check if timer expired + if (_timing.CurTime < gibTime) + continue; + + // Time to gib! + if (TryComp(entityUid, out var body)) + { + _body.GibBody(entityUid, gib.GibOrgans, body); + anyGibbed = true; + } + + gib.GibRemovalBuffer.Add(entityUid); + } + + // Remove gibbed/deleted entities from tracking + foreach (var entityUid in gib.GibRemovalBuffer) + { + gib.DoomedEntities.Remove(entityUid); + } + + // Throw nearby entities after gibbing (skip doomed ones) + if (anyGibbed && gib.ThrowOnGib) + { + ThrowNearbyEntities(uid, gib, anomalyPos); + } + + // Check if we should enter grace period after gib + if (anyGibbed) + { + TryEnterGracePeriod(uid, gib, anomaly); + } + + // Pin remaining doomed entities to center + foreach (var entityUid in gib.DoomedEntities.Keys) + { + if (Deleted(entityUid)) + continue; + + // Keep them at center + _transform.SetWorldPosition(entityUid, anomalyPos); + + // Zero velocity so they don't drift + if (TryComp(entityUid, out var physics)) + { + _physics.SetLinearVelocity(entityUid, Vector2.Zero, body: physics); + } + } + } + + // Phase 3: Update appearance based on pending + doomed count (only when changed) + var hasDanger = gib.DoomedEntities.Count > 0 || gib.PendingDoom.Count > 0; + if (hasDanger != gib.LastDangerState) + { + gib.LastDangerState = hasDanger; + _appearance.SetData(uid, ZoneAnomalyGibVisuals.Doomed, hasDanger); + } + } + } + + /// + /// Throws all nearby non-static entities away from the center point. + /// + /// + /// Entities at or very near the center receive a random direction. + /// Force is scaled by distance (closer = stronger) and entity mass. + /// + private void ThrowNearbyEntities(EntityUid anomalyUid, ZoneAnomalyEffectGibComponent gib, Vector2 center) + { + var epicenter = _transform.GetMapCoordinates(anomalyUid); + var targets = _lookup.GetEntitiesInRange(epicenter, gib.ThrowRange); + + foreach (var target in targets) + { + // Skip entities that are still doomed (they're pinned anyway) + if (gib.DoomedEntities.ContainsKey(target)) + continue; + + if (!TryComp(target, out var physics) || physics.BodyType == BodyType.Static) + continue; + + var targetPos = _transform.GetWorldPosition(target); + var direction = targetPos - center; + var distance = direction.Length(); + + Vector2 normalizedDir; + if (distance < 0.1f) + { + // Entity is at/near center - give random direction to scatter + var angle = _random.NextDouble() * Math.PI * 2; + normalizedDir = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)); + distance = 0.1f; // Treat as very close for force calculation + } + else + { + normalizedDir = direction / distance; + } + + var forceMult = Math.Max(0.5f, 1f - (distance / gib.ThrowRange)); + var force = normalizedDir * gib.ThrowForce * forceMult * physics.Mass; + + _physics.ApplyLinearImpulse(target, force, body: physics); + } + } + + /// + /// Checks if the anomaly should enter grace period. + /// Triggers when: no pending doom, no doomed entities, and no valid targets in range. + /// + private void TryEnterGracePeriod( + EntityUid uid, + ZoneAnomalyEffectGibComponent gib, + ZoneAnomalyComponent anomaly) + { + // Only enter grace period from Activated state + if (anomaly.State != ZoneAnomalyState.Activated) + return; + + // Must have no entities being processed + if (gib.PendingDoom.Count > 0 || gib.DoomedEntities.Count > 0) + return; + + // Respect the configured activation delay as minimum duration + if (_timing.CurTime < anomaly.ActivationTime) + return; + + // Check for remaining valid targets in range + gib.StaleEntityBuffer.Clear(); + foreach (var entityUid in anomaly.InAnomaly) + { + // Clean up stale (deleted) entities + if (Deleted(entityUid)) + { + gib.StaleEntityBuffer.Add(entityUid); + continue; + } + + if (gib.Whitelist is { } whitelist && + !_whitelistSystem.IsWhitelistPass(whitelist, entityUid)) + continue; + + if (!HasComp(entityUid)) + continue; + + // Found a valid target - don't enter grace period + return; + } + + // Remove stale entities from tracking + foreach (var stale in gib.StaleEntityBuffer) + { + anomaly.InAnomaly.Remove(stale); + } + + // No pending, no doomed, no valid targets → grace period + _zoneAnomaly.TryRecharge((uid, anomaly)); + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectGravityWellSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectGravityWellSystem.cs new file mode 100644 index 00000000000..f4d3e240a24 --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectGravityWellSystem.cs @@ -0,0 +1,136 @@ +using Content.Shared._Stalker.ZoneAnomaly; +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Content.Shared.Standing; +using Content.Shared.Whitelist; +using Robust.Server.GameObjects; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Timing; +using System.Numerics; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +public sealed class ZoneAnomalyEffectGravityWellSystem : EntitySystem +{ + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly PhysicsSystem _physics = default!; + [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + + private EntityQuery _physicsQuery; + private EntityQuery _transformQuery; + + public override void Initialize() + { + base.Initialize(); + + _physicsQuery = GetEntityQuery(); + _transformQuery = GetEntityQuery(); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var effect, out var anomaly, out _)) + { + if (anomaly.State != ZoneAnomalyState.Activated) + continue; + + if (effect.PeriodTime > _timing.CurTime) + continue; + + GravPulse((uid, effect)); + effect.PeriodTime = _timing.CurTime + effect.Period; + } + } + + private void GravPulse(Entity effect) + { + var epicenter = _transform.GetMapCoordinates(effect); + var targets = _lookup.GetEntitiesInRange(epicenter, effect.Comp.Distance); + var bodyQuery = GetEntityQuery(); + var xformQuery = GetEntityQuery(); + + foreach (var entity in targets) + { + if (effect.Comp.Whitelist is { } whitelist && !_whitelistSystem.IsWhitelistPass(whitelist, entity)) + continue; + + if (!bodyQuery.TryGetComponent(entity, out var physics) || physics.BodyType == BodyType.Static) + continue; + + var entityPosition = _transform.GetWorldPosition(entity, xformQuery); + var displacement = epicenter.Position - entityPosition; + var distance = displacement.Length(); + + if (distance == 0) + continue; // Avoid division by zero + + // Skip pulling entities already in gib core radius + if (TryComp(effect.Owner, out var gib) && + distance <= gib.CoreRadius) + { + continue; + } + + // Normalized vector pointing towards the epicenter + var radialDirection = displacement / distance; + + // **Adjust radial direction based on mode** + if (effect.Comp.Mode == ZoneAnomalyEffectGravityWellMode.Repel) + { + // Reverse the radial direction for repulsion + radialDirection = -radialDirection; + } + + // Perpendicular vector for tangential force (rotating around the center) + var tangentialDirection = new Vector2(-radialDirection.Y, radialDirection.X); + + // Calculate scaling factor based on distance and gradient + var scaling = GetScaling(effect, distance); + + // Calculate radial and tangential forces + var radialForce = radialDirection * effect.Comp.Radial * scaling; + var tangentialForce = tangentialDirection * effect.Comp.Tangential * scaling; + + // Reduce force for prone entities + var forceMultiplier = 1.0f; + if (TryComp(entity, out var standing) && !standing.Standing) + { + forceMultiplier = 0.5f; + } + + // Total force + var totalForce = (radialForce + tangentialForce) * physics.Mass * forceMultiplier; + + // Apply the impulse to the entity + _physics.ApplyLinearImpulse(entity, totalForce, body: physics); + } + } + + private float GetScaling(Entity effect, float distance) + { + var maxDistance = effect.Comp.Distance; + + // Clamp distance to maxDistance to prevent scaling beyond the effect's range + distance = Math.Min(distance, maxDistance); + + switch (effect.Comp.Gradient) + { + case ZoneAnomalyEffectGravityWellGradient.Linear: + // Scaling increases linearly with distance + return distance / maxDistance; + case ZoneAnomalyEffectGravityWellGradient.ReversedLinear: + // Scaling decreases linearly with distance + return 1f - (distance / maxDistance); + default: + // Default to constant scaling + return 1f; + } + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectLightArcSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectLightArcSystem.cs new file mode 100644 index 00000000000..ff4568a4a0b --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectLightArcSystem.cs @@ -0,0 +1,65 @@ +using Content.Server.Lightning; +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Content.Shared.Whitelist; +using Robust.Shared.Map.Components; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +public sealed class ZoneAnomalyEffectLightArcSystem : EntitySystem +{ + private const int MaxIterations = 12; + + [Dependency] private readonly BatterySystem _battery = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly LightningSystem _lightning = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnActivate); + } + + private void OnActivate(Entity effect, ref ZoneAnomalyActivateEvent args) + { + var i = 0; + var entities = _lookup.GetEntitiesInRange(Transform(effect).Coordinates, effect.Comp.Distance); + foreach (var entity in entities) + { + if (i > MaxIterations) + break; + + // We don't need to shoot all the entities + if(_whitelistSystem.IsWhitelistPass(effect.Comp.Whitelist, entity)) + continue; + + // Fixes 10 million shots being fired at one entity due to it containing targets + if (IsValidRecursively(effect, entity)) + continue; + + TryRecharge(effect, entity); + _lightning.ShootLightning(effect, entity, effect.Comp.Lighting); + + i++; + } + } + + private void TryRecharge(Entity effect, EntityUid target) + { + if (!TryComp(target, out var battery)) + return; + + _battery.SetCharge(target, battery.CurrentCharge + battery.MaxCharge * effect.Comp.ChargePercent, battery); + } + + private bool IsValidRecursively(Entity effect, EntityUid uid) + { + var parent = Transform(uid).ParentUid; + if (HasComp(parent) || HasComp(parent)) + return false; + + return _whitelistSystem.IsWhitelistPass(effect.Comp.Whitelist, parent) || IsValidRecursively(effect, parent); + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectPlaySoundSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectPlaySoundSystem.cs new file mode 100644 index 00000000000..41c44227b06 --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectPlaySoundSystem.cs @@ -0,0 +1,25 @@ +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Robust.Server.Audio; +using Robust.Shared.Audio; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +public sealed class ZoneAnomalyEffectPlaySoundSystem : EntitySystem +{ + [Dependency] private readonly AudioSystem _audio = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnActivate); + } + + private void OnActivate(Entity effect, ref ZoneAnomalyActivateEvent args) + { + _audio.PlayPredicted(effect.Comp.Sound, Transform(effect).Coordinates, effect, new AudioParams() + { + MaxDistance = effect.Comp.Range, + Volume = effect.Comp.Volume, + }); + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectPointLightSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectPointLightSystem.cs new file mode 100644 index 00000000000..88f90c525d6 --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectPointLightSystem.cs @@ -0,0 +1,21 @@ +using Content.Shared._Stalker.ZoneAnomaly; +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Robust.Server.GameObjects; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +public sealed class ZoneAnomalyEffectPointLightSystem : EntitySystem +{ + [Dependency] private readonly PointLightSystem _pointLight = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnChangeState); + } + + private void OnChangeState(Entity effect, ref ZoneAnomalyChangedState args) + { + _pointLight.SetEnabled(effect, args.Current == ZoneAnomalyState.Activated); + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectRandomTeleportSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectRandomTeleportSystem.cs new file mode 100644 index 00000000000..abd09fb91b5 --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectRandomTeleportSystem.cs @@ -0,0 +1,44 @@ +using System.Linq; +using Content.Shared._Stalker.ZoneAnomaly; +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Systems; +using Robust.Shared.Random; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +public sealed class ZoneAnomalyEffectRandomTeleportSystem : SharedZoneAnomalyEffectRandomTeleportSystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly ZoneAnomalySystem _anomaly = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnActivate); + } + + private void OnActivate(Entity effect, ref ZoneAnomalyActivateEvent args) + { + var points = EntityQuery().ToList(); + if (points.Contains(effect)) + points.Remove(effect); + + foreach (var trigger in args.Triggers) + { + var transform = Transform(effect); + if (points.Count == 0) + { + TeleportEntity(trigger, transform.Coordinates); + return; + } + + var point = _random.Pick(points); + var destination = Transform(point.Owner).Coordinates; + + if (TryComp(point.Owner, out var comp)) + _anomaly.TryRecharge((point.Owner, comp)); + + TeleportEntity(trigger, destination); + } + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectSpawnSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectSpawnSystem.cs new file mode 100644 index 00000000000..1eb4bb16a81 --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectSpawnSystem.cs @@ -0,0 +1,27 @@ +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Robust.Shared.Random; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +public sealed class ZoneAnomalyEffectSpawnSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnActivate); + } + + private void OnActivate(Entity effect, ref ZoneAnomalyActivateEvent args) + { + var content = _random.Pick(effect.Comp.Entry); + var position = Transform(effect).Coordinates; + + for (var i = 0; i < content.Amount; i++) + { + var offset = _random.NextVector2(-effect.Comp.Offset, effect.Comp.Offset); + Spawn(content.PrototypeId, position.Offset(offset)); + } + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectStealthSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectStealthSystem.cs new file mode 100644 index 00000000000..e48954d1592 --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectStealthSystem.cs @@ -0,0 +1,74 @@ +using Content.Server.Stealth; +using Content.Shared._Stalker.ZoneAnomaly; +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Content.Shared.Stealth.Components; +using Robust.Shared.Timing; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +public sealed partial class ZoneAnomalyEffectStealthSystem : EntitySystem +{ + [Dependency] private readonly StealthSystem _stealth = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnChangeState); + } + + private void OnStartup(Entity effect, ref ComponentStartup args) + { + if (!HasComp(effect.Owner)) + return; + _stealth.SetVisibility(effect, effect.Comp.Idle); + } + + private void OnChangeState(Entity effect, ref ZoneAnomalyChangedState args) + { + // Cancel any active fade when state changes + effect.Comp.IsFading = false; + + switch (args.Current) + { + case ZoneAnomalyState.Idle: + _stealth.SetVisibility(effect, effect.Comp.Idle); + break; + + case ZoneAnomalyState.Activated: + _stealth.SetVisibility(effect, effect.Comp.Activated); + break; + + case ZoneAnomalyState.Charging: + // Start fade animation instead of instant visibility change + effect.Comp.IsFading = true; + effect.Comp.FadeStartVisibility = effect.Comp.Activated; + effect.Comp.FadeStartTime = _timing.CurTime; + break; + } + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var effect, out _)) + { + if (!effect.IsFading) + continue; + + var elapsed = (float)(_timing.CurTime - effect.FadeStartTime).TotalSeconds; + var progress = Math.Clamp(elapsed / effect.ChargingFadeDuration, 0f, 1f); + + var visibility = effect.FadeStartVisibility + + (effect.Charging - effect.FadeStartVisibility) * progress; + + _stealth.SetVisibility(uid, visibility); + + if (progress >= 1f) + effect.IsFading = false; + } + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectThrowSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectThrowSystem.cs new file mode 100644 index 00000000000..40a07725036 --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyEffectThrowSystem.cs @@ -0,0 +1,37 @@ +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Content.Shared.Throwing; +using Content.Shared.Whitelist; +using Robust.Server.GameObjects; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +public sealed class ZoneAnomalyEffectThrowSystem : EntitySystem +{ + [Dependency] private readonly ThrowingSystem _throwing = default!; + [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnActivate); + } + + private void OnActivate(Entity effect, ref ZoneAnomalyActivateEvent args) + { + if (!TryComp(effect, out var anomaly)) + return; + + foreach (var entity in anomaly.InAnomaly) + { + if (effect.Comp.Whitelist is { } whitelist && _whitelist.IsWhitelistFail(whitelist, entity)) + continue; + + var direction = _transform.GetWorldPosition(entity) - _transform.GetWorldPosition(effect); + if (direction.Length() < effect.Comp.MinDistance) + continue; + + _throwing.TryThrow(entity, direction * effect.Comp.Distance, effect.Comp.Force, effect, 0); + } + } +} diff --git a/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyStealthPulseSystem.cs b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyStealthPulseSystem.cs new file mode 100644 index 00000000000..7dbf2f3611d --- /dev/null +++ b/Content.Server/_Stalker/ZoneAnomaly/Effects/Systems/ZoneAnomalyStealthPulseSystem.cs @@ -0,0 +1,72 @@ +using Content.Server.Stealth; +using Content.Shared._Stalker.ZoneAnomaly; +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Content.Shared.Stealth.Components; +using Robust.Shared.Timing; + +namespace Content.Server._Stalker.ZoneAnomaly.Effects.Systems; + +/// +/// Pulses the stealth visibility during anomaly idle state. +/// Creates a breathing/pulsing visual effect by modifying the Stealth component's visibility. +/// +public sealed class ZoneAnomalyStealthPulseSystem : EntitySystem +{ + [Dependency] private readonly StealthSystem _stealth = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnStateChanged); + } + + private void OnStartup(EntityUid uid, ZoneAnomalyStealthPulseComponent comp, ComponentStartup args) + { + // Check initial state and start pulsing if idle + if (TryComp(uid, out var anomaly)) + { + comp.IsPulsing = anomaly.State == ZoneAnomalyState.Idle; + } + } + + private void OnStateChanged(EntityUid uid, ZoneAnomalyStealthPulseComponent comp, ref ZoneAnomalyChangedState args) + { + comp.IsPulsing = args.Current == ZoneAnomalyState.Idle; + comp.PulseTime = 0f; // Reset pulse cycle when state changes + + // Note: When not pulsing, ZoneAnomalyEffectStealth handles the visibility + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var curTime = _timing.CurTime; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var pulse, out var stealth)) + { + // Only pulse during idle state + if (!pulse.IsPulsing) + continue; + + // Throttle updates based on interval + if (curTime < pulse.NextUpdate) + continue; + + pulse.NextUpdate = curTime + TimeSpan.FromSeconds(pulse.UpdateInterval); + pulse.PulseTime += pulse.UpdateInterval; + + // Sine wave oscillation between MinVisibility and MaxVisibility + // Sin output is -1 to 1, we convert to 0 to 1 for interpolation + var t = (MathF.Sin(pulse.PulseTime * MathF.PI * 2f / pulse.PulseDuration) + 1f) / 2f; + var visibility = pulse.MinVisibility + (pulse.MaxVisibility - pulse.MinVisibility) * t; + + _stealth.SetVisibility(uid, visibility); + } + } +} diff --git a/Content.Shared/_Stalker/Anomaly/Data/STAnomalyGenerationOptions.cs b/Content.Shared/_Stalker/Anomaly/Data/STAnomalyGenerationOptions.cs new file mode 100644 index 00000000000..764ae787d26 --- /dev/null +++ b/Content.Shared/_Stalker/Anomaly/Data/STAnomalyGenerationOptions.cs @@ -0,0 +1,29 @@ +using Robust.Shared.Map; +using Robust.Shared.Prototypes; + +namespace Content.Shared._Stalker.Anomaly.Data; + +[DataDefinition, Serializable] +public partial struct STAnomalyGenerationOptions +{ + public MapId MapId; + + [DataField] + public int TotalCount = 400; + + [DataField] + public HashSet AnomalyEntries = []; +} + +[DataDefinition, Serializable] +public partial struct STAnomalyGeneratorAnomalyEntry +{ + [DataField] + public EntProtoId ProtoId; + + [DataField] + public float Dangerous = 1f; + + [DataField] + public float Weight = 1f; +} diff --git a/Content.Shared/_Stalker/Anomaly/Prototypes/STAnomalyGenerationOptionsPrototype.cs b/Content.Shared/_Stalker/Anomaly/Prototypes/STAnomalyGenerationOptionsPrototype.cs new file mode 100644 index 00000000000..8ae7479dd05 --- /dev/null +++ b/Content.Shared/_Stalker/Anomaly/Prototypes/STAnomalyGenerationOptionsPrototype.cs @@ -0,0 +1,14 @@ +using Content.Shared._Stalker.Anomaly.Data; +using Robust.Shared.Prototypes; + +namespace Content.Shared._Stalker.Anomaly.Prototypes; + +[Prototype("stAnomalyGenerationOptions")] +public sealed partial class STAnomalyGenerationOptionsPrototype : IPrototype +{ + [IdDataField] + public string ID { get; private set; } = string.Empty; + + [DataField] + public STAnomalyGenerationOptions Options; +} diff --git a/Content.Shared/_Stalker/Anomaly/Prototypes/STAnomalyNaturePrototype.cs b/Content.Shared/_Stalker/Anomaly/Prototypes/STAnomalyNaturePrototype.cs new file mode 100644 index 00000000000..c8c2acf5c7d --- /dev/null +++ b/Content.Shared/_Stalker/Anomaly/Prototypes/STAnomalyNaturePrototype.cs @@ -0,0 +1,10 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared._Stalker.Anomaly.Prototypes; + +[Prototype("stAnomalyNature")] +public sealed partial class STAnomalyNaturePrototype : IPrototype +{ + [IdDataField] + public string ID { get; private set; } = string.Empty; +} diff --git a/Content.Shared/_Stalker/Anomaly/STAnomalyTipsComponent.cs b/Content.Shared/_Stalker/Anomaly/STAnomalyTipsComponent.cs new file mode 100644 index 00000000000..3b751f6480c --- /dev/null +++ b/Content.Shared/_Stalker/Anomaly/STAnomalyTipsComponent.cs @@ -0,0 +1,17 @@ +using System.Numerics; +using Robust.Shared.Utility; + +namespace Content.Shared._Stalker.Anomaly; + +[RegisterComponent] +public sealed partial class STAnomalyTipsComponent : Component +{ + [DataField] + public Vector2 Offset = new(-0.5f, -0.5f); + + [DataField] + public SpriteSpecifier Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/_Stalker/Interface/Overlays/anomaly_tips.rsi"), "unknown"); + + [DataField] + public float Visibility = 0.5f; +} diff --git a/Content.Shared/_Stalker/Anomaly/STAnomalyTipsViewingComponent.cs b/Content.Shared/_Stalker/Anomaly/STAnomalyTipsViewingComponent.cs new file mode 100644 index 00000000000..44c711ed27b --- /dev/null +++ b/Content.Shared/_Stalker/Anomaly/STAnomalyTipsViewingComponent.cs @@ -0,0 +1,8 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._Stalker.Anomaly; + +[RegisterComponent, NetworkedComponent] +public sealed partial class STAnomalyTipsViewingComponent : Component +{ +} diff --git a/Content.Shared/_Stalker/Anomaly/STAnomalyVisuals.cs b/Content.Shared/_Stalker/Anomaly/STAnomalyVisuals.cs new file mode 100644 index 00000000000..8b1d8b883e2 --- /dev/null +++ b/Content.Shared/_Stalker/Anomaly/STAnomalyVisuals.cs @@ -0,0 +1,9 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared._Stalker.Anomaly; + +[Serializable, NetSerializable] +public enum STAnomalyVisuals +{ + Layer, +} diff --git a/Content.Shared/_Stalker/Anomaly/Triggers/Events/STAnomalyChangedStateEvent.cs b/Content.Shared/_Stalker/Anomaly/Triggers/Events/STAnomalyChangedStateEvent.cs new file mode 100644 index 00000000000..76923754049 --- /dev/null +++ b/Content.Shared/_Stalker/Anomaly/Triggers/Events/STAnomalyChangedStateEvent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared._Stalker.Anomaly.Triggers.Events; + +public sealed class STAnomalyChangedStateEvent(string previousState, string state) : EntityEventArgs +{ + public readonly string PreviousState = previousState; + public readonly string State = state; +} diff --git a/Content.Shared/_Stalker/Anomaly/Triggers/Events/STAnomalyTriggerEvent.cs b/Content.Shared/_Stalker/Anomaly/Triggers/Events/STAnomalyTriggerEvent.cs new file mode 100644 index 00000000000..af2c3f33e30 --- /dev/null +++ b/Content.Shared/_Stalker/Anomaly/Triggers/Events/STAnomalyTriggerEvent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared._Stalker.Anomaly.Triggers.Events; + +public sealed class STAnomalyTriggerEvent(HashSet groups, bool stateChanger = true) : EntityEventArgs +{ + public IReadOnlySet Groups = groups; + public readonly bool StateChanger = stateChanger; +} diff --git a/Content.Shared/_Stalker/Anomaly/Triggers/Events/STAnomalyTriggerStartCollideGetAdditionalGroupsEvent.cs b/Content.Shared/_Stalker/Anomaly/Triggers/Events/STAnomalyTriggerStartCollideGetAdditionalGroupsEvent.cs new file mode 100644 index 00000000000..f42cc0f6f07 --- /dev/null +++ b/Content.Shared/_Stalker/Anomaly/Triggers/Events/STAnomalyTriggerStartCollideGetAdditionalGroupsEvent.cs @@ -0,0 +1,19 @@ +namespace Content.Shared._Stalker.Anomaly.Triggers.Events; + +public sealed class STAnomalyTriggerStartCollideGetAdditionalGroupsEvent(EntityUid target) : EntityEventArgs +{ + public readonly EntityUid Target = target; + public IReadOnlySet Groups => _groups; + + private readonly HashSet _groups = new(); + + public void Add(string group) + { + _groups.Add(group); + } + + public void Add(IEnumerable groups) + { + _groups.UnionWith(groups); + } +} diff --git a/Content.Shared/_Stalker/Weight/GetWeightModifiersEvent.cs b/Content.Shared/_Stalker/Weight/GetWeightModifiersEvent.cs new file mode 100644 index 00000000000..a84249a3da4 --- /dev/null +++ b/Content.Shared/_Stalker/Weight/GetWeightModifiersEvent.cs @@ -0,0 +1,17 @@ +namespace Content.Shared._Stalker.Weight; + +/// +/// Raised on an entity to collect weight modifiers from all contributing components. +/// +public sealed class GetWeightModifiersEvent : EntityEventArgs +{ + /// + /// The entity's own weight contribution. + /// + public float Self; + + /// + /// Weight from items inside the entity (inventory, storage, etc). + /// + public float Inside; +} diff --git a/Content.Shared/_Stalker/Weight/STWeightComponent.cs b/Content.Shared/_Stalker/Weight/STWeightComponent.cs new file mode 100644 index 00000000000..9ad9cf6e983 --- /dev/null +++ b/Content.Shared/_Stalker/Weight/STWeightComponent.cs @@ -0,0 +1,12 @@ +namespace Content.Shared._Stalker.Weight; + +/// +/// Stub component for entity weight tracking. +/// Used by anomaly weight-based triggers to assign trigger groups. +/// +[RegisterComponent] +public sealed partial class STWeightComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Total; +} diff --git a/Content.Shared/_Stalker/Weight/STWeightSystem.cs b/Content.Shared/_Stalker/Weight/STWeightSystem.cs new file mode 100644 index 00000000000..0ef2d06b769 --- /dev/null +++ b/Content.Shared/_Stalker/Weight/STWeightSystem.cs @@ -0,0 +1,28 @@ +namespace Content.Shared._Stalker.Weight; + +/// +/// Stub system for entity weight tracking. +/// Used by anomaly weight-based triggers and ZoneAnomaly weight modifiers. +/// +public sealed class STWeightSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + } + + /// + /// Attempts to recalculate the total weight for an entity by raising GetWeightModifiersEvent. + /// + public void TryUpdateWeight(EntityUid uid) + { + if (!TryComp(uid, out var weight)) + return; + + var ev = new GetWeightModifiersEvent(); + RaiseLocalEvent(uid, ev); + + weight.Total = ev.Self + ev.Inside; + Dirty(uid, weight); + } +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Audio/ZoneAnomalyProximitySoundComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Audio/ZoneAnomalyProximitySoundComponent.cs new file mode 100644 index 00000000000..fc81a470394 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Audio/ZoneAnomalyProximitySoundComponent.cs @@ -0,0 +1,65 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared._Stalker.ZoneAnomaly.Audio; + +/// +/// Plays an ambient sound that changes volume based on player distance to the entity center. +/// Volume scales linearly from MinVolume at MaxRange to MaxVolume at the center. +/// +/// +/// Uses cooldown-based updates for performance (not per-frame). +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ZoneAnomalyProximitySoundComponent : Component +{ + /// + /// Maximum distance from center where the sound is audible. + /// At this distance, volume equals MinVolume. + /// + [DataField, AutoNetworkedField] + public float MaxRange = 12f; + + /// + /// Volume at maximum range (0.0 to 1.0). + /// + [DataField, AutoNetworkedField] + public float MinVolume = 0.2f; + + /// + /// Volume at center (0.0 to 1.0). + /// + [DataField, AutoNetworkedField] + public float MaxVolume = 1.0f; + + /// + /// How often to update volume (in seconds). + /// Lower values = smoother but more expensive. + /// + [DataField, AutoNetworkedField] + public float UpdateCooldown = 0.25f; + + /// + /// The sound to play. + /// + [DataField(required: true), AutoNetworkedField] + public SoundSpecifier Sound = default!; + + // Runtime state (not serialized) + + /// + /// Entity of the currently playing audio stream. + /// + [ViewVariables] + public EntityUid? PlayingStream; + + /// + /// Next time to update volume. + /// + public TimeSpan NextUpdate; + + /// + /// Current calculated volume (for smooth transitions). + /// + public float CurrentVolume; +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Components/ZoneAnomalyComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Components/ZoneAnomalyComponent.cs new file mode 100644 index 00000000000..dcbb31eeb9f --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Components/ZoneAnomalyComponent.cs @@ -0,0 +1,92 @@ +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared._Stalker.ZoneAnomaly.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ZoneAnomalyComponent : Component +{ + public bool Charged => State == ZoneAnomalyState.Idle; + + [DataField, AutoNetworkedField] + public bool Detected = true; + + [DataField, AutoNetworkedField] + public int DetectedLevel = 0; + + [DataField, AutoNetworkedField] + public ZoneAnomalyState State = ZoneAnomalyState.Idle; + + [DataField, AutoNetworkedField] + public TimeSpan PreparingDelay = TimeSpan.FromSeconds(2); + + [DataField, AutoNetworkedField] + public TimeSpan PreparingTime; + + [DataField, AutoNetworkedField] + public TimeSpan ActivationDelay = TimeSpan.FromSeconds(2); + + [DataField, AutoNetworkedField] + public TimeSpan ActivationTime; + + [DataField("chargeTime"), AutoNetworkedField] + public TimeSpan ChargingDelay = TimeSpan.FromSeconds(2); + + [DataField, AutoNetworkedField] + public TimeSpan ChargingTime; + + [DataField, AutoNetworkedField] + public HashSet Triggers = new(); + + [DataField, AutoNetworkedField] + public HashSet InAnomaly = new(); + + [DataField, AutoNetworkedField] + public EntityWhitelist CollisionWhitelist = new(); + + [DataField, AutoNetworkedField] + public EntityWhitelist CollisionBlacklist = new(); +} + +public sealed partial class ZoneAnomalyStartCollideArgs : EventArgs +{ + public readonly EntityUid Anomaly; + public readonly EntityUid OtherEntity; + + public bool Activate; + + public ZoneAnomalyStartCollideArgs(EntityUid anomaly, EntityUid otherEntity) + { + Anomaly = anomaly; + OtherEntity = otherEntity; + } +} + +public sealed partial class ZoneAnomalyEndCollideArgs : EventArgs +{ + public readonly EntityUid Anomaly; + public readonly EntityUid OtherEntity; + public readonly bool IgnoreWhitelist; + + public bool Activate; + + public ZoneAnomalyEndCollideArgs(EntityUid anomaly, EntityUid otherEntity, bool ignoreWhitelist) + { + Anomaly = anomaly; + OtherEntity = otherEntity; + IgnoreWhitelist = ignoreWhitelist; + } +} + +[ByRefEvent] +public readonly record struct ZoneAnomalyChangedState(EntityUid Anomaly, ZoneAnomalyState Current, ZoneAnomalyState Previous); + +[ByRefEvent] +public readonly record struct ZoneAnomalyActivateEvent(EntityUid Anomaly, HashSet Triggers); + +[ByRefEvent] +public readonly record struct ZoneAnomalyEntityAddEvent(EntityUid Anomaly, EntityUid Entity); + +[ByRefEvent] +public readonly record struct ZoneAnomalyEntityRemoveEvent(EntityUid Anomaly, EntityUid Entity); + diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Components/ZoneAnomalyDetectorActivatorComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Components/ZoneAnomalyDetectorActivatorComponent.cs new file mode 100644 index 00000000000..a78969db3cd --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Components/ZoneAnomalyDetectorActivatorComponent.cs @@ -0,0 +1,25 @@ +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared._Stalker.ZoneAnomaly.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyDetectorActivatorComponent : Component +{ + [DataField] + public int Level; + + [DataField] + public float Distance = 5f; + + [DataField] + public int MaxCount; + + [DataField] + public bool Enabled; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan ActivationDelay = TimeSpan.FromMinutes(1f); + + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] + public TimeSpan NexActivationTime; +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Components/ZoneAnomalyDetectorComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Components/ZoneAnomalyDetectorComponent.cs new file mode 100644 index 00000000000..567c3fb42c1 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Components/ZoneAnomalyDetectorComponent.cs @@ -0,0 +1,44 @@ +using Robust.Shared.Audio; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared._Stalker.ZoneAnomaly.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyDetectorComponent : Component +{ + [DataField] + public int Level; + + [DataField] + public float Distance = 10f; + + [DataField] + public bool Enabled; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan MaxBeepInterval = TimeSpan.FromSeconds(2.5f); + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan MinBeepInterval = TimeSpan.FromSeconds(0.05f); + + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] + public TimeSpan NextBeepTime; + + [DataField] + public SoundSpecifier BeepSound = new SoundPathSpecifier("/Audio/Items/locator_beep.ogg"); +} + +[RegisterComponent] +public sealed partial class ZoneAnomalyDetectorArtifactActivatorComponent : Component +{ + [DataField] + public int Level; +} + +[Serializable, NetSerializable] +public enum ZoneAnomalyDetectorVisuals : byte +{ + Enabled, + Layer, +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectActivatorComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectActivatorComponent.cs new file mode 100644 index 00000000000..45eeb9015b9 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectActivatorComponent.cs @@ -0,0 +1,13 @@ +using Content.Shared.Whitelist; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectActivatorComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist Whitelist = new(); + + [DataField] + public float Distance = 8f; +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectBlastComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectBlastComponent.cs new file mode 100644 index 00000000000..b059075b8fd --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectBlastComponent.cs @@ -0,0 +1,62 @@ +using Content.Shared.Whitelist; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +/// +/// Throws entities outward from the anomaly center when charging state begins. +/// +/// +/// Force scales with distance - entities closer to center are thrown harder. +/// Can be configured with a delay before the blast triggers. +/// +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectBlastComponent : Component +{ + /// + /// Range of the blast effect (in tiles). + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float ThrowRange = 5f; + + /// + /// Base force for throwing entities. + /// + /// + /// Force is scaled by entity mass and distance from center. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float ThrowForce = 60f; + + /// + /// Delay after charging state begins before blast triggers (in seconds). + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Delay; + + /// + /// Only affect entities matching this whitelist. If null, affects all non-static entities. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist? Whitelist; + + /// + /// Whether the blast has triggered this charging cycle. + /// + /// + /// Runtime state - reset when anomaly leaves charging state. + /// + [ViewVariables] + public bool Triggered; + + /// + /// Time accumulated since charging state began. + /// + [ViewVariables] + public float AccumulatedTime; + + /// + /// Previous anomaly state for detecting state transitions. + /// + [ViewVariables] + public int? LastState; +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectDamageComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectDamageComponent.cs new file mode 100644 index 00000000000..a5493672e19 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectDamageComponent.cs @@ -0,0 +1,28 @@ +using Content.Shared.Damage; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectDamageComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public DamageSpecifier? Damage; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float FireStacks; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public bool DamageUpdate = false; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public bool IgnoreResistances; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public bool InterruptsDoAfters = true; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan DamageUpdateTime; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan DamageUpdateDelay = TimeSpan.FromSeconds(0.05f); +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectDestroyComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectDestroyComponent.cs new file mode 100644 index 00000000000..fc7f69f6bd0 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectDestroyComponent.cs @@ -0,0 +1,13 @@ +using Content.Shared.Whitelist; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectDestroyComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist Whitelist = new(); + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Lifetime = 0.01f; +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectDischargeComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectDischargeComponent.cs new file mode 100644 index 00000000000..95bd36c09ad --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectDischargeComponent.cs @@ -0,0 +1,19 @@ +using Content.Shared.Whitelist; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectDischargeComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist Whitelist = new(); + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float DischargePercentage = 1f; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan DischargeUpdateTime; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan DischargeUpdateDelay = TimeSpan.FromSeconds(1f); +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectExplosionComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectExplosionComponent.cs new file mode 100644 index 00000000000..1eab66ee5a3 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectExplosionComponent.cs @@ -0,0 +1,20 @@ +using Content.Shared.Explosion; +using Robust.Shared.Prototypes; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectExplosionComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public ProtoId ProtoId = "Son"; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float TotalIntensity = 500f; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Slope = 4f; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float MaxTileIntensity = 1000f; +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectFlashComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectFlashComponent.cs new file mode 100644 index 00000000000..b0fc840a55d --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectFlashComponent.cs @@ -0,0 +1,16 @@ +using Content.Shared.Whitelist; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectFlashComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist Whitelist = new(); + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Range = 3f; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Duration = 8f; +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectFoamComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectFoamComponent.cs new file mode 100644 index 00000000000..34ec07525b9 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectFoamComponent.cs @@ -0,0 +1,20 @@ +using Content.Shared.Chemistry.Reagent; +using Robust.Shared.Prototypes; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectFoamComponent : Component +{ + [DataField] + public ProtoId Reagent; + + [DataField] + public float ReagentAmount; + + [DataField] + public int Range; + + [DataField] + public float Duration; +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectGibComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectGibComponent.cs new file mode 100644 index 00000000000..d5a6a34503c --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectGibComponent.cs @@ -0,0 +1,120 @@ +using Content.Shared.Whitelist; +using Robust.Shared.Audio; +using Robust.Shared.Timing; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +/// +/// Gibs entities that reach the anomaly's core after a delay. +/// When entity reaches core: stun → teleport to center → wait → gib → throw nearby entities. +/// +/// +/// Used by vortex-type anomalies that pull entities in and destroy them at the center. +/// The gib effect continues processing even if the anomaly changes state, ensuring +/// doomed entities are always gibbed once marked. +/// +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectGibComponent : Component +{ + /// + /// Distance from anomaly center at which entity becomes doomed (in tiles). + /// + /// + /// Recommended range: 0.3 to 1.0. Smaller values require precise positioning. + /// Should be smaller than the anomaly's collision fixture radius. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float CoreRadius = 0.5f; + + /// + /// Time before doomed entity gets gibbed. + /// + /// + /// Recommended range: 2.0 to 4 seconds. + /// Entity remains stunned and pinned to center during this time. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan GibDelay = TimeSpan.FromSeconds(3.5); + + /// + /// Entities marked for gibbing with their scheduled gib time. + /// + /// + /// Runtime state - not serialized. Cleared when anomaly deactivates. + /// + [ViewVariables] + public Dictionary DoomedEntities = new(); + + /// + /// Entities currently in the core radius with their doom deadline. + /// If they stay until the deadline, they become doomed. + /// If they escape before, they're removed from this tracking. + /// + /// + /// Runtime state - not serialized. Entities can escape by leaving the core radius + /// before their deadline expires. + /// + [ViewVariables] + public Dictionary PendingDoom = new(); + + /// + /// Sound to play when an entity enters pending doom state (warning of imminent death). + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier? PendingDoomSound; + + /// + /// Whether to also gib organs when gibbing the body. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public bool GibOrgans = true; + + /// + /// Only gib entities matching this whitelist. If null, gibs all valid entities. + /// + /// + /// Typically set to MobState component to only affect living creatures. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist? Whitelist; + + /// + /// Whether to throw everything away from center after gibbing. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public bool ThrowOnGib = true; + + /// + /// Range of the throw effect after gibbing (in tiles). + /// + /// + /// Recommended range: 3.0 to 10.0. Determines how far gibs and nearby items scatter. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float ThrowRange = 4f; + + /// + /// Force multiplier for the throw effect. + /// + /// + /// Recommended range: 30.0 to 100.0. Higher values throw entities farther. + /// Force is scaled by entity mass and distance from center. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float ThrowForce = 50f; + + /// + /// Tracks the last sent danger state to avoid redundant appearance updates. + /// + /// + /// Runtime state - not serialized. Used to prevent calling SetData every frame + /// when the danger state hasn't changed. + /// + [ViewVariables] + public bool LastDangerState; + + // Reusable lists to avoid per-frame allocations + public readonly List PendingRemovalBuffer = new(); + public readonly List GibRemovalBuffer = new(); + public readonly List StaleEntityBuffer = new(); +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectGravityWellComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectGravityWellComponent.cs new file mode 100644 index 00000000000..788e4821c95 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectGravityWellComponent.cs @@ -0,0 +1,47 @@ +using Content.Shared.Whitelist; +using Robust.Shared.Serialization; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectGravityWellComponent : Component +{ + [DataField] + public EntityWhitelist? Whitelist; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Distance = 3f; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Radial = 10f; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public ZoneAnomalyEffectGravityWellGradient Gradient = ZoneAnomalyEffectGravityWellGradient.Default; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan PeriodTime; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan Period = TimeSpan.FromSeconds(0.5); + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Tangential = 0.001f; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public ZoneAnomalyEffectGravityWellMode Mode = ZoneAnomalyEffectGravityWellMode.Attract; +} + +[Serializable, NetSerializable] +public enum ZoneAnomalyEffectGravityWellGradient +{ + Default, + Linear, + ReversedLinear, +} + +[Serializable, NetSerializable] +public enum ZoneAnomalyEffectGravityWellMode +{ + Attract, + Repel, +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectLightArcComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectLightArcComponent.cs new file mode 100644 index 00000000000..ad86d59cf4d --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectLightArcComponent.cs @@ -0,0 +1,20 @@ +using Content.Shared.Whitelist; +using Robust.Shared.Prototypes; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectLightArcComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist Whitelist = new(); + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntProtoId Lighting = "SourceLightning"; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float ChargePercent = 0.2f; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Distance = 6f; +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectPlaySoundComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectPlaySoundComponent.cs new file mode 100644 index 00000000000..d87eeb55554 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectPlaySoundComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Audio; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectPlaySoundComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/_Stalker/Effects/springboard_blowout.ogg"); + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Range = 10f; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Volume; +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectPointLightComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectPointLightComponent.cs new file mode 100644 index 00000000000..e143b782afa --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectPointLightComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectPointLightComponent : Component +{ + +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectRandomTeleportComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectRandomTeleportComponent.cs new file mode 100644 index 00000000000..513c98c9612 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectRandomTeleportComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectRandomTeleportComponent : Component +{ + +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectSpawnComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectSpawnComponent.cs new file mode 100644 index 00000000000..f6690dfadbd --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectSpawnComponent.cs @@ -0,0 +1,13 @@ +using Content.Shared.Storage; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectSpawnComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public List Entry = new(); + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Offset = 2f; +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectStealthComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectStealthComponent.cs new file mode 100644 index 00000000000..6d1a1272889 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectStealthComponent.cs @@ -0,0 +1,37 @@ +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectStealthComponent : Component +{ + [DataField] + public float Idle = -0.5f; + + [DataField] + public float Activated = 0f; + + [DataField] + public float Charging = -0.5f; + + /// + /// Duration of the fade-out animation when entering Charging state (in seconds). + /// + [DataField] + public float ChargingFadeDuration = 0.5f; + + // Runtime state (not serialized) + + /// + /// Whether the entity is currently fading to charging visibility. + /// + public bool IsFading; + + /// + /// The visibility value when fade started. + /// + public float FadeStartVisibility; + + /// + /// The time when fade started. + /// + public TimeSpan FadeStartTime; +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectThrowComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectThrowComponent.cs new file mode 100644 index 00000000000..22ac0314b83 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyEffectThrowComponent.cs @@ -0,0 +1,19 @@ +using Content.Shared.Whitelist; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +[RegisterComponent] +public sealed partial class ZoneAnomalyEffectThrowComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist? Whitelist; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Distance = 10f; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Force = 10f; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float MinDistance; +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyStealthPulseComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyStealthPulseComponent.cs new file mode 100644 index 00000000000..a5b72dc33d0 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Components/ZoneAnomalyStealthPulseComponent.cs @@ -0,0 +1,51 @@ +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Components; + +/// +/// Pulses the stealth visibility during anomaly idle state. +/// Works with the existing Stealth system by modifying visibility values. +/// +[RegisterComponent] +public sealed partial class ZoneAnomalyStealthPulseComponent : Component +{ + /// + /// Minimum stealth visibility during idle pulse (0.0 to 1.0). + /// + [DataField] + public float MinVisibility = 0.3f; + + /// + /// Maximum stealth visibility during idle pulse (0.0 to 1.0). + /// + [DataField] + public float MaxVisibility = 0.7f; + + /// + /// Duration of one complete pulse cycle (min→max→min) in seconds. + /// + [DataField] + public float PulseDuration = 2.0f; + + /// + /// How often to update visibility in seconds. + /// Lower values = smoother animation but more server updates. + /// + [DataField] + public float UpdateInterval = 0.1f; + + // Runtime state (not serialized) + + /// + /// Next time to update the visibility. + /// + public TimeSpan NextUpdate; + + /// + /// Current position in the pulse cycle. + /// + public float PulseTime; + + /// + /// Whether the component is currently pulsing (only in Idle state). + /// + public bool IsPulsing; +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/Systems/SharedZoneAnomalyEffectRandomTeleportSystem.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Systems/SharedZoneAnomalyEffectRandomTeleportSystem.cs new file mode 100644 index 00000000000..a90cbe9aeb8 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/Systems/SharedZoneAnomalyEffectRandomTeleportSystem.cs @@ -0,0 +1,17 @@ +using Robust.Shared.Map; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Systems; + +public class SharedZoneAnomalyEffectRandomTeleportSystem : EntitySystem +{ + [Dependency] protected readonly SharedTransformSystem _transform = default!; + + public void TeleportEntity(EntityUid entity, EntityCoordinates coords, bool reParent = true) + { + _transform.SetCoordinates(entity, coords); + + var map = coords.GetMapUid(EntityManager); + if (map != null && reParent) + _transform.SetParent(entity, map.Value); + } +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Effects/ZoneAnomalyGibVisuals.cs b/Content.Shared/_Stalker/ZoneAnomaly/Effects/ZoneAnomalyGibVisuals.cs new file mode 100644 index 00000000000..57dbd5d7fe1 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Effects/ZoneAnomalyGibVisuals.cs @@ -0,0 +1,9 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects; + +[Serializable, NetSerializable] +public enum ZoneAnomalyGibVisuals +{ + Doomed, +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/InteractionDoAfterEvent.cs b/Content.Shared/_Stalker/ZoneAnomaly/InteractionDoAfterEvent.cs new file mode 100644 index 00000000000..0290de72cb9 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/InteractionDoAfterEvent.cs @@ -0,0 +1,17 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Network; +using Robust.Shared.Serialization; + + +namespace Content.Shared._Stalker.ZoneAnomaly +{ + [Serializable, NetSerializable] + public sealed partial class InteractionDoAfterEvent : SimpleDoAfterEvent { } +} + +/// +/// Raised when a ZoneAnomalyDestructor finishes interacting after a delay. +/// This event must be serializable so it can sync across client/server. +/// +//[Serializable, NetSerializable, DataDefinition] +//public sealed partial class InteractionDoAfterEvent : SimpleDoAfterEvent { } diff --git a/Content.Shared/_Stalker/ZoneAnomaly/SharedZoneAnomalyDetectorSystem.cs b/Content.Shared/_Stalker/ZoneAnomaly/SharedZoneAnomalyDetectorSystem.cs new file mode 100644 index 00000000000..bddd0ce7f3f --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/SharedZoneAnomalyDetectorSystem.cs @@ -0,0 +1,5 @@ +namespace Content.Shared._Stalker.ZoneAnomaly; + +public abstract class SharedZoneAnomalyDetectorSystem : EntitySystem +{ +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/SharedZoneAnomalySystem.cs b/Content.Shared/_Stalker/ZoneAnomaly/SharedZoneAnomalySystem.cs new file mode 100644 index 00000000000..dcf258fe2fa --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/SharedZoneAnomalySystem.cs @@ -0,0 +1,10 @@ +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Robust.Shared.Physics.Events; +using Robust.Shared.Timing; + +namespace Content.Shared._Stalker.ZoneAnomaly; + +public abstract class SharedZoneAnomalySystem : EntitySystem +{ + +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Systems/ZoneAnomalyEffectActivatorSystem.cs b/Content.Shared/_Stalker/ZoneAnomaly/Systems/ZoneAnomalyEffectActivatorSystem.cs new file mode 100644 index 00000000000..6877f458d6b --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Systems/ZoneAnomalyEffectActivatorSystem.cs @@ -0,0 +1,32 @@ +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Content.Shared.Whitelist; + +namespace Content.Shared._Stalker.ZoneAnomaly.Effects.Systems; + +public sealed class ZoneAnomalyEffectActivatorSystem : EntitySystem +{ + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly ZoneAnomalySystem _anomalySystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnActivate); + } + + private void OnActivate(Entity effect, ref ZoneAnomalyActivateEvent args) + { + var entities = _lookup.GetEntitiesInRange(Transform(effect).Coordinates, effect.Comp.Distance); + foreach (var entity in entities) + { + if(!_whitelist.IsWhitelistPass(effect.Comp.Whitelist, entity)) + continue; + + if (!TryComp(entity, out var anomaly)) + return; + + _anomalySystem.TryActivate((entity, anomaly)); + } + } +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Triggers/ZoneAnomalyTriggerCollideComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Triggers/ZoneAnomalyTriggerCollideComponent.cs new file mode 100644 index 00000000000..bb5b5ee18a7 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Triggers/ZoneAnomalyTriggerCollideComponent.cs @@ -0,0 +1,24 @@ +using Content.Shared.Whitelist; + +namespace Content.Shared._Stalker.ZoneAnomaly.Triggers; + +public abstract partial class ZoneAnomalyTriggerCollideComponent : Component +{ + /// + /// I don't hate working with fucking masks, fucking bullets go to hell. + /// + public readonly EntityWhitelist? BaseBlacklist = new() + { + Components = new [] + { + "Projectile", + "FishingLure", + }, + }; + + [DataField] + public EntityWhitelist? Whitelist; + + [DataField] + public EntityWhitelist? Blacklist; +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Triggers/ZoneAnomalyTriggerCollideSystem.cs b/Content.Shared/_Stalker/ZoneAnomaly/Triggers/ZoneAnomalyTriggerCollideSystem.cs new file mode 100644 index 00000000000..9d0ac8b43b2 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Triggers/ZoneAnomalyTriggerCollideSystem.cs @@ -0,0 +1,112 @@ +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared.Whitelist; +using Robust.Shared.Physics.Events; + +namespace Content.Shared._Stalker.ZoneAnomaly.Triggers; + +public sealed class ZoneAnomalyTriggerCollideSystem : EntitySystem +{ + [Dependency] private readonly ZoneAnomalySystem _anomaly = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartCollide); + SubscribeLocalEvent(OnEndCollide); + + SubscribeLocalEvent(OnEntityAdd); + SubscribeLocalEvent(OnEntityRemove); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var anomaly, out var trigger)) + { + if (trigger.InAnomaly.Count == 0) + continue; + + _anomaly.TryActivate((uid, anomaly), trigger.InAnomaly); + } + } + + private void OnEntityAdd(Entity trigger, ref ZoneAnomalyEntityAddEvent args) + { + if (!Validate(args.Entity, trigger, trigger.Comp)) + return; + + if (TryComp(trigger, out var anomalyComponent)) + _anomaly.TryActivate((trigger, anomalyComponent), args.Entity); + + trigger.Comp.InAnomaly.Add(args.Entity); + } + + private void OnEntityRemove(Entity trigger, ref ZoneAnomalyEntityRemoveEvent args) + { + if (!Validate(args.Entity, trigger, trigger.Comp)) + return; + + if (!trigger.Comp.InAnomaly.Contains(args.Entity)) + return; + + if (TryComp(trigger, out var anomalyComponent)) + _anomaly.TryActivate((trigger, anomalyComponent), args.Entity); + + trigger.Comp.InAnomaly.Remove(args.Entity); + } + + private void OnStartCollide(Entity trigger, ref StartCollideEvent args) + { + TryActivate(args.OtherEntity, trigger, trigger.Comp); + } + + private void OnEndCollide(Entity trigger, ref EndCollideEvent args) + { + TryActivate(args.OtherEntity, trigger, trigger.Comp); + } + + private void TryActivate(EntityUid target, EntityUid triggerUid, ZoneAnomalyTriggerCollideComponent component) + { + TryActivate(target, (triggerUid, component)); + } + + private void TryActivate(EntityUid target, Entity trigger) + { + if (!Validate(target, trigger)) + return; + + if (!TryComp(trigger, out var anomaly)) + return; + + _anomaly.TryActivate((trigger, anomaly), target); + } + + private bool Validate(EntityUid target, EntityUid triggerUid, ZoneAnomalyTriggerCollideComponent component) + { + return Validate(target, (triggerUid, component)); + } + + private bool Validate(EntityUid target, Entity trigger) + { + return ValidateWhitelist(target, trigger.Comp.Whitelist) && + ValidateBlacklist(target, trigger.Comp.Blacklist) && + ValidateBlacklist(target, trigger.Comp.BaseBlacklist); + } + + private bool ValidateWhitelist(EntityUid uid, EntityWhitelist? whitelist) + { + return whitelist is null || _whitelistSystem.IsWhitelistPass(whitelist, uid); + } + + private bool ValidateBlacklist(EntityUid uid, EntityWhitelist? blacklist) + { + if (blacklist is null) + return true; + + return _whitelistSystem.IsBlacklistFail(blacklist, uid); + } +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Triggers/ZoneAnomalyTriggerEndCollideComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Triggers/ZoneAnomalyTriggerEndCollideComponent.cs new file mode 100644 index 00000000000..3b336b28ab2 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Triggers/ZoneAnomalyTriggerEndCollideComponent.cs @@ -0,0 +1,6 @@ +namespace Content.Shared._Stalker.ZoneAnomaly.Triggers; + +[RegisterComponent] +public sealed partial class ZoneAnomalyTriggerEndCollideComponent : ZoneAnomalyTriggerCollideComponent +{ +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Triggers/ZoneAnomalyTriggerStartCollideComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Triggers/ZoneAnomalyTriggerStartCollideComponent.cs new file mode 100644 index 00000000000..24e2386a141 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Triggers/ZoneAnomalyTriggerStartCollideComponent.cs @@ -0,0 +1,6 @@ +namespace Content.Shared._Stalker.ZoneAnomaly.Triggers; + +[RegisterComponent] +public sealed partial class ZoneAnomalyTriggerStartCollideComponent : ZoneAnomalyTriggerCollideComponent +{ +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/Triggers/ZoneAnomalyUpdateTriggerCollideComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/Triggers/ZoneAnomalyUpdateTriggerCollideComponent.cs new file mode 100644 index 00000000000..67acfbf24f3 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/Triggers/ZoneAnomalyUpdateTriggerCollideComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Shared._Stalker.ZoneAnomaly.Triggers; + +[RegisterComponent] +public sealed partial class ZoneAnomalyUpdateTriggerCollideComponent : ZoneAnomalyTriggerCollideComponent +{ + [DataField] + public HashSet InAnomaly = new(); +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/ZoneAnomalyActivatorComponent.cs b/Content.Shared/_Stalker/ZoneAnomaly/ZoneAnomalyActivatorComponent.cs new file mode 100644 index 00000000000..6b07c2d55fe --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/ZoneAnomalyActivatorComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared._Stalker.ZoneAnomaly; + +[RegisterComponent] +public sealed partial class ZoneAnomalyActivatorComponent : Component +{ + +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/ZoneAnomalyState.cs b/Content.Shared/_Stalker/ZoneAnomaly/ZoneAnomalyState.cs new file mode 100644 index 00000000000..9169fd19a1e --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/ZoneAnomalyState.cs @@ -0,0 +1,12 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared._Stalker.ZoneAnomaly; + +[Serializable, NetSerializable] +public enum ZoneAnomalyState +{ + Idle, + Activated, + Charging, + Preparing, +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/ZoneAnomalySystem.cs b/Content.Shared/_Stalker/ZoneAnomaly/ZoneAnomalySystem.cs new file mode 100644 index 00000000000..1d24f5f247b --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/ZoneAnomalySystem.cs @@ -0,0 +1,165 @@ +using Content.Shared._Stalker.ZoneAnomaly.Components; +using Content.Shared._Stalker.ZoneAnomaly.Effects.Components; +using Content.Shared.Whitelist; +using Robust.Shared.Physics.Events; +using Robust.Shared.Timing; + +namespace Content.Shared._Stalker.ZoneAnomaly; + +public sealed class ZoneAnomalySystem : SharedZoneAnomalySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartCollide); + SubscribeLocalEvent(OnEndCollide); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var anomaly)) + { + switch (anomaly.State) + { + case ZoneAnomalyState.Idle: + break; + + case ZoneAnomalyState.Activated: + if (_timing.CurTime < anomaly.ActivationTime) + break; + + // Skip timer-based recharge for anomalies with gib effect + // (GibSystem will handle state transition after all targets are resolved) + if (HasComp(uid)) + break; + + Recharge((uid, anomaly)); + break; + + case ZoneAnomalyState.Charging: + if (_timing.CurTime < anomaly.ChargingTime) + break; + + CalmDown((uid, anomaly)); + break; + + case ZoneAnomalyState.Preparing: + if (_timing.CurTime < anomaly.PreparingTime) + break; + + Activate((uid, anomaly)); + break; + } + } + } + + private void OnStartCollide(Entity anomaly, ref StartCollideEvent args) + { + if (_whitelistSystem.IsBlacklistPass(anomaly.Comp.CollisionBlacklist, args.OtherEntity)) + return; + + TryAddEntity(anomaly, args.OtherEntity); + } + + private void OnEndCollide(Entity anomaly, ref EndCollideEvent args) + { + TryRemoveEntity(anomaly, args.OtherEntity); + } + + public bool TryActivate(Entity anomaly, EntityUid? trigger = null) + { + var list = new HashSet(); + if (trigger is { } item) + list.Add(item); + + return TryActivate(anomaly, list); + } + + public bool TryActivate(Entity anomaly, HashSet triggers) + { + if (anomaly.Comp.State != ZoneAnomalyState.Idle) + return false; + + anomaly.Comp.Triggers.UnionWith(triggers); + + if (anomaly.Comp.PreparingDelay.TotalSeconds == 0) + { + Activate(anomaly); + return true; + } + + anomaly.Comp.PreparingTime = _timing.CurTime + anomaly.Comp.PreparingDelay; + SetState(anomaly, ZoneAnomalyState.Preparing); + return true; + } + + public void Activate(Entity anomaly) + { + SetState(anomaly, ZoneAnomalyState.Activated); + + var ev = new ZoneAnomalyActivateEvent(anomaly, anomaly.Comp.Triggers); + RaiseLocalEvent(anomaly, ref ev); + + anomaly.Comp.Triggers.Clear(); + anomaly.Comp.ActivationTime = _timing.CurTime + anomaly.Comp.ActivationDelay; + } + + public bool TryRecharge(Entity anomaly, TimeSpan? delay = null) + { + Recharge(anomaly, delay); + return true; + } + + private void Recharge(Entity anomaly, TimeSpan? delay = null) + { + SetState(anomaly, ZoneAnomalyState.Charging); + + anomaly.Comp.ChargingTime = _timing.CurTime + (delay ?? anomaly.Comp.ChargingDelay); + } + + private void CalmDown(Entity anomaly) + { + SetState(anomaly, ZoneAnomalyState.Idle); + } + + private void SetState(Entity anomaly, ZoneAnomalyState state) + { + var previous = anomaly.Comp.State; + anomaly.Comp.State = state; + + var ev = new ZoneAnomalyChangedState(anomaly, state, previous); + RaiseLocalEvent(anomaly, ref ev); + + _appearance.SetData(anomaly, ZoneAnomalyVisuals.Layer, state); + } + + private void TryAddEntity(Entity anomaly, EntityUid uid) + { + if (anomaly.Comp.InAnomaly.Contains(uid)) + return; + + anomaly.Comp.InAnomaly.Add(uid); + + var ev = new ZoneAnomalyEntityAddEvent(anomaly, uid); + RaiseLocalEvent(anomaly, ref ev); + } + + public void TryRemoveEntity(Entity anomaly, EntityUid uid) + { + if (!anomaly.Comp.InAnomaly.Contains(uid)) + return; + + anomaly.Comp.InAnomaly.Remove(uid); + + var ev = new ZoneAnomalyEntityRemoveEvent(anomaly, uid); + RaiseLocalEvent(anomaly, ref ev); + } +} diff --git a/Content.Shared/_Stalker/ZoneAnomaly/ZoneAnomalyVisuals.cs b/Content.Shared/_Stalker/ZoneAnomaly/ZoneAnomalyVisuals.cs new file mode 100644 index 00000000000..c46aa64b380 --- /dev/null +++ b/Content.Shared/_Stalker/ZoneAnomaly/ZoneAnomalyVisuals.cs @@ -0,0 +1,9 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared._Stalker.ZoneAnomaly; + +[Serializable, NetSerializable] +public enum ZoneAnomalyVisuals +{ + Layer, +} diff --git a/Resources/Audio/_Lua/Effects/Vortex/blowout.ogg b/Resources/Audio/_Lua/Effects/Vortex/blowout.ogg new file mode 100644 index 00000000000..0d6bae6891a Binary files /dev/null and b/Resources/Audio/_Lua/Effects/Vortex/blowout.ogg differ diff --git a/Resources/Audio/_Lua/Effects/Vortex/idle.ogg b/Resources/Audio/_Lua/Effects/Vortex/idle.ogg new file mode 100644 index 00000000000..7cb1b5bc090 Binary files /dev/null and b/Resources/Audio/_Lua/Effects/Vortex/idle.ogg differ diff --git a/Resources/Audio/_Lua/Effects/acidic_active.ogg b/Resources/Audio/_Lua/Effects/acidic_active.ogg new file mode 100644 index 00000000000..68a7c8c40bc Binary files /dev/null and b/Resources/Audio/_Lua/Effects/acidic_active.ogg differ diff --git a/Resources/Audio/_Lua/Effects/acidic_idle.ogg b/Resources/Audio/_Lua/Effects/acidic_idle.ogg new file mode 100644 index 00000000000..600134e7ac5 Binary files /dev/null and b/Resources/Audio/_Lua/Effects/acidic_idle.ogg differ diff --git a/Resources/Audio/_Lua/Effects/anomaly_gravy_idle.ogg b/Resources/Audio/_Lua/Effects/anomaly_gravy_idle.ogg new file mode 100644 index 00000000000..87deea0aa26 Binary files /dev/null and b/Resources/Audio/_Lua/Effects/anomaly_gravy_idle.ogg differ diff --git a/Resources/Audio/_Lua/Effects/attributions.yml b/Resources/Audio/_Lua/Effects/attributions.yml new file mode 100644 index 00000000000..cfe72738d48 --- /dev/null +++ b/Resources/Audio/_Lua/Effects/attributions.yml @@ -0,0 +1,31 @@ +# Ported from docs/stalker (Stalker fork) for STAnomaly / ZoneAnomaly systems +- files: + - springboard_blowout.ogg + - hush.ogg + - acidic_active.ogg + - acidic_idle.ogg + - anomaly_gravy_idle.ogg + - electra_blast.ogg + - electra_idle.ogg + - emp_activation.ogg + - fireball_idle.ogg + - sear.ogg + license: "Ported from Stalker" + copyright: "Ported from docs/stalker/Resources/Audio/_Stalker/Effects for sector-frontier-14 _Lua" + source: "Stalker fork / docs/stalker" + +- files: + - glass_shards_passthrough.ogg + - glass_hit_01.ogg + - glass_hit_02.ogg + - glass_hit_03.ogg + license: "Ported from Stalker EN" + copyright: "Ported from docs/stalker/Resources/Audio/_Stalker_EN/Effects for sector-frontier-14 _Lua" + source: "Stalker fork / docs/stalker" + +- files: + - Vortex/blowout.ogg + - Vortex/idle.ogg + license: "Ported from Stalker EN" + copyright: "Ported from docs/stalker/Resources/Audio/_Stalker_EN/Anomalies/Vortex for sector-frontier-14 _Lua" + source: "Stalker fork / docs/stalker" diff --git a/Resources/Audio/_Lua/Effects/electra_blast.ogg b/Resources/Audio/_Lua/Effects/electra_blast.ogg new file mode 100644 index 00000000000..0831ce68771 Binary files /dev/null and b/Resources/Audio/_Lua/Effects/electra_blast.ogg differ diff --git a/Resources/Audio/_Lua/Effects/electra_idle.ogg b/Resources/Audio/_Lua/Effects/electra_idle.ogg new file mode 100644 index 00000000000..54e40284c7e Binary files /dev/null and b/Resources/Audio/_Lua/Effects/electra_idle.ogg differ diff --git a/Resources/Audio/_Lua/Effects/emp_activation.ogg b/Resources/Audio/_Lua/Effects/emp_activation.ogg new file mode 100644 index 00000000000..0b7795876ee Binary files /dev/null and b/Resources/Audio/_Lua/Effects/emp_activation.ogg differ diff --git a/Resources/Audio/_Lua/Effects/fireball_idle.ogg b/Resources/Audio/_Lua/Effects/fireball_idle.ogg new file mode 100644 index 00000000000..89c56562b2d Binary files /dev/null and b/Resources/Audio/_Lua/Effects/fireball_idle.ogg differ diff --git a/Resources/Audio/_Lua/Effects/glass_hit_01.ogg b/Resources/Audio/_Lua/Effects/glass_hit_01.ogg new file mode 100644 index 00000000000..868b807e057 Binary files /dev/null and b/Resources/Audio/_Lua/Effects/glass_hit_01.ogg differ diff --git a/Resources/Audio/_Lua/Effects/glass_hit_02.ogg b/Resources/Audio/_Lua/Effects/glass_hit_02.ogg new file mode 100644 index 00000000000..02945266c70 Binary files /dev/null and b/Resources/Audio/_Lua/Effects/glass_hit_02.ogg differ diff --git a/Resources/Audio/_Lua/Effects/glass_hit_03.ogg b/Resources/Audio/_Lua/Effects/glass_hit_03.ogg new file mode 100644 index 00000000000..ea12b319dc7 Binary files /dev/null and b/Resources/Audio/_Lua/Effects/glass_hit_03.ogg differ diff --git a/Resources/Audio/_Lua/Effects/glass_shards_passthrough.ogg b/Resources/Audio/_Lua/Effects/glass_shards_passthrough.ogg new file mode 100644 index 00000000000..dc789600715 Binary files /dev/null and b/Resources/Audio/_Lua/Effects/glass_shards_passthrough.ogg differ diff --git a/Resources/Audio/_Lua/Effects/hush.ogg b/Resources/Audio/_Lua/Effects/hush.ogg new file mode 100644 index 00000000000..083046934d7 Binary files /dev/null and b/Resources/Audio/_Lua/Effects/hush.ogg differ diff --git a/Resources/Audio/_Lua/Effects/sear.ogg b/Resources/Audio/_Lua/Effects/sear.ogg new file mode 100644 index 00000000000..9e052914fda Binary files /dev/null and b/Resources/Audio/_Lua/Effects/sear.ogg differ diff --git a/Resources/Audio/_Lua/Effects/springboard_blowout.ogg b/Resources/Audio/_Lua/Effects/springboard_blowout.ogg new file mode 100644 index 00000000000..a3624f760d0 Binary files /dev/null and b/Resources/Audio/_Lua/Effects/springboard_blowout.ogg differ diff --git a/Resources/Locale/ru-RU/_Lua/reagents/anomaly.ftl b/Resources/Locale/ru-RU/_Lua/reagents/anomaly.ftl new file mode 100644 index 00000000000..64d15bcc583 --- /dev/null +++ b/Resources/Locale/ru-RU/_Lua/reagents/anomaly.ftl @@ -0,0 +1,2 @@ +reagent-name-absolute-absorber = Абсолютный поглотитель +reagent-desc-absolute-absorber = Опасное кислотное вещество, обнаруженное в аномальных зонах. diff --git a/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml b/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml index a2963ceacdb..9a402d04d72 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml @@ -264,6 +264,7 @@ - type: Physics bodyType: Static - type: AnomalyGenerator + spawnerPrototype: RandomLuaAnomalySpawner generatingSound: path: /Audio/Machines/anomaly_generate.ogg generatingFinishedSound: diff --git a/Resources/Prototypes/Entities/World/Debris/asteroids.yml b/Resources/Prototypes/Entities/World/Debris/asteroids.yml index bc64c6a3892..563cc89e76c 100644 --- a/Resources/Prototypes/Entities/World/Debris/asteroids.yml +++ b/Resources/Prototypes/Entities/World/Debris/asteroids.yml @@ -47,9 +47,6 @@ - id: WallRockBananium prob: 0.02 orGroup: rock - - id: WallRockArtifactFragment - prob: 0.01 - orGroup: rock # Lua start - id: SalvageMobSpawner prob: 0.001 diff --git a/Resources/Prototypes/Procedural/Magnet/asteroid_ore_gens.yml b/Resources/Prototypes/Procedural/Magnet/asteroid_ore_gens.yml index 6c25c885e43..130c16cff44 100644 --- a/Resources/Prototypes/Procedural/Magnet/asteroid_ore_gens.yml +++ b/Resources/Prototypes/Procedural/Magnet/asteroid_ore_gens.yml @@ -9,7 +9,6 @@ OreSilver: 0.25 OrePlasma: 0.20 OreUranium: 0.20 - OreArtifactFragment: 0.10 OreBananium: 0.10 - type: oreDunGen @@ -101,13 +100,3 @@ count: 6 minGroupSize: 3 maxGroupSize: 6 - -- type: oreDunGen - id: OreArtifactFragment - # replacement: AsteroidRock # upstream#33105 - entityMask: # upstream#33105 - - AsteroidRock # upstream#33105 - entity: AsteroidRockArtifactFragment - count: 5 - minGroupSize: 1 - maxGroupSize: 2 \ No newline at end of file diff --git a/Resources/Prototypes/Procedural/biome_ore_templates.yml b/Resources/Prototypes/Procedural/biome_ore_templates.yml index ae033230b6f..ec6a27c54a5 100644 --- a/Resources/Prototypes/Procedural/biome_ore_templates.yml +++ b/Resources/Prototypes/Procedural/biome_ore_templates.yml @@ -140,7 +140,7 @@ WallRockChromite: WallRockChromiteArtifactFragment WallRockSand: WallRockSandArtifactFragment WallRockSnow: WallRockSnowArtifactFragment - maxCount: 6 + maxCount: 0 # Disable minGroupSize: 1 maxGroupSize: 2 radius: 4 diff --git a/Resources/Prototypes/Procedural/biome_ore_templates_low.yml b/Resources/Prototypes/Procedural/biome_ore_templates_low.yml index 3b0e2424469..62e3dcd1866 100644 --- a/Resources/Prototypes/Procedural/biome_ore_templates_low.yml +++ b/Resources/Prototypes/Procedural/biome_ore_templates_low.yml @@ -140,7 +140,7 @@ WallRockChromite: WallRockChromiteArtifactFragment WallRockSand: WallRockSandArtifactFragment WallRockSnow: WallRockSnowArtifactFragment - maxCount: 3 + maxCount: 0 # Disable minGroupSize: 1 maxGroupSize: 2 radius: 4 diff --git a/Resources/Prototypes/Procedural/salvage_rewards.yml b/Resources/Prototypes/Procedural/salvage_rewards.yml index 0471cf085c5..e2207a1c3f9 100644 --- a/Resources/Prototypes/Procedural/salvage_rewards.yml +++ b/Resources/Prototypes/Procedural/salvage_rewards.yml @@ -12,7 +12,6 @@ CrateFoodSoftdrinks: 0.25 CrateFunInstrumentsVariety: 0.25 CrateSalvageEquipment: 0.25 - RandomArtifactSpawner: 0.25 # weighted down since it sells for a lot NuclearBombKeg: 0.1 # money diff --git a/Resources/Prototypes/Procedural/vgroid.yml b/Resources/Prototypes/Procedural/vgroid.yml index 4cb97e4df93..5dbf7a8f111 100644 --- a/Resources/Prototypes/Procedural/vgroid.yml +++ b/Resources/Prototypes/Procedural/vgroid.yml @@ -93,14 +93,6 @@ count: 35 minGroupSize: 4 maxGroupSize: 8 - - !type:OreDunGen - # replacement: IronRock # upstream#33105 - entityMask: # upstream#33105 - - IronRock # upstream#33105 - entity: IronRockArtifactFragment - count: 25 - minGroupSize: 1 - maxGroupSize: 3 - !type:OreDunGen # replacement: IronRock # upstream#33105 entityMask: # upstream#33105 diff --git a/Resources/Prototypes/_Lua/Entities/Effects/lightning.yml b/Resources/Prototypes/_Lua/Entities/Effects/lightning.yml new file mode 100644 index 00000000000..a33fdbb80a4 --- /dev/null +++ b/Resources/Prototypes/_Lua/Entities/Effects/lightning.yml @@ -0,0 +1,54 @@ +# Lightning prototypes for anomaly electrical effects +- type: entity + name: source lightning + id: SourceLightning + parent: ChargedLightning + categories: [ HideSpawnMenu ] + components: + - type: Sprite + sprite: /Textures/Effects/lightning.rsi + drawdepth: Effects + layers: + - state: blue_lightning + shader: unshaded + - type: Electrified + requirePower: false + shockDamage: 150 + shockTime: 0 + - type: Lightning + canArc: true + lightningPrototype: SourceLightning + - type: PointLight + enabled: true + color: "#f2fdff" + radius: 3.5 + softness: 1 + autoRot: true + castShadows: false + +- type: entity + name: garland lightning + id: GarlandLightning + parent: ChargedLightning + categories: [ HideSpawnMenu ] + components: + - type: Sprite + sprite: /Textures/Effects/lightning.rsi + drawdepth: Effects + layers: + - state: blue_lightning + shader: unshaded + - type: Electrified + requirePower: false + shockDamage: 30 + shockTime: 0 + - type: Lightning + canArc: false + lightningPrototype: GarlandLightning + - type: PointLight + enabled: true + color: "#f2fdff" + radius: 1.0 + softness: 1 + autoRot: true + castShadows: false diff --git a/Resources/Prototypes/_Lua/Entities/Markers/Spawners/Random/anomaly.yml b/Resources/Prototypes/_Lua/Entities/Markers/Spawners/Random/anomaly.yml new file mode 100644 index 00000000000..1f6c2a8b38d --- /dev/null +++ b/Resources/Prototypes/_Lua/Entities/Markers/Spawners/Random/anomaly.yml @@ -0,0 +1,40 @@ +# Spawner for MachineAnomalyGenerator / MachineMiniAnomalyGenerator: Lua (Zone + ST) anomalies only +- type: entity + id: RandomLuaAnomalySpawner + name: random Lua anomaly spawner + parent: MarkerBase + components: + - type: Sprite + layers: + - state: red + - sprite: Structures/Specific/anomaly.rsi + state: anom1 + - type: RandomSpawner + chance: 1 + prototypes: + # ZoneAnomaly - chemical + - ZoneAnomalyKissel + - ZoneAnomalyGazirovka + - ZoneAnomalySmoke + - ZoneAnomalyBeer + # ZoneAnomaly - electrical + - ZoneAnomalyElectra + - ZoneAnomalyEMP + - ZoneAnomalyGarland + - ZoneAnomalySource + # ZoneAnomaly - gravity + - ZoneAnomalyGravi + - ZoneAnomalyGrabber + - ZoneAnomalySpringboard + - ZoneAnomalyPulse + - ZoneAnomalyVortex + - ZoneAnomalyGlassShards + # ZoneAnomaly - thermal + - ZoneAnomalyJarka + - ZoneAnomalyJarkaStrong + - ZoneAnomalySteam + - ZoneAnomalySol + # ZoneAnomaly - psy / spatial + - ZoneAnomalyLamp + - ZoneAnomalyRift + offset: 0.15 diff --git a/Resources/Prototypes/_Lua/Entities/Markers/Spawners/Random/wallrock.yml b/Resources/Prototypes/_Lua/Entities/Markers/Spawners/Random/wallrock.yml index b6193129f67..a9b5cc22a43 100644 --- a/Resources/Prototypes/_Lua/Entities/Markers/Spawners/Random/wallrock.yml +++ b/Resources/Prototypes/_Lua/Entities/Markers/Spawners/Random/wallrock.yml @@ -53,37 +53,31 @@ - WallRockPlasma - WallRockUranium - WallRockBananium - - WallRockArtifactFragment - WallRockAndesiteGold - WallRockAndesiteSilver - WallRockAndesitePlasma - WallRockAndesiteUranium - WallRockAndesiteBananium - - WallRockAndesiteArtifactFragment - WallRockBasaltGold - WallRockBasaltSilver - WallRockBasaltPlasma - WallRockBasaltUranium - WallRockBasaltBananium - - WallRockBasaltArtifactFragment - WallRockChromiteGold - WallRockChromiteSilver - WallRockChromitePlasma - WallRockChromiteUranium - WallRockChromiteBananium - - WallRockChromiteArtifactFragment - WallRockSandGold - WallRockSandSilver - WallRockSandPlasma - WallRockSandUranium - WallRockSandBananium - - WallRockSandArtifactFragment - WallRockSnowGold - WallRockSnowSilver - WallRockSnowPlasma - WallRockSnowUranium - WallRockSnowBananium - - WallRockSnowArtifactFragment rareChance: 0.2 superRarePrototypes: - WallRockDiamond diff --git a/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/base.yml b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/base.yml new file mode 100644 index 00000000000..ac72dd9a2b0 --- /dev/null +++ b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/base.yml @@ -0,0 +1,30 @@ +- type: entity + parent: BaseAnomaly + id: ZoneAnomalyBase + name: anomaly + suffix: Stalker, Anomaly + abstract: true + components: + - type: Clickable + - type: InteractionOutline + - type: Physics + bodyType: Static + fixedRotation: true + - type: ZoneAnomaly + - type: Appearance + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.45 + hard: false + mask: + - MobMask + layer: + - MobLayer + - LowImpassable + # ZoneArtifactDetectorTarget - not ported yet + # ZoneArtifactSpawner - not ported yet + - type: STAnomalyTips + # ESViewconeOccludable - not ported yet diff --git a/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/chemical.yml b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/chemical.yml new file mode 100644 index 00000000000..4eabb1e9081 --- /dev/null +++ b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/chemical.yml @@ -0,0 +1,96 @@ +- type: entity + parent: ZoneAnomalyBase + id: ZoneAnomalyBaseChemical + abstract: true + components: + - type: PointLight + radius: 3 + energy: 3 + color: "#c0f133" + castShadows: false + - type: AmbientSound + enabled: true + volume: -18 + range: 10 + sound: + path: /Audio/_Lua/Effects/acidic_idle.ogg + - type: STAnomalyTips + icon: + sprite: /Textures/_Lua/Interface/Overlays/anomaly_tips.rsi + state: toxic + +- type: entity + id: ZoneAnomalyKissel + parent: ZoneAnomalyBaseChemical + suffix: Stalker, Kissel, Anomaly + components: + - type: ZoneAnomalyUpdateTriggerCollide + - type: ZoneAnomalyEffectDamage + damage: + types: + Caustic: 10 + - type: ZoneAnomalyEffectDestroy + whitelist: + tags: + - STBolt + - type: ZoneAnomaly + detectedLevel: 0 + preparingDelay: 0 + activationDelay: 0 + chargeTime: 0.05 + - type: Sprite + sprite: _Lua/Objects/Other/Anomalys/kissel.rsi + layers: + - state: idle + shader: unshaded + +- type: entity + parent: ZoneAnomalyBaseChemical + id: ZoneAnomalyGazirovka + suffix: Stalker, Gazirovka, Anomaly + components: + - type: ZoneAnomalyUpdateTriggerCollide + - type: ZoneAnomalyEffectDamage + damage: + types: + Caustic: 90 + - type: ZoneAnomalyEffectDestroy + whitelist: + tags: + - STBolt + # - type: ZoneAnomalyEffectSpawn # GazirovkaActiveEffect not ported yet + # entry: + # - id: GazirovkaActiveEffect + # amount: 3 + - type: ZoneAnomalyEffectPlaySound + sound: /Audio/_Lua/Effects/acidic_active.ogg + - type: ZoneAnomaly + detectedLevel: 3 + activationDelay: 0 + preparingDelay: 0 + chargeTime: 3 + +- type: entity + parent: ZoneAnomalyBaseChemical + id: ZoneAnomalySmoke + suffix: Stalker, Smoke, Anomaly + components: + - type: PointLight + radius: 3 + energy: 3 + color: "Yellow" + castShadows: false + - type: ZoneAnomalyUpdateTriggerCollide + blacklist: + components: + - Smoke + - type: ZoneAnomalyEffectFoam + reagent: AbsoluteAbsorber + reagentAmount: 20 + range: 20 + duration: 30 + - type: ZoneAnomaly + detectedLevel: 3 + activationDelay: 0 + preparingDelay: 0 + chargeTime: 10 diff --git a/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/chemical_en.yml b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/chemical_en.yml new file mode 100644 index 00000000000..18c77b60d61 --- /dev/null +++ b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/chemical_en.yml @@ -0,0 +1,42 @@ +# Beer anomaly - partially ported +# STBaseToxic, STChemicalsDelivery, SyncSprite, STAcidicEffect not ported yet +# Using only ZoneAnomalyBaseChemical as parent + +- type: entity + id: ZoneAnomalyBeer + name: Beer + parent: ZoneAnomalyBaseChemical + suffix: Stalker, Kissel, Anomaly, Beer + components: + - type: PointLight + radius: 3 + energy: 3 + color: "Yellow" + castShadows: false + - type: ZoneAnomalyUpdateTriggerCollide + - type: ZoneAnomalyEffectDamage + damage: + types: + Caustic: 2 + - type: ZoneAnomalyEffectDestroy + whitelist: + tags: + - STBolt + - type: Sprite + sprite: _Lua/Objects/Other/Anomalys/beer.rsi + layers: + - state: idle + shader: unshaded + # STChemicalsDelivery - not ported yet + # - type: STChemicalsDelivery + # reagent: Beer + # amount: 20 + # deliverSound: /Audio/Items/hiss.ogg + # entry: + # id: STAcidicEffect + # amount: 2 + - type: ZoneAnomaly + detectedLevel: 0 + preparingDelay: 0 + activationDelay: 0 + chargeTime: 0.05 diff --git a/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/electrical.yml b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/electrical.yml new file mode 100644 index 00000000000..a006b20ed8a --- /dev/null +++ b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/electrical.yml @@ -0,0 +1,220 @@ +- type: entity + parent: ZoneAnomalyBase + id: ZoneAnomalyBaseElectrical + abstract: true + components: + - type: STAnomalyTips + icon: + sprite: /Textures/_Lua/Interface/Overlays/anomaly_tips.rsi + state: electric + +- type: entity + parent: ZoneAnomalyBase + id: ZoneAnomalyElectra + suffix: Stalker, Electra, Anomaly + components: + - type: ZoneAnomalyUpdateTriggerCollide + - type: ZoneAnomalyEffectDamage + damage: + types: + Shock: 80 + - type: ZoneAnomalyEffectDestroy + whitelist: + tags: + - STBolt + - type: ZoneAnomalyEffectPlaySound + sound: /Audio/_Lua/Effects/electra_blast.ogg + - type: ZoneAnomaly + detectedLevel: 0 + preparingDelay: 0 + activationDelay: 0.3 + chargeTime: 2 + - type: Sprite + sprite: _Lua/Objects/Anomalies/electra.rsi + layers: + - state: idle + map: ["base"] + shader: unshaded + - type: GenericVisualizer + visuals: + enum.ZoneAnomalyVisuals.Layer: + base: + Activated: { state: active } + Charging: { state: idle } + Idle: { state: idle } + - type: AmbientSound + enabled: true + volume: -5 + range: 8 + sound: + path: /Audio/_Lua/Effects/electra_idle.ogg + +- type: entity + parent: ZoneAnomalyBase + id: ZoneAnomalyEMP + name: EMP anomaly + suffix: Stalker, EMP, Anomaly + components: + - type: ZoneAnomalyUpdateTriggerCollide + - type: ZoneAnomalyEffectDamage + damage: + types: + Shock: 15 + - type: ZoneAnomalyEffectDestroy + whitelist: + tags: + - STBolt + - type: ZoneAnomalyEffectPlaySound + sound: /Audio/_Lua/Effects/emp_activation.ogg + - type: ZoneAnomalyEffectDischarge + - type: ZoneAnomaly + detectedLevel: 0 + preparingDelay: 1 + activationDelay: 0.75 + chargeTime: 5 + - type: Sprite + sprite: _Lua/Objects/Anomalies/emp.rsi + layers: + - state: idle + map: ["base"] + shader: unshaded + - type: GenericVisualizer + visuals: + enum.ZoneAnomalyVisuals.Layer: + base: + Activated: { state: active } + Charging: { state: idle } + Idle: { state: idle } + - type: AmbientSound + enabled: true + volume: -40 + range: 20 + sound: + path: /Audio/_Lua/Effects/electra_idle.ogg + - type: ZoneAnomalyEffectFlash + range: 5 + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 4 + hard: false + mask: + - MobMask + layer: + - MobLayer + - LowImpassable + +- type: entity + parent: ZoneAnomalyBaseElectrical + id: ZoneAnomalyGarland + suffix: Stalker, Garland, Anomaly + components: + - type: Tag + tags: + - AnomalyGarlandActivationTarget + - HideContextMenu + - type: ZoneAnomalyTriggerStartCollide + - type: ZoneAnomalyTriggerEndCollide + - type: ZoneAnomalyEffectLightArc + whitelist: + components: + - ZoneAnomalyActivator + - Battery + tags: + - AnomalyGarlandActivationTarget + lighting: GarlandLightning + chargePercent: 0.1 + distance: 3 + - type: ZoneAnomalyEffectActivator + whitelist: + tags: + - AnomalyGarlandActivationTarget + distance: 8 + - type: ZoneAnomalyEffectPlaySound + sound: /Audio/_Lua/Effects/electra_blast.ogg + - type: ZoneAnomaly + detectedLevel: 3 + preparingDelay: 0 + activationDelay: 1 + chargeTime: 6 + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.35 + density: 50 + hard: false + mask: + - MobMask + layer: + - MobLayer + - LowImpassable + - type: Sprite + sprite: _Lua/Objects/Other/Anomalys/garland.rsi + layers: + - state: idle + shader: unshaded + - type: AmbientSound + enabled: true + volume: -7 + range: 2 + sound: + path: /Audio/_Lua/Effects/electra_idle.ogg + +- type: entity + parent: ZoneAnomalyBaseElectrical + id: ZoneAnomalySource + suffix: Stalker, Source, Anomaly + components: + - type: ZoneAnomalyTriggerStartCollide + - type: ZoneAnomalyTriggerEndCollide + - type: ZoneAnomalyEffectLightArc + whitelist: + components: + - ZoneAnomalyActivator + - Battery + lighting: SourceLightning + chargePercent: 1 + distance: 6 + - type: ZoneAnomalyEffectPlaySound + sound: /Audio/_Lua/Effects/electra_blast.ogg + - type: ZoneAnomaly + detectedLevel: 1 + preparingDelay: 0 + activationDelay: 3 + chargeTime: 5 + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 1.5 + density: 50 + hard: false + mask: + - MobMask + layer: + - MobLayer + - LowImpassable + - type: Sprite + sprite: _Lua/Objects/Other/Anomalys/source.rsi + layers: + - state: idle + map: ["base"] + shader: unshaded + - type: GenericVisualizer + visuals: + enum.ZoneAnomalyVisuals.Layer: + base: + Activated: { state: active } + Charging: { state: charge } + Idle: { state: idle } + - type: AmbientSound + enabled: true + volume: -10 + range: 3 + sound: + path: /Audio/_Lua/Effects/electra_idle.ogg diff --git a/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/gravity.yml b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/gravity.yml new file mode 100644 index 00000000000..bf3e5b2fd9e --- /dev/null +++ b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/gravity.yml @@ -0,0 +1,180 @@ +- type: entity + parent: ZoneAnomalyBase + id: ZoneAnomalyBaseGravity + abstract: true + components: + - type: STAnomalyTips + icon: + sprite: /Textures/_Lua/Interface/Overlays/anomaly_tips.rsi + state: gravity + +- type: entity + id: ZoneAnomalyGravi + parent: ZoneAnomalyBaseGravity + suffix: Stalker, Gravi, Anomaly + components: + - type: ZoneAnomalyTriggerStartCollide + - type: ZoneAnomalyEffectDamage + damageUpdate: true + damage: + types: + Compression: 0.5 + - type: ZoneAnomalyEffectGravityWell + distance: 1.5 + period: 0.005 + radial: 40 + tangential: 0.1 + gradient: Linear + - type: Stealth + - type: ZoneAnomalyEffectStealth + idle: -0.3 + activated: 1 + - type: ZoneAnomaly + detectedLevel: 1 + activationDelay: 10 + preparingDelay: 0 + chargeTime: 5 + - type: Sprite + sprite: _Lua/Objects/Anomalies/gravi.rsi + layers: + - state: idle + map: ["base"] + - type: GenericVisualizer + visuals: + enum.ZoneAnomalyVisuals.Layer: + base: + Activated: { state: active } + Charging: { state: charging } + Idle: { state: idle } + - type: AmbientSound + enabled: true + volume: -5 + range: 4 + sound: + path: /Audio/_Lua/Effects/anomaly_gravy_idle.ogg + +- type: entity + id: ZoneAnomalyGrabber + parent: ZoneAnomalyBaseGravity + suffix: Stalker, Grabber, Anomaly + components: + - type: ZoneAnomalyTriggerStartCollide + - type: ZoneAnomalyEffectDamage + damageUpdate: true + damage: + types: + Compression: 2 + - type: ZoneAnomalyEffectGravityWell + distance: 1.5 + period: 0.005 + radial: 10 + tangential: 0.1 + gradient: Linear + - type: Stealth + - type: ZoneAnomalyEffectStealth + idle: -0.3 + activated: 1 + - type: ZoneAnomaly + detectedLevel: 2 + activationDelay: 12 + preparingDelay: 0 + chargeTime: 7 + - type: Sprite + sprite: _Lua/Objects/Anomalies/grabber.rsi + layers: + - state: idle + map: ["base"] + - type: GenericVisualizer + visuals: + enum.ZoneAnomalyVisuals.Layer: + base: + Activated: { state: active } + Charging: { state: charging } + Idle: { state: idle } + - type: AmbientSound + enabled: true + volume: -5 + range: 8 + sound: + path: /Audio/_Lua/Effects/anomaly_gravy_idle.ogg + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 1.5 + density: 50 + hard: false + mask: + - MobMask + layer: + - MobLayer + - LowImpassable + +- type: entity + id: ZoneAnomalySpringboard + parent: ZoneAnomalyBaseGravity + suffix: Stalker, Springboard, Anomaly + components: + - type: ZoneAnomalyUpdateTriggerCollide + - type: ZoneAnomalyEffectDamage + damage: + types: + Compression: 20 + - type: ZoneAnomalyEffectThrow + force: 40 + distance: 10 + - type: ZoneAnomalyEffectPlaySound + sound: /Audio/_Lua/Effects/springboard_blowout.ogg + # - type: ZoneAnomalyEffectSpawn # SpringboardActiveEffect not ported yet + # entry: + # - id: SpringboardActiveEffect + # amount: 3 + - type: Stealth + - type: ZoneAnomalyEffectStealth + idle: -0.3 + activated: -0.3 + - type: ZoneAnomaly + detectedLevel: 0 + activationDelay: 0 + preparingDelay: 0 + chargeTime: 3 + - type: Sprite + sprite: _Lua/Objects/Anomalies/springobard.rsi + layers: + - state: idle + - type: AmbientSound + enabled: true + volume: -2 + range: 8 + sound: + path: /Audio/_Lua/Effects/anomaly_gravy_idle.ogg + +- type: entity + parent: ZoneAnomalyBaseGravity + id: ZoneAnomalyPulse + suffix: Stalker, Pulse, Anomaly + components: + - type: ZoneAnomalyUpdateTriggerCollide + - type: ZoneAnomalyEffectDamage + damage: + types: + Compression: 80 + - type: ZoneAnomalyEffectThrow + force: 100 + distance: 70 + - type: ZoneAnomalyEffectPlaySound + sound: /Audio/_Lua/Effects/springboard_blowout.ogg + - type: Stealth + - type: ZoneAnomalyEffectStealth + idle: -1 + activated: 1 + - type: ZoneAnomaly + detectedLevel: 4 + activationDelay: 1 + preparingDelay: 0 + chargeTime: 5 + - type: Sprite + sprite: _Lua/Objects/Anomalies/springobard.rsi + layers: + - state: idle diff --git a/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/gravity_en.yml b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/gravity_en.yml new file mode 100644 index 00000000000..c39db381a95 --- /dev/null +++ b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/gravity_en.yml @@ -0,0 +1,113 @@ +- type: entity + parent: ZoneAnomalyBase + id: ZoneAnomalyGlassShards + name: glass shards + suffix: Stalker, GlassShards, Anomaly + description: A field of nearly invisible glass shards that cut through anyone running carelessly. + components: + - type: STAnomalyTriggerGroupsStateTransition + - type: STAnomalyTriggerStartCollide + blacklist: + components: + - Projectile + - type: STAnomalyTriggerStartCollideSprinting + sprintingTriggerGroup: Sprinting + - type: STAnomalyTriggerStartCollideBoots + noBootsSprintingGroup: NoBootsSprinting + noBootsWalkingGroup: NoBootsWalking + slotName: shoes + - type: STAnomalyTriggerTimeDelay + options: + ActiveBase: + group: TimeDelayActive + delay: 2 + ActiveDouble: + group: TimeDelayActive + delay: 2 + - type: STAnomaly + state: Idle + states: + Idle: + - group: NoBootsSprinting + state: ActiveDouble + - group: NoBootsWalking + state: ActiveBase + - group: Sprinting + state: ActiveBase + ActiveBase: + - group: TimeDelayActive + state: Idle + ActiveDouble: + - group: TimeDelayActive + state: Idle + - type: STAnomalyVisualState + - type: STAnomalyEffectDamage + options: + StateActiveBase: + range: 0.8 + damage: + types: + Slash: 12 + StateActiveDouble: + range: 0.5 + damage: + types: + Slash: 24 + - type: STAnomalyEffectDamageWeightBonus + weightThreshold: 100 + weightCap: 190 + bonusPerTenKg: 0.1 + maxBonus: 1.0 + options: + StateActiveBase: + range: 0.8 + damage: + types: + Slash: 12 + StateActiveDouble: + range: 0.8 + damage: + types: + Slash: 24 + - type: STAnomalyEffectDamageSoundConditional + passthroughSound: /Audio/_Lua/Effects/glass_shards_passthrough.ogg + weightThreshold: 100 + baseDamageSound: /Audio/_Lua/Effects/glass_hit_01.ogg + doubleDamageSound: /Audio/_Lua/Effects/glass_hit_02.ogg + weightBonusSound: /Audio/_Lua/Effects/glass_hit_03.ogg + doubleDamageGroup: StateActiveDouble + range: 0.8 + - type: Sprite + sprite: _Lua/Objects/Anomalies/glass_shards.rsi + drawdepth: Effects + layers: + - state: idle + map: ["base"] + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.8 + hard: false + mask: + - MobMask + layer: + - MobLayer + - LowImpassable + - type: GenericVisualizer + visuals: + enum.ZoneAnomalyVisuals.Layer: + base: + Idle: { state: idle } + ActiveBase: { state: active } + ActiveDouble: { state: active } + - type: ZoneAnomaly + detectedLevel: 1 + activationDelay: 0 + preparingDelay: 0 + chargeTime: 2 + - type: STAnomalyTips + icon: + sprite: /Textures/_Lua/Interface/Overlays/anomaly_tips.rsi + state: gravity diff --git a/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/psy.yml b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/psy.yml new file mode 100644 index 00000000000..781a56b60cc --- /dev/null +++ b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/psy.yml @@ -0,0 +1,81 @@ +- type: entity + parent: ZoneAnomalyBase + id: ZoneAnomalyLamp + suffix: Stalker, Lamp, Anomaly + components: + - type: ZoneAnomalyUpdateTriggerCollide + - type: ZoneAnomalyEffectPointLight + - type: ZoneAnomaly + detectedLevel: 0 + preparingDelay: 0 + activationDelay: 60 + chargeTime: 5 + - type: Sprite + sprite: _Lua/Objects/Anomalies/lamp.rsi + layers: + - state: idle + map: ["base"] + - type: PointLight + radius: 30 + energy: 16 + color: "#fce16e" + castShadows: true + enabled: false + - type: GenericVisualizer + visuals: + enum.ZoneAnomalyVisuals.Layer: + base: + Activated: { state: active } + Charging: { state: idle } + Idle: { state: idle2 } + - type: AmbientSound + volume: -22 + range: 3 + sound: + path: /Audio/_Lua/Effects/electra_idle.ogg + - type: STAnomalyTips + icon: + sprite: /Textures/_Lua/Interface/Overlays/anomaly_tips.rsi + state: unknown + +- type: entity + parent: ZoneAnomalyBase + id: ZoneAnomalyRift + suffix: Stalker, Rift, Anomaly + components: + - type: ZoneAnomalyTriggerStartCollide + - type: ZoneAnomalyEffectDamage + ignoreResistances: true + damage: + types: + Psy: 60 + - type: ZoneAnomalyEffectSpawn + entry: + - id: EffectEmpPulse + - type: ZoneAnomalyEffectStealth + idle: 0 + activated: -0.6 + - type: ZoneAnomalyEffectRandomTeleport + - type: ZoneAnomaly + detectedLevel: 4 + preparingDelay: 0 + activationDelay: 1 + chargeTime: 1 + - type: Stealth + - type: Sprite + sprite: _Lua/Objects/Other/Anomalys/rift.rsi + layers: + - state: idle + map: ["base"] + shader: unshaded + - type: GenericVisualizer + visuals: + enum.ZoneAnomalyVisuals.Layer: + base: + Activated: { state: active } + Idle: { state: idle } + - type: STAnomalyTips + icon: + sprite: /Textures/_Lua/Interface/Overlays/anomaly_tips.rsi + state: unknown + # AddOrDelOnCollideSafeZone - not ported yet diff --git a/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/spatial.yml b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/spatial.yml new file mode 100644 index 00000000000..6724fd8ee82 --- /dev/null +++ b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/spatial.yml @@ -0,0 +1,4 @@ +- type: entity + parent: ZoneAnomalyBase + id: ZoneAnomalyBaseSpatial + abstract: true diff --git a/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/thermal.yml b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/thermal.yml new file mode 100644 index 00000000000..962bf667152 --- /dev/null +++ b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/thermal.yml @@ -0,0 +1,238 @@ +- type: entity + parent: ZoneAnomalyBase + id: ZoneAnomalyBaseThermal + abstract: true + components: + - type: STAnomalyTips + icon: + sprite: /Textures/_Lua/Interface/Overlays/anomaly_tips.rsi + state: fiery + +- type: entity + parent: ZoneAnomalyBaseThermal + id: ZoneAnomalyJarka + suffix: Stalker, Jarka, Anomaly + components: + - type: ZoneAnomalyUpdateTriggerCollide + - type: ZoneAnomalyEffectDamage + fireStacks: 0.25 + damageUpdate: true + damage: + types: + Heat: 2 + - type: ZoneAnomalyEffectDestroy + whitelist: + tags: + - STBolt + - type: ZoneAnomalyEffectPointLight + - type: ZoneAnomaly + detectedLevel: 0 + preparingDelay: 0 + activationDelay: 4 + chargeTime: 0 + - type: Stealth + - type: Sprite + sprite: _Lua/Objects/Anomalies/jarka.rsi + offset: 0, 0.5 + layers: + - state: idle + map: ["base"] + - type: PointLight + radius: 10 + energy: 12 + color: "#f37011" + castShadows: true + enabled: false + - type: AmbientSound + volume: -16 + range: 8 + sound: + path: /Audio/_Lua/Effects/fireball_idle.ogg + - type: GenericVisualizer + visuals: + enum.ZoneAnomalyVisuals.Layer: + base: + Activated: { state: active } + Charging: { state: idle } + Idle: { state: idle } + +- type: entity + parent: ZoneAnomalyJarka + id: ZoneAnomalyJarkaStrong + suffix: Stalker, Jarka strong, Anomaly + components: + - type: ZoneAnomaly + detectedLevel: 1 + - type: ZoneAnomalyEffectDamage + fireStacks: 0.4 + damageUpdate: true + damage: + types: + Heat: 5 + - type: Stealth + - type: Sprite + sprite: _Lua/Objects/Anomalies/jarka_strong.rsi + +- type: entity + parent: ZoneAnomalyBaseThermal + id: ZoneAnomalyJarkaLong + suffix: Stalker, Jarka, Anomaly + components: + - type: ZoneAnomalyUpdateTriggerCollide + - type: ZoneAnomalyEffectDamage + fireStacks: 0.25 + damageUpdate: true + damage: + types: + Heat: 2 + - type: ZoneAnomalyEffectDestroy + whitelist: + tags: + - STBolt + - type: ZoneAnomalyEffectPointLight + - type: ZoneAnomaly + detectedLevel: 0 + preparingDelay: 0 + activationDelay: 4000 + chargeTime: 0 + - type: Stealth + - type: Sprite + sprite: _Lua/Objects/Anomalies/jarka.rsi + offset: 0, 0.5 + layers: + - state: idle + map: ["base"] + - type: PointLight + radius: 10 + energy: 12 + color: "#f37011" + castShadows: true + enabled: false + - type: AmbientSound + volume: -16 + range: 8 + sound: + path: /Audio/_Lua/Effects/fireball_idle.ogg + - type: GenericVisualizer + visuals: + enum.ZoneAnomalyVisuals.Layer: + base: + Activated: { state: active } + Charging: { state: idle } + Idle: { state: idle } + +- type: entity + parent: ZoneAnomalyJarka + id: ZoneAnomalySteam + suffix: Stalker, Steam, Anomaly + components: + - type: ZoneAnomalyUpdateTriggerCollide + - type: ZoneAnomalyEffectDamage + fireStacks: 0.5 + damageUpdate: true + damage: + types: + Heat: 8 + - type: ZoneAnomaly + detectedLevel: 0 + preparingDelay: 0 + activationDelay: 4 + chargeTime: 0 + - type: Stealth + - type: Sprite + sprite: _Lua/Objects/Anomalies/steam.rsi + offset: 0, 1 + +- type: entity + id: ZoneAnomalySol + parent: ZoneAnomalyBaseThermal + suffix: Stalker, Sol, Anomaly + components: + - type: ZoneAnomalyUpdateTriggerCollide + - type: ZoneAnomalyEffectExplosion + protoId: Son + totalIntensity: 300 + slope: 10 + maxTileIntensity: 700 + - type: ZoneAnomalyEffectDestroy + whitelist: + tags: + - STBolt + - type: ZoneAnomalyEffectPointLight + - type: ZoneAnomaly + detectedLevel: 2 + preparingDelay: 0 + activationDelay: 3 + chargeTime: 5 + - type: Sprite + sprite: _Lua/Objects/Other/Anomalys/sol.rsi + layers: + - state: idle + map: ["base"] + shader: unshaded + - type: PointLight + radius: 10 + energy: 12 + color: "#f3b511" + castShadows: true + enabled: false + - type: AmbientSound + volume: -16 + range: 8 + sound: + path: /Audio/_Lua/Effects/fireball_idle.ogg + - type: GenericVisualizer + visuals: + enum.ZoneAnomalyVisuals.Layer: + base: + Activated: { state: active } + Idle: { state: idle } + +- type: entity + parent: ZoneAnomalyBaseThermal + id: ZoneAnomalyJarkaNoArts + suffix: Stalker, Jarka, Anomaly, NO ARTS + categories: [ HideSpawnMenu ] + components: + - type: ZoneAnomalyUpdateTriggerCollide + - type: ZoneAnomalyEffectDamage + fireStacks: 0.4 + damageUpdate: true + damage: + types: + Heat: 3 + - type: ZoneAnomalyEffectDestroy + whitelist: + tags: + - STBolt + - type: ZoneAnomalyEffectPointLight + - type: ZoneAnomaly + detectedLevel: 0 + preparingDelay: 0 + activationDelay: 4 + chargeTime: 0 + - type: Stealth + - type: Sprite + sprite: _Lua/Objects/Anomalies/jarka.rsi + offset: 0, 0.5 + layers: + - state: idle + map: ["base"] + - type: PointLight + radius: 10 + energy: 12 + color: "#f37011" + castShadows: true + enabled: false + - type: AmbientSound + volume: -16 + range: 8 + sound: + path: /Audio/_Lua/Effects/fireball_idle.ogg + - type: GenericVisualizer + visuals: + enum.ZoneAnomalyVisuals.Layer: + base: + Activated: { state: active } + Charging: { state: idle } + Idle: { state: idle } diff --git a/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/vortex.yml b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/vortex.yml new file mode 100644 index 00000000000..3d06f31f398 --- /dev/null +++ b/Resources/Prototypes/_Lua/Entities/Objects/Anomalies/vortex.yml @@ -0,0 +1,87 @@ +- type: entity + parent: ZoneAnomalyBaseGravity + id: ZoneAnomalyVortex + suffix: Stalker, Vortex, Anomaly + description: A deadly gravitational vortex. You'll hear it before you see it. Those caught inside are paralyzed and dragged to their doom. + components: + - type: ZoneAnomalyTriggerStartCollide + - type: ZoneAnomalyEffectDamage + damageUpdate: true + damageUpdateDelay: 1.0 + damage: + types: + Compression: 3 + - type: ZoneAnomalyEffectGravityWell + distance: 3.5 + period: 1.0 + radial: 50 + tangential: 0.5 + gradient: ReversedLinear + - type: ZoneAnomalyEffectGib + coreRadius: 0.5 + gibDelay: 2.5 + gibOrgans: true + throwOnGib: true + throwRange: 5 + throwForce: 60 + pendingDoomSound: + path: /Audio/_Lua/Effects/Vortex/blowout.ogg + whitelist: + components: + - MobState + - type: ZoneAnomalyEffectBlast + throwRange: 5 + throwForce: 60 + delay: 0.5 + - type: Stealth + - type: ZoneAnomalyEffectStealth + idle: 0.2 + activated: 1 + charging: 0 + - type: ZoneAnomaly + detectedLevel: 3 + activationDelay: 3 + preparingDelay: 0 + chargeTime: 5 + - type: Sprite + sprite: _Lua/Objects/Anomalies/vortex.rsi + scale: 2, 2 + layers: + - state: idle + map: ["base"] + - type: Appearance + - type: GenericVisualizer + visuals: + enum.ZoneAnomalyVisuals.Layer: + base: + Activated: { state: active } + Charging: { state: charging } + Idle: { state: idle } + enum.Content.Shared._Stalker.ZoneAnomaly.Effects.ZoneAnomalyGibVisuals.Doomed: + base: + True: { state: doomed } + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 3 + density: 50 + hard: false + mask: + - MobMask + layer: + - MobLayer + - LowImpassable + - type: ZoneAnomalyStealthPulse + minVisibility: 0.2 + maxVisibility: 0.6 + pulseDuration: 2.0 + updateInterval: 0.1 + - type: ZoneAnomalyProximitySound + maxRange: 10 + minVolume: 0.1 + maxVolume: 1 + updateCooldown: 0.25 + sound: + path: /Audio/_Lua/Effects/Vortex/idle.ogg diff --git a/Resources/Prototypes/_Lua/Explosion/anomaly.yml b/Resources/Prototypes/_Lua/Explosion/anomaly.yml new file mode 100644 index 00000000000..c764f965232 --- /dev/null +++ b/Resources/Prototypes/_Lua/Explosion/anomaly.yml @@ -0,0 +1,8 @@ +# Explosion prototype for Sol anomaly +- type: explosion + id: Son + damagePerIntensity: + types: + Heat: 10 + tileBreakChance: [0, 0, 0] + tileBreakIntensity: [0, 0, 0] diff --git a/Resources/Prototypes/_Lua/Reagents/anomaly.yml b/Resources/Prototypes/_Lua/Reagents/anomaly.yml new file mode 100644 index 00000000000..c16d5537c3b --- /dev/null +++ b/Resources/Prototypes/_Lua/Reagents/anomaly.yml @@ -0,0 +1,34 @@ +# Reagent used by Smoke anomaly +- type: reagent + id: AbsoluteAbsorber + name: reagent-name-absolute-absorber + desc: reagent-desc-absolute-absorber + physicalDesc: reagent-physical-desc-acidic + group: Toxins + flavor: bitter + color: "#9aff38" + boilingPoint: 55.5 + meltingPoint: -50.0 + metabolisms: + Poison: + effects: + - !type:HealthChange + damage: + types: + Cellular: 2 + Poison: 2 + - !type:PopupMessage + visualType: MediumCaution + type: Local + messages: [ "generic-reagent-effect-parched" ] + probability: 0.5 + reactiveEffects: + Acidic: + methods: [ Touch ] + effects: + - !type:HealthChange + scaleByQuantity: true + ignoreResistances: false + damage: + types: + Caustic: 2.5 diff --git a/Resources/Prototypes/_Lua/Tags/anomaly.yml b/Resources/Prototypes/_Lua/Tags/anomaly.yml new file mode 100644 index 00000000000..83a11c411b2 --- /dev/null +++ b/Resources/Prototypes/_Lua/Tags/anomaly.yml @@ -0,0 +1,9 @@ +# Tags used by the anomaly systems +- type: Tag + id: STBolt + +- type: Tag + id: AnomalyGarlandActivationTarget + +- type: Tag + id: STAnomalyGenerationIntersectionSkip diff --git a/Resources/Prototypes/_NF/Entities/Markers/Spawners/Random/Salvage/minerals.yml b/Resources/Prototypes/_NF/Entities/Markers/Spawners/Random/Salvage/minerals.yml index 86983668063..4852d11422a 100644 --- a/Resources/Prototypes/_NF/Entities/Markers/Spawners/Random/Salvage/minerals.yml +++ b/Resources/Prototypes/_NF/Entities/Markers/Spawners/Random/Salvage/minerals.yml @@ -24,8 +24,6 @@ weight: 0.04 - id: WallRockBananium weight: 0.01 - - id: WallRockArtifactFragment - weight: 0.01 - id: WallRockDiamond weight: 0.002 - id: NFWallRockBluespace @@ -77,8 +75,6 @@ weight: 0.004 - id: NFWallCobblebrickBananium weight: 0.001 - - id: NFWallCobblebrickArtifactFragment - weight: 0.001 - id: NFWallCobblebrickBluespace weight: 0.0003 - id: NFCobblebrickElementalSpawner @@ -109,8 +105,6 @@ children: - id: NFWallCobblebrick weight: 0.3 - - id: NFWallCobblebrickArtifactFragment - weight: 0.1 - id: NFWallCobblebrickBluespace weight: 0.3 - id: NFWallCobblebrickDiamond @@ -161,8 +155,6 @@ weight: 0.01 - id: WallRockSnowBananium weight: 0.005 - - id: WallRockSnowArtifactFragment - weight: 0.04 - id: WallRockSnowDiamond weight: 0.002 - id: NFWallRockSnowBluespace @@ -212,8 +204,6 @@ weight: 0.001 - id: NFWallIceBananium weight: 0.0005 - - id: NFWallIceArtifactFragment - weight: 0.004 # - id: NFWallIceDiamond # weight: 0.0002 - id: NFWallIceBluespace @@ -244,8 +234,6 @@ children: - id: NFWallIce weight: 0.3 - - id: NFWallIceArtifactFragment - weight: 0.5 - id: NFWallIceBluespace weight: 0.1 - id: NFWallIceDiamond @@ -296,8 +284,6 @@ weight: 0.01 - id: WallRockAndesiteBananium weight: 0.005 - - id: WallRockAndesiteArtifactFragment - weight: 0.01 - id: WallRockAndesiteDiamond weight: 0.008 - id: NFWallRockAndesiteBluespace @@ -347,8 +333,6 @@ weight: 0.001 - id: NFWallAndesiteCobblebrickBananium weight: 0.0005 - - id: NFWallAndesiteCobblebrickArtifactFragment - weight: 0.001 # - id: NFWallAndesiteCobblebrickDiamond # weight: 0.0012 - id: NFWallAndesiteCobblebrickBluespace @@ -379,8 +363,6 @@ children: - id: NFWallAndesiteCobblebrick weight: 0.3 - - id: NFWallAndesiteCobblebrickArtifactFragment - weight: 0.1 - id: NFWallAndesiteCobblebrickBluespace weight: 0.1 - id: NFWallAndesiteCobblebrickDiamond @@ -431,8 +413,6 @@ weight: 0.05 - id: WallRockBasaltBananium weight: 0.005 - - id: WallRockBasaltArtifactFragment - weight: 0.01 - id: WallRockBasaltDiamond weight: 0.005 - id: NFWallRockBasaltBluespace @@ -482,8 +462,6 @@ weight: 0.005 - id: NFWallBasaltCobblebrickBananium weight: 0.0005 - - id: NFWallBasaltCobblebrickArtifactFragment - weight: 0.001 # - id: NFWallBasaltCobblebrickDiamond # weight: 0.0005 - id: NFWallBasaltCobblebrickBluespace @@ -514,8 +492,6 @@ children: - id: NFWallBasaltCobblebrick weight: 0.3 - - id: NFWallBasaltCobblebrickArtifactFragment - weight: 0.25 - id: NFWallBasaltCobblebrickBluespace weight: 0.25 - id: NFWallBasaltCobblebrickDiamond @@ -566,8 +542,6 @@ weight: 0.01 - id: WallRockSandBananium weight: 0.01 - - id: WallRockSandArtifactFragment - weight: 0.01 - id: WallRockSandDiamond weight: 0.002 - id: NFWallRockSandBluespace @@ -617,8 +591,6 @@ weight: 0.001 - id: NFWallSandstoneBananium weight: 0.001 - - id: NFWallSandstoneArtifactFragment - weight: 0.001 # - id: NFWallSandstoneDiamond # weight: 0.0002 - id: NFWallSandstoneBluespace @@ -649,8 +621,6 @@ children: - id: NFWallBasaltCobblebrick weight: 0.3 - - id: NFWallBasaltCobblebrickArtifactFragment - weight: 0.3 - id: NFWallBasaltCobblebrickBluespace weight: 0.1 - id: NFWallBasaltCobblebrickDiamond @@ -701,8 +671,6 @@ weight: 0.09 - id: WallRockChromiteBananium weight: 0.015 - - id: WallRockChromiteArtifactFragment - weight: 0.01 - id: WallRockChromiteDiamond weight: 0.005 - id: NFWallRockChromiteBluespace @@ -752,8 +720,6 @@ weight: 0.009 - id: NFWallChromiteCobblebrickBananium weight: 0.0015 - - id: NFWallChromiteCobblebrickArtifactFragment - weight: 0.001 # - id: NFWallChromiteCobblebrickDiamond # weight: 0.0005 - id: NFWallChromiteCobblebrickBluespace @@ -784,8 +750,6 @@ children: - id: NFWallBasaltCobblebrick weight: 0.3 - - id: NFWallBasaltCobblebrickArtifactFragment - weight: 0.25 - id: NFWallBasaltCobblebrickBluespace weight: 0.25 - id: NFWallBasaltCobblebrickDiamond @@ -836,8 +800,6 @@ weight: 0.04 - id: AsteroidRockBananium weight: 0.04 - - id: AsteroidRockArtifactFragment - weight: 0.01 - id: NFAsteroidRockBluespace weight: 0.002 - id: AsteroidRockDiamond @@ -887,8 +849,6 @@ weight: 0.004 - id: NFWallNecropolisBananium weight: 0.004 - - id: NFWallNecropolisArtifactFragment - weight: 0.001 - id: NFWallNecropolisBluespace weight: 0.0002 # - id: NFWallNecropolisDiamond @@ -919,8 +879,6 @@ children: - id: NFWallNecropolis weight: 0.3 - - id: NFWallNecropolisArtifactFragment - weight: 0.2 - id: NFWallNecropolisBluespace weight: 0.25 - id: NFWallNecropolisDiamond @@ -971,8 +929,6 @@ weight: 0.01 - id: NFWallRockScrapPileBananium weight: 0.005 - - id: NFWallRockScrapPileArtifactFragment - weight: 0.02 - id: NFWallRockScrapPileDiamond weight: 0.002 - id: NFWallRockScrapPileBluespace diff --git a/Resources/Prototypes/_NF/Entities/World/Debris/wrecks.yml b/Resources/Prototypes/_NF/Entities/World/Debris/wrecks.yml index b90338da0bb..653d38b2d59 100644 --- a/Resources/Prototypes/_NF/Entities/World/Debris/wrecks.yml +++ b/Resources/Prototypes/_NF/Entities/World/Debris/wrecks.yml @@ -78,8 +78,6 @@ prob: 0.1 - id: NFSalvageSuitStorageSpawner prob: 0.1 - - id: RandomArtifactSpawner - prob: 0.05 - id: NFSalvageTankSpawnerHighCapacity prob: 0.0005 - id: RandomSurivalSpawndWrecks diff --git a/Resources/Prototypes/_NF/Procedural/basalt_vgroid.yml b/Resources/Prototypes/_NF/Procedural/basalt_vgroid.yml index 9d8c3d5f495..33161671012 100644 --- a/Resources/Prototypes/_NF/Procedural/basalt_vgroid.yml +++ b/Resources/Prototypes/_NF/Procedural/basalt_vgroid.yml @@ -110,13 +110,6 @@ count: 100 minGroupSize: 6 maxGroupSize: 16 - - !type:OreDunGen - entityMask: - - WallRockBasalt - entity: WallRockBasaltArtifactFragment - count: 25 - minGroupSize: 1 - maxGroupSize: 3 - !type:OreDunGen entityMask: - WallRockBasalt diff --git a/Resources/Prototypes/_NF/Procedural/cave_vgroid.yml b/Resources/Prototypes/_NF/Procedural/cave_vgroid.yml index 419efe35cc9..750dc51069d 100644 --- a/Resources/Prototypes/_NF/Procedural/cave_vgroid.yml +++ b/Resources/Prototypes/_NF/Procedural/cave_vgroid.yml @@ -110,13 +110,6 @@ count: 100 minGroupSize: 6 maxGroupSize: 16 - - !type:OreDunGen - entityMask: - - WallRock - entity: WallRockArtifactFragment - count: 25 - minGroupSize: 1 - maxGroupSize: 3 - !type:OreDunGen entityMask: - WallRock diff --git a/Resources/Prototypes/_NF/Procedural/chromite_vgroid.yml b/Resources/Prototypes/_NF/Procedural/chromite_vgroid.yml index 3c77bc9d2b5..42aa8906f47 100644 --- a/Resources/Prototypes/_NF/Procedural/chromite_vgroid.yml +++ b/Resources/Prototypes/_NF/Procedural/chromite_vgroid.yml @@ -110,13 +110,6 @@ count: 100 minGroupSize: 6 maxGroupSize: 16 - - !type:OreDunGen - entityMask: - - WallRockChromite - entity: WallRockChromiteArtifactFragment - count: 25 - minGroupSize: 1 - maxGroupSize: 3 - !type:OreDunGen entityMask: - WallRockChromite diff --git a/Resources/Prototypes/_NF/Procedural/scrap_vgroid.yml b/Resources/Prototypes/_NF/Procedural/scrap_vgroid.yml index 31c6a0efb46..b6f1ba1870b 100644 --- a/Resources/Prototypes/_NF/Procedural/scrap_vgroid.yml +++ b/Resources/Prototypes/_NF/Procedural/scrap_vgroid.yml @@ -108,13 +108,6 @@ count: 70 minGroupSize: 6 maxGroupSize: 16 - - !type:OreDunGen - entityMask: - - NFWallRockScrapPile - entity: NFWallRockScrapPileArtifactFragment - count: 15 - minGroupSize: 1 - maxGroupSize: 3 - !type:OreDunGen entityMask: - NFWallRockScrapPile diff --git a/Resources/Prototypes/_NF/Procedural/snow_vgroid.yml b/Resources/Prototypes/_NF/Procedural/snow_vgroid.yml index 825861d4523..ddae8be379e 100644 --- a/Resources/Prototypes/_NF/Procedural/snow_vgroid.yml +++ b/Resources/Prototypes/_NF/Procedural/snow_vgroid.yml @@ -110,13 +110,6 @@ count: 100 minGroupSize: 6 maxGroupSize: 16 - - !type:OreDunGen - entityMask: - - WallRockSnow - entity: WallRockSnowArtifactFragment - count: 25 - minGroupSize: 1 - maxGroupSize: 3 - !type:OreDunGen entityMask: - WallRockSnow diff --git a/Resources/Prototypes/_NF/Procedural/zombie_vgroid.yml b/Resources/Prototypes/_NF/Procedural/zombie_vgroid.yml index 802ff4035dd..a926440ae1d 100644 --- a/Resources/Prototypes/_NF/Procedural/zombie_vgroid.yml +++ b/Resources/Prototypes/_NF/Procedural/zombie_vgroid.yml @@ -105,13 +105,6 @@ count: 100 minGroupSize: 6 maxGroupSize: 16 - - !type:OreDunGen - entityMask: - - WallRock - entity: WallRockArtifactFragment - count: 25 - minGroupSize: 1 - maxGroupSize: 3 - !type:OreDunGen entityMask: - WallRock diff --git a/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/electric.png b/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/electric.png new file mode 100644 index 00000000000..0506499bf82 Binary files /dev/null and b/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/electric.png differ diff --git a/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/fiery.png b/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/fiery.png new file mode 100644 index 00000000000..ef5e8f26b6a Binary files /dev/null and b/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/fiery.png differ diff --git a/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/gravity.png b/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/gravity.png new file mode 100644 index 00000000000..3046fe8948e Binary files /dev/null and b/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/gravity.png differ diff --git a/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/icy.png b/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/icy.png new file mode 100644 index 00000000000..5d4a024a355 Binary files /dev/null and b/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/icy.png differ diff --git a/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/meta.json b/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/meta.json new file mode 100644 index 00000000000..1a847eff018 --- /dev/null +++ b/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/meta.json @@ -0,0 +1,29 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by Tornado-Technology (GitHub) for Stalker", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "unknown" + }, + { + "name": "electric" + }, + { + "name": "fiery" + }, + { + "name": "gravity" + }, + { + "name": "icy" + }, + { + "name": "toxic" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/toxic.png b/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/toxic.png new file mode 100644 index 00000000000..e92d21d34d8 Binary files /dev/null and b/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/toxic.png differ diff --git a/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/unknown.png b/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/unknown.png new file mode 100644 index 00000000000..c66c3bbe748 Binary files /dev/null and b/Resources/Textures/_Lua/Interface/Overlays/anomaly_tips.rsi/unknown.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/anomaly_generator_blocker.rsi/icon.png b/Resources/Textures/_Lua/Objects/Anomalies/anomaly_generator_blocker.rsi/icon.png new file mode 100644 index 00000000000..12e3e3c1889 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/anomaly_generator_blocker.rsi/icon.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/anomaly_generator_blocker.rsi/meta.json b/Resources/Textures/_Lua/Objects/Anomalies/anomaly_generator_blocker.rsi/meta.json new file mode 100644 index 00000000000..322970a2e6c --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Anomalies/anomaly_generator_blocker.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "license": "CC-BY-SA-3.0", + "copyright": "From Viis for Stalker14", + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + } + ] +} diff --git a/Resources/Textures/_Lua/Objects/Anomalies/barabashka.rsi/active.png b/Resources/Textures/_Lua/Objects/Anomalies/barabashka.rsi/active.png new file mode 100644 index 00000000000..4e834ed911a Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/barabashka.rsi/active.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/barabashka.rsi/idle.png b/Resources/Textures/_Lua/Objects/Anomalies/barabashka.rsi/idle.png new file mode 100644 index 00000000000..59f345af7b3 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/barabashka.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/barabashka.rsi/meta.json b/Resources/Textures/_Lua/Objects/Anomalies/barabashka.rsi/meta.json new file mode 100644 index 00000000000..dda4d248d97 --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Anomalies/barabashka.rsi/meta.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by Tornado-Technology (Github) for Stalker 14", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "active", + "delays": [ + [ + 1, + 1, + 1, + 1 + ] + ] + }, + { + "name": "idle", + "delays": [ + [ + 1, + 1, + 1, + 1 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Objects/Anomalies/bubble.rsi/active.png b/Resources/Textures/_Lua/Objects/Anomalies/bubble.rsi/active.png new file mode 100644 index 00000000000..a238166586e Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/bubble.rsi/active.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/bubble.rsi/idle.png b/Resources/Textures/_Lua/Objects/Anomalies/bubble.rsi/idle.png new file mode 100644 index 00000000000..30e6dbb7456 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/bubble.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/bubble.rsi/meta.json b/Resources/Textures/_Lua/Objects/Anomalies/bubble.rsi/meta.json new file mode 100644 index 00000000000..9f8735e9245 --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Anomalies/bubble.rsi/meta.json @@ -0,0 +1,105 @@ + { + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Corvax STALKER", + "size": { + "x": 96, + "y": 96 + }, + "states": [ + { + "name": "active", + "delays": [ + [ + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01 + ] + ] + }, + { + "name": "idle", + "delays": [ + [ + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Objects/Anomalies/bubble2.rsi/idle.png b/Resources/Textures/_Lua/Objects/Anomalies/bubble2.rsi/idle.png new file mode 100644 index 00000000000..1c57eb1052d Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/bubble2.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/bubble2.rsi/meta.json b/Resources/Textures/_Lua/Objects/Anomalies/bubble2.rsi/meta.json new file mode 100644 index 00000000000..3686e5a9431 --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Anomalies/bubble2.rsi/meta.json @@ -0,0 +1,31 @@ + { + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Corvax STALKER", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "idle", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Objects/Anomalies/electra.rsi/active.png b/Resources/Textures/_Lua/Objects/Anomalies/electra.rsi/active.png new file mode 100644 index 00000000000..f386e634245 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/electra.rsi/active.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/electra.rsi/idle.png b/Resources/Textures/_Lua/Objects/Anomalies/electra.rsi/idle.png new file mode 100644 index 00000000000..555b6354464 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/electra.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/electra.rsi/meta.json b/Resources/Textures/_Lua/Objects/Anomalies/electra.rsi/meta.json new file mode 100644 index 00000000000..a0eaff0f985 --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Anomalies/electra.rsi/meta.json @@ -0,0 +1,49 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Corvax STALKER", + "size": { + "x": 128, + "y": 128 + }, + "states": [ + { + "name": "idle", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "active", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/_Lua/Objects/Anomalies/emp.rsi/active.png b/Resources/Textures/_Lua/Objects/Anomalies/emp.rsi/active.png new file mode 100644 index 00000000000..1ecf613b783 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/emp.rsi/active.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/emp.rsi/idle.png b/Resources/Textures/_Lua/Objects/Anomalies/emp.rsi/idle.png new file mode 100644 index 00000000000..30819145b3e Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/emp.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/emp.rsi/meta.json b/Resources/Textures/_Lua/Objects/Anomalies/emp.rsi/meta.json new file mode 100644 index 00000000000..fef9b42d56c --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Anomalies/emp.rsi/meta.json @@ -0,0 +1,39 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by Viis for Corvax STALKER", + "size": { + "x": 256, + "y": 256 + }, + "states": [ + { + "name": "idle", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "active", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] + } diff --git a/Resources/Textures/_Lua/Objects/Anomalies/freezer.rsi/active.png b/Resources/Textures/_Lua/Objects/Anomalies/freezer.rsi/active.png new file mode 100644 index 00000000000..99f82dfc07f Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/freezer.rsi/active.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/freezer.rsi/idle.png b/Resources/Textures/_Lua/Objects/Anomalies/freezer.rsi/idle.png new file mode 100644 index 00000000000..a4ea80197a4 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/freezer.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/freezer.rsi/meta.json b/Resources/Textures/_Lua/Objects/Anomalies/freezer.rsi/meta.json new file mode 100644 index 00000000000..18d4ddc952a --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Anomalies/freezer.rsi/meta.json @@ -0,0 +1,101 @@ + { + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by Tornado-Technology (Github) for Stalker", + "size": { + "x": 128, + "y": 128 + }, + "states": [ + { + "name": "idle" + }, + { + "name": "active", + "delays": [ + [ + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Objects/Anomalies/glass_shards.rsi/active.png b/Resources/Textures/_Lua/Objects/Anomalies/glass_shards.rsi/active.png new file mode 100644 index 00000000000..ef2e3326558 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/glass_shards.rsi/active.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/glass_shards.rsi/idle.png b/Resources/Textures/_Lua/Objects/Anomalies/glass_shards.rsi/idle.png new file mode 100644 index 00000000000..1a9f6d0b669 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/glass_shards.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/glass_shards.rsi/meta.json b/Resources/Textures/_Lua/Objects/Anomalies/glass_shards.rsi/meta.json new file mode 100644 index 00000000000..26ee2b7c07d --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Anomalies/glass_shards.rsi/meta.json @@ -0,0 +1,45 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "sprites by carousel", + "size": { + "x": 64, + "y": 64 + }, + "states": [ + { + "name": "idle", + "delays": [ + [ + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32 + ] + ] + }, + { + "name": "active", + "delays": [ + [ + 0.28, + 0.2, + 0.2, + 0.2 + ] + ] + } + ] +} diff --git a/Resources/Textures/_Lua/Objects/Anomalies/grabber.rsi/active.png b/Resources/Textures/_Lua/Objects/Anomalies/grabber.rsi/active.png new file mode 100644 index 00000000000..0c28f8dc3fa Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/grabber.rsi/active.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/grabber.rsi/charging.png b/Resources/Textures/_Lua/Objects/Anomalies/grabber.rsi/charging.png new file mode 100644 index 00000000000..0c28f8dc3fa Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/grabber.rsi/charging.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/grabber.rsi/idle.png b/Resources/Textures/_Lua/Objects/Anomalies/grabber.rsi/idle.png new file mode 100644 index 00000000000..0c28f8dc3fa Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/grabber.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/grabber.rsi/meta.json b/Resources/Textures/_Lua/Objects/Anomalies/grabber.rsi/meta.json new file mode 100644 index 00000000000..c768c3dda62 --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Anomalies/grabber.rsi/meta.json @@ -0,0 +1,47 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "By Tornado-Technology (github) for Stalker", + "size": { + "x": 96, + "y": 96 + }, + "states": [ + { + "name": "idle", + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "charging", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "active", + "delays": [ + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Objects/Anomalies/gravi.rsi/active.png b/Resources/Textures/_Lua/Objects/Anomalies/gravi.rsi/active.png new file mode 100644 index 00000000000..882f8ec2bde Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/gravi.rsi/active.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/gravi.rsi/charging.png b/Resources/Textures/_Lua/Objects/Anomalies/gravi.rsi/charging.png new file mode 100644 index 00000000000..882f8ec2bde Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/gravi.rsi/charging.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/gravi.rsi/idle.png b/Resources/Textures/_Lua/Objects/Anomalies/gravi.rsi/idle.png new file mode 100644 index 00000000000..882f8ec2bde Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/gravi.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/gravi.rsi/meta.json b/Resources/Textures/_Lua/Objects/Anomalies/gravi.rsi/meta.json new file mode 100644 index 00000000000..d4e58ffee2b --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Anomalies/gravi.rsi/meta.json @@ -0,0 +1,47 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "By Tornado-Technology (github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "idle", + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "charging", + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "active", + "delays": [ + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Objects/Anomalies/jarka.rsi/active.png b/Resources/Textures/_Lua/Objects/Anomalies/jarka.rsi/active.png new file mode 100644 index 00000000000..e378926c0e5 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/jarka.rsi/active.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/jarka.rsi/idle.png b/Resources/Textures/_Lua/Objects/Anomalies/jarka.rsi/idle.png new file mode 100644 index 00000000000..ed7c651f7b4 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/jarka.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/jarka.rsi/meta.json b/Resources/Textures/_Lua/Objects/Anomalies/jarka.rsi/meta.json new file mode 100644 index 00000000000..094a809e349 --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Anomalies/jarka.rsi/meta.json @@ -0,0 +1,92 @@ + { + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Corvax STALKER", + "size": { + "x": 32, + "y": 64 + }, + "states": [ + { + "name": "active", + "delays": [ + [ + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01 + ] + ] + }, + { + "name": "idle", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Objects/Anomalies/jarka_strong.rsi/active.png b/Resources/Textures/_Lua/Objects/Anomalies/jarka_strong.rsi/active.png new file mode 100644 index 00000000000..f25e8a61ff4 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/jarka_strong.rsi/active.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/jarka_strong.rsi/idle.png b/Resources/Textures/_Lua/Objects/Anomalies/jarka_strong.rsi/idle.png new file mode 100644 index 00000000000..ed7c651f7b4 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/jarka_strong.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/jarka_strong.rsi/meta.json b/Resources/Textures/_Lua/Objects/Anomalies/jarka_strong.rsi/meta.json new file mode 100644 index 00000000000..094a809e349 --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Anomalies/jarka_strong.rsi/meta.json @@ -0,0 +1,92 @@ + { + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Corvax STALKER", + "size": { + "x": 32, + "y": 64 + }, + "states": [ + { + "name": "active", + "delays": [ + [ + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01 + ] + ] + }, + { + "name": "idle", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Objects/Anomalies/lamp.rsi/active.png b/Resources/Textures/_Lua/Objects/Anomalies/lamp.rsi/active.png new file mode 100644 index 00000000000..b57d6d122ab Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/lamp.rsi/active.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/lamp.rsi/idle.png b/Resources/Textures/_Lua/Objects/Anomalies/lamp.rsi/idle.png new file mode 100644 index 00000000000..871f9a1a4cc Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/lamp.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/lamp.rsi/idle2.png b/Resources/Textures/_Lua/Objects/Anomalies/lamp.rsi/idle2.png new file mode 100644 index 00000000000..871f9a1a4cc Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/lamp.rsi/idle2.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/lamp.rsi/meta.json b/Resources/Textures/_Lua/Objects/Anomalies/lamp.rsi/meta.json new file mode 100644 index 00000000000..49cb62bd5bf --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Anomalies/lamp.rsi/meta.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "By Tornado-Technology (Github) for Stalker14", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "active" + }, + { + "name": "idle" + }, + { + "name": "idle2" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Objects/Anomalies/springobard.rsi/idle.png b/Resources/Textures/_Lua/Objects/Anomalies/springobard.rsi/idle.png new file mode 100644 index 00000000000..ac9583e2f09 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/springobard.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/springobard.rsi/meta.json b/Resources/Textures/_Lua/Objects/Anomalies/springobard.rsi/meta.json new file mode 100644 index 00000000000..193d5c43c87 --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Anomalies/springobard.rsi/meta.json @@ -0,0 +1,23 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "By Tornado-Technology (github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "idle", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Objects/Anomalies/steam.rsi/active.png b/Resources/Textures/_Lua/Objects/Anomalies/steam.rsi/active.png new file mode 100644 index 00000000000..7e558201f68 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/steam.rsi/active.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/steam.rsi/idle.png b/Resources/Textures/_Lua/Objects/Anomalies/steam.rsi/idle.png new file mode 100644 index 00000000000..ba6e7e059e3 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/steam.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/steam.rsi/meta.json b/Resources/Textures/_Lua/Objects/Anomalies/steam.rsi/meta.json new file mode 100644 index 00000000000..4b2e1825af3 --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Anomalies/steam.rsi/meta.json @@ -0,0 +1,92 @@ + { + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by Tornado-Technology (Github) for Stalker14", + "size": { + "x": 32, + "y": 98 + }, + "states": [ + { + "name": "active", + "delays": [ + [ + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01 + ] + ] + }, + { + "name": "idle", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Objects/Anomalies/vortex.rsi/active.png b/Resources/Textures/_Lua/Objects/Anomalies/vortex.rsi/active.png new file mode 100644 index 00000000000..560309ad4ee Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/vortex.rsi/active.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/vortex.rsi/charging.png b/Resources/Textures/_Lua/Objects/Anomalies/vortex.rsi/charging.png new file mode 100644 index 00000000000..560309ad4ee Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/vortex.rsi/charging.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/vortex.rsi/doomed.png b/Resources/Textures/_Lua/Objects/Anomalies/vortex.rsi/doomed.png new file mode 100644 index 00000000000..45d75b33882 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/vortex.rsi/doomed.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/vortex.rsi/idle.png b/Resources/Textures/_Lua/Objects/Anomalies/vortex.rsi/idle.png new file mode 100644 index 00000000000..560309ad4ee Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Anomalies/vortex.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Anomalies/vortex.rsi/meta.json b/Resources/Textures/_Lua/Objects/Anomalies/vortex.rsi/meta.json new file mode 100644 index 00000000000..ffd1d2fd1b3 --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Anomalies/vortex.rsi/meta.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Stalker-14-EN", + "size": { + "x": 64, + "y": 64 + }, + "states": [ + { + "name": "idle", + "delays": [[0.2, 0.2, 0.2, 0.2, 0.2]] + }, + { + "name": "charging", + "delays": [[0.15, 0.15, 0.15, 0.15, 0.15]] + }, + { + "name": "active", + "delays": [[0.1, 0.1, 0.1, 0.1, 0.1]] + }, + { + "name": "doomed", + "delays": [[0.05, 0.05, 0.05, 0.05, 0.05]] + } + ] +} diff --git a/Resources/Textures/_Lua/Objects/Other/Anomalys/beer.rsi/idle.png b/Resources/Textures/_Lua/Objects/Other/Anomalys/beer.rsi/idle.png new file mode 100644 index 00000000000..3ee84d9be8a Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Other/Anomalys/beer.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Other/Anomalys/beer.rsi/meta.json b/Resources/Textures/_Lua/Objects/Other/Anomalys/beer.rsi/meta.json new file mode 100644 index 00000000000..68114459c24 --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Other/Anomalys/beer.rsi/meta.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "By Tornado-Technology (github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "idle", + "delays": [ + [ + 0.4, + 0.4, + 0.4, + 0.4 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Objects/Other/Anomalys/garland.rsi/idle.png b/Resources/Textures/_Lua/Objects/Other/Anomalys/garland.rsi/idle.png new file mode 100644 index 00000000000..5b066ffa5ba Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Other/Anomalys/garland.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Other/Anomalys/garland.rsi/meta.json b/Resources/Textures/_Lua/Objects/Other/Anomalys/garland.rsi/meta.json new file mode 100644 index 00000000000..a66e1c313a4 --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Other/Anomalys/garland.rsi/meta.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "By Tornado-Technology (github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "idle", + "delays": [ + [ + 5.4, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Objects/Other/Anomalys/kissel.rsi/idle.png b/Resources/Textures/_Lua/Objects/Other/Anomalys/kissel.rsi/idle.png new file mode 100644 index 00000000000..34f4e74a86e Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Other/Anomalys/kissel.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Other/Anomalys/kissel.rsi/meta.json b/Resources/Textures/_Lua/Objects/Other/Anomalys/kissel.rsi/meta.json new file mode 100644 index 00000000000..68114459c24 --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Other/Anomalys/kissel.rsi/meta.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "By Tornado-Technology (github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "idle", + "delays": [ + [ + 0.4, + 0.4, + 0.4, + 0.4 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Objects/Other/Anomalys/rift.rsi/active.png b/Resources/Textures/_Lua/Objects/Other/Anomalys/rift.rsi/active.png new file mode 100644 index 00000000000..aaf6631ec38 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Other/Anomalys/rift.rsi/active.png differ diff --git a/Resources/Textures/_Lua/Objects/Other/Anomalys/rift.rsi/idle.png b/Resources/Textures/_Lua/Objects/Other/Anomalys/rift.rsi/idle.png new file mode 100644 index 00000000000..37164828dcb Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Other/Anomalys/rift.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Other/Anomalys/rift.rsi/meta.json b/Resources/Textures/_Lua/Objects/Other/Anomalys/rift.rsi/meta.json new file mode 100644 index 00000000000..d87704cc9c1 --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Other/Anomalys/rift.rsi/meta.json @@ -0,0 +1,35 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "By Tornado-Technology (github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "active", + "delays": [ + [ + 0.01, + 0.01, + 0.01, + 0.01, + 0.01 + ] + ] + }, + { + "name": "idle", + "delays": [ + [ + 0.6, + 0.6, + 0.6, + 0.6, + 0.6 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Objects/Other/Anomalys/sol.rsi/active.png b/Resources/Textures/_Lua/Objects/Other/Anomalys/sol.rsi/active.png new file mode 100644 index 00000000000..f40c2eabaa1 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Other/Anomalys/sol.rsi/active.png differ diff --git a/Resources/Textures/_Lua/Objects/Other/Anomalys/sol.rsi/idle.png b/Resources/Textures/_Lua/Objects/Other/Anomalys/sol.rsi/idle.png new file mode 100644 index 00000000000..eb27d037f17 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Other/Anomalys/sol.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Other/Anomalys/sol.rsi/meta.json b/Resources/Textures/_Lua/Objects/Other/Anomalys/sol.rsi/meta.json new file mode 100644 index 00000000000..2ebfe44fb0f --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Other/Anomalys/sol.rsi/meta.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "By Tornado-Technology (github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "active", + "delays": [ + [ + 0.05, + 0.05, + 0.05 + ] + ] + }, + { + "name": "idle", + "delays": [ + [ + 0.4, + 0.4, + 0.4, + 0.4 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Lua/Objects/Other/Anomalys/source.rsi/active.png b/Resources/Textures/_Lua/Objects/Other/Anomalys/source.rsi/active.png new file mode 100644 index 00000000000..cc7b191cc65 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Other/Anomalys/source.rsi/active.png differ diff --git a/Resources/Textures/_Lua/Objects/Other/Anomalys/source.rsi/charge.png b/Resources/Textures/_Lua/Objects/Other/Anomalys/source.rsi/charge.png new file mode 100644 index 00000000000..152beeede30 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Other/Anomalys/source.rsi/charge.png differ diff --git a/Resources/Textures/_Lua/Objects/Other/Anomalys/source.rsi/idle.png b/Resources/Textures/_Lua/Objects/Other/Anomalys/source.rsi/idle.png new file mode 100644 index 00000000000..45dcb4e9393 Binary files /dev/null and b/Resources/Textures/_Lua/Objects/Other/Anomalys/source.rsi/idle.png differ diff --git a/Resources/Textures/_Lua/Objects/Other/Anomalys/source.rsi/meta.json b/Resources/Textures/_Lua/Objects/Other/Anomalys/source.rsi/meta.json new file mode 100644 index 00000000000..ad54c50916c --- /dev/null +++ b/Resources/Textures/_Lua/Objects/Other/Anomalys/source.rsi/meta.json @@ -0,0 +1,47 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "By Tornado-Technology (github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "active", + "delays": [ + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ] + ] + }, + { + "name": "idle", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "charge", + "delays": [ + [ + 0.07, + 0.07, + 0.07, + 0.07, + 0.07 + ] + ] + } + ] +} \ No newline at end of file