From caefd5722c994a07318271025a7b6c1d14d9ff8b Mon Sep 17 00:00:00 2001 From: WardexOfficial <132839034+WardexOfficial@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:00:34 +0300 Subject: [PATCH 1/6] Update SCP-173 entity with movement restriction Added ScpRestrictMovementOnVisibility component to SCP-173. --- .../_Scp/Entities/Mobs/Player/Scp/Main/scp173.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Resources/Prototypes/_Scp/Entities/Mobs/Player/Scp/Main/scp173.yml b/Resources/Prototypes/_Scp/Entities/Mobs/Player/Scp/Main/scp173.yml index c76d5713a27..538fea85dda 100644 --- a/Resources/Prototypes/_Scp/Entities/Mobs/Player/Scp/Main/scp173.yml +++ b/Resources/Prototypes/_Scp/Entities/Mobs/Player/Scp/Main/scp173.yml @@ -1,4 +1,4 @@ -- type: entity +- type: entity parent: - BaseScp - MobCombat @@ -19,6 +19,10 @@ - type: Scp class: Euclid - type: Scp173 + - type: ScpRestrictMovementOnVisibility + containersMoveWhitelist: + components: + - ScpCage - type: ShowBlinkable - type: XenoArtifact effectsTable: !type:GroupSelector From f1ef56236dee4e833a777abd4f74c169e028ad16 Mon Sep 17 00:00:00 2001 From: WardexOfficial <132839034+WardexOfficial@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:01:00 +0300 Subject: [PATCH 2/6] Clean up movement event handling for Scp173Component Removed unused movement event subscriptions and related methods for Scp173Component. --- .../_Scp/Scp173/SharedScp173System.cs | 70 +------------------ 1 file changed, 1 insertion(+), 69 deletions(-) diff --git a/Content.Shared/_Scp/Scp173/SharedScp173System.cs b/Content.Shared/_Scp/Scp173/SharedScp173System.cs index 6d79abb9ac0..b1905fcaf23 100644 --- a/Content.Shared/_Scp/Scp173/SharedScp173System.cs +++ b/Content.Shared/_Scp/Scp173/SharedScp173System.cs @@ -1,4 +1,4 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using System.Numerics; using Content.Shared._Scp.Blinking; using Content.Shared._Scp.Containment.Cage; @@ -7,8 +7,6 @@ using Content.Shared._Scp.Watching; using Content.Shared.ActionBlocker; using Content.Shared.DoAfter; -using Content.Shared.Interaction.Events; -using Content.Shared.Movement.Events; using Content.Shared.Physics; using Content.Shared.Popups; using Content.Shared.Storage.Components; @@ -39,13 +37,6 @@ public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnAttackAttempt); - - SubscribeLocalEvent(OnDirectionAttempt); - SubscribeLocalEvent(OnMoveAttempt); - SubscribeLocalEvent(OnMoveInput); - SubscribeLocalEvent(OnMove); - SubscribeLocalEvent(OnStartedBlind); SubscribeLocalEvent(OnBlind); @@ -53,65 +44,6 @@ public override void Initialize() _scpCageQuery = GetEntityQuery(); } - #region Movement - - private void OnAttackAttempt(Entity ent, ref AttackAttemptEvent args) - { - if (IsInScpCage(ent, out _)) - { - args.Cancel(); - return; - } - - if (Watching.IsWatchedByAny(ent, useTimeCompensation: true)) - { - args.Cancel(); - return; - } - } - - private void OnDirectionAttempt(Entity ent, ref ChangeDirectionAttemptEvent args) - { - // В клетке можно двигаться - if (IsInScpCage(ent, out _)) - return; - - if (!Watching.IsWatchedByAny(ent, useTimeCompensation: true)) - return; - - args.Cancel(); - } - - private void OnMoveAttempt(Entity ent, ref UpdateCanMoveEvent args) - { - // В клетке можно двигаться - if (IsInScpCage(ent, out _)) - return; - - if (!Watching.IsWatchedByAny(ent, useTimeCompensation: true)) - return; - - args.Cancel(); - } - - private void OnMoveInput(Entity ent, ref MoveInputEvent args) - { - // Метод подвязанный на MoveInputEvent так же нужен, вместе с методом на MoveEvent - // Этот метод исправляет проблему, когда 173 должен мочь двинуться, но ему об этом никто не сказал - // То есть последний вопрос от 173 МОГУ ЛИ Я ДВИНУТЬСЯ был когда он еще мог двинуться, через MoveEvent - // Потом он перестал мочь, и следственно больше НЕ МОЖЕТ задать вопрос, может они двинуться - // Это фикслось в игре сменой направления спрайта мышкой - // Но данный метод как раз будет спрашивать у 173, может ли он сдвинуться, когда как раз не двигается - _blocker.UpdateCanMove(ent); - } - - private void OnMove(Entity ent, ref MoveEvent args) - { - _blocker.UpdateCanMove(ent); - } - - #endregion - #region Abillities private void OnStartedBlind(Entity ent, ref Scp173BlindAction args) From 18e518925c6e55e6decdd7f6772e94d0b7bfdaba Mon Sep 17 00:00:00 2001 From: WardexOfficial <132839034+WardexOfficial@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:01:37 +0300 Subject: [PATCH 3/6] Create ScpRestrictMovementOnVisibilityComponent --- .../ScpRestrictMovementOnVisibilityComponent | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/ScpRestrictMovementOnVisibilityComponent diff --git a/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/ScpRestrictMovementOnVisibilityComponent b/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/ScpRestrictMovementOnVisibilityComponent new file mode 100644 index 00000000000..6699e295ffc --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/ScpRestrictMovementOnVisibilityComponent @@ -0,0 +1,16 @@ +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared._Scp.Other.ScpRestrictMovementOnVisibility; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ScpRestrictMovementOnVisibilityComponent : Component +{ + [DataField] + public EntityWhitelist? ContainersMoveWhitelist; + + public EntityWhitelist? ContainersMoveBlacklist; + + [DataField] + public float ContainmentRoomSearchRadius = 8f; +} From fc68d49d4bde113c5360324b9f14cc0d9de72e6b Mon Sep 17 00:00:00 2001 From: WardexOfficial <132839034+WardexOfficial@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:01:53 +0300 Subject: [PATCH 4/6] Create SharedScpRestrictMovementOnVisibilitySystem --- ...haredScpRestrictMovementOnVisibilitySystem | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/SharedScpRestrictMovementOnVisibilitySystem diff --git a/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/SharedScpRestrictMovementOnVisibilitySystem b/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/SharedScpRestrictMovementOnVisibilitySystem new file mode 100644 index 00000000000..735495c5b9d --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/SharedScpRestrictMovementOnVisibilitySystem @@ -0,0 +1,104 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared._Scp.Watching; +using Content.Shared.ActionBlocker; +using Content.Shared.Interaction.Events; +using Content.Shared.Movement.Events; +using Content.Shared.Storage.Components; +using Content.Shared.Whitelist; +using Robust.Shared.Containers; + +namespace Content.Shared._Scp.Other.ScpRestrictMovementOnVisibility; + +public sealed class SharedScpRestrictMovementOnVisibilitySystem : EntitySystem +{ + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly ActionBlockerSystem _blocker = default!; + [Dependency] private readonly EyeWatchingSystem _watching = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + + private EntityQuery _insideQuery; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAttackAttempt); + + SubscribeLocalEvent(OnDirectionAttempt); + SubscribeLocalEvent(OnMoveAttempt); + SubscribeLocalEvent(OnMoveInput); + SubscribeLocalEvent(OnMove); + } + + public void OnAttackAttempt(Entity ent, ref AttackAttemptEvent args) + { + if (IsInContainer(ent, out _)) + { + args.Cancel(); + return; + } + + if (_watching.IsWatchedByAny(ent, useTimeCompensation: true)) + { + args.Cancel(); + return; + } + } + + public void OnDirectionAttempt(Entity ent, ref ChangeDirectionAttemptEvent args) + { + // В контейнере можно двигаться + if (IsInContainer(ent, out _)) + return; + + if (!_watching.IsWatchedByAny(ent, useTimeCompensation: true)) + return; + + args.Cancel(); + } + + public void OnMoveAttempt(Entity ent, ref UpdateCanMoveEvent args) + { + // В контейнере можно двигаться + if (IsInContainer(ent, out _)) + return; + + if (!_watching.IsWatchedByAny(ent, useTimeCompensation: true)) + return; + + args.Cancel(); + } + + public void OnMoveInput(Entity ent, ref MoveInputEvent args) + { + // Метод подвязанный на MoveInputEvent так же нужен, вместе с методом на MoveEvent + // Этот метод исправляет проблему, когда сущность должен мочь двинуться, но ему об этом никто не сказал + // То есть последний вопрос от сущности МОГУ ЛИ Я ДВИНУТЬСЯ был когда он еще мог двинуться, через MoveEvent + // Потом он перестал мочь, и следственно больше НЕ МОЖЕТ задать вопрос, может они двинуться + // Это фикслось в игре сменой направления спрайта мышкой + // Но данный метод как раз будет спрашивать у сущности, может ли он сдвинуться, когда как раз не двигается + _blocker.UpdateCanMove(ent); + } + + public void OnMove(Entity ent, ref MoveEvent args) + { + _blocker.UpdateCanMove(ent); + } + + public bool IsInContainer(Entity ent, [NotNullWhen(true)] out EntityUid? storage) + { + storage = null; + + if (!_insideQuery.TryComp(ent, out var insideEntityStorageComponent)) + return false; + + if (!_containerSystem.TryGetContainingContainer(ent.Owner, out var container)) + return false; + + if (!_whitelist.CheckBoth(container.Owner, ent.Comp.ContainersMoveBlacklist, ent.Comp.ContainersMoveWhitelist)) + return false; + + storage = insideEntityStorageComponent.Storage; + return true; + } +} From 086e98dc9fd1cc34b1697dd202c176443401094e Mon Sep 17 00:00:00 2001 From: Wardex Date: Tue, 28 Apr 2026 12:26:12 +0300 Subject: [PATCH 5/6] ScpRestrictMovementOnVisibility --- ...ityComponent => ScpRestrictMovementOnVisibilityComponent.cs} | 1 + ...bilitySystem => SharedRestrictMovementOnVisibilitySystem.cs} | 2 ++ 2 files changed, 3 insertions(+) rename Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/{ScpRestrictMovementOnVisibilityComponent => ScpRestrictMovementOnVisibilityComponent.cs} (96%) rename Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/{SharedScpRestrictMovementOnVisibilitySystem => SharedRestrictMovementOnVisibilitySystem.cs} (98%) diff --git a/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/ScpRestrictMovementOnVisibilityComponent b/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/ScpRestrictMovementOnVisibilityComponent.cs similarity index 96% rename from Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/ScpRestrictMovementOnVisibilityComponent rename to Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/ScpRestrictMovementOnVisibilityComponent.cs index 6699e295ffc..d445117fcce 100644 --- a/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/ScpRestrictMovementOnVisibilityComponent +++ b/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/ScpRestrictMovementOnVisibilityComponent.cs @@ -9,6 +9,7 @@ public sealed partial class ScpRestrictMovementOnVisibilityComponent : Component [DataField] public EntityWhitelist? ContainersMoveWhitelist; + [DataField] public EntityWhitelist? ContainersMoveBlacklist; [DataField] diff --git a/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/SharedScpRestrictMovementOnVisibilitySystem b/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/SharedRestrictMovementOnVisibilitySystem.cs similarity index 98% rename from Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/SharedScpRestrictMovementOnVisibilitySystem rename to Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/SharedRestrictMovementOnVisibilitySystem.cs index 735495c5b9d..75bb7a09777 100644 --- a/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/SharedScpRestrictMovementOnVisibilitySystem +++ b/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/SharedRestrictMovementOnVisibilitySystem.cs @@ -28,6 +28,8 @@ public override void Initialize() SubscribeLocalEvent(OnMoveAttempt); SubscribeLocalEvent(OnMoveInput); SubscribeLocalEvent(OnMove); + + _insideQuery = GetEntityQuery(); } public void OnAttackAttempt(Entity ent, ref AttackAttemptEvent args) From b3c04eb0f4c47415fd394151058c0725504e880f Mon Sep 17 00:00:00 2001 From: Wardex Date: Tue, 28 Apr 2026 15:43:44 +0300 Subject: [PATCH 6/6] =?UTF-8?q?=D0=BF=D1=83=D0=BF=D1=83=D0=BF=D1=83...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_Scp/Other/ScpClog/ScpClogSystem.cs | 123 ++++++++++++ ...rtifactScp173BlindEveryoneInRangeSystem.cs | 6 +- Content.Server/_Scp/Scp173/Scp173System.cs | 66 +------ .../ScpBlind/ScpBlindActionSerializable.cs | 10 + .../ScpBlind/ScpBlindAllowerComponent.cs | 10 + .../ScpBlind/ScpBlindBlockerComponent.cs | 10 + .../_Scp/Other/ScpBlind/ScpBlindComponent.cs | 50 +++++ .../Other/ScpBlind/SharedScpBlindSystem.cs | 181 ++++++++++++++++++ .../_Scp/Other/ScpClog/ScpClogActionEvent.cs | 4 + .../_Scp/Other/ScpClog/ScpClogComponent.cs | 38 ++++ .../_Scp/Scp173/SharedScp173System.cs | 147 +++----------- .../BlindOnTrigger/BlindOnTriggerSystem.cs | 6 +- Resources/Prototypes/_Scp/Actions/scp173.yml | 4 +- .../Prototypes/_Scp/Entities/Markers/scp.yml | 2 + .../Entities/Mobs/Player/Scp/Main/scp173.yml | 13 +- 15 files changed, 470 insertions(+), 200 deletions(-) create mode 100644 Content.Server/_Scp/Other/ScpClog/ScpClogSystem.cs create mode 100644 Content.Shared/_Scp/Other/ScpBlind/ScpBlindActionSerializable.cs create mode 100644 Content.Shared/_Scp/Other/ScpBlind/ScpBlindAllowerComponent.cs create mode 100644 Content.Shared/_Scp/Other/ScpBlind/ScpBlindBlockerComponent.cs create mode 100644 Content.Shared/_Scp/Other/ScpBlind/ScpBlindComponent.cs create mode 100644 Content.Shared/_Scp/Other/ScpBlind/SharedScpBlindSystem.cs create mode 100644 Content.Shared/_Scp/Other/ScpClog/ScpClogActionEvent.cs create mode 100644 Content.Shared/_Scp/Other/ScpClog/ScpClogComponent.cs diff --git a/Content.Server/_Scp/Other/ScpClog/ScpClogSystem.cs b/Content.Server/_Scp/Other/ScpClog/ScpClogSystem.cs new file mode 100644 index 00000000000..a69580e738d --- /dev/null +++ b/Content.Server/_Scp/Other/ScpClog/ScpClogSystem.cs @@ -0,0 +1,123 @@ +using Content.Shared.Popups; +using Content.Shared._Scp.Other.ScpClog; +using Content.Shared._Scp.Watching; +using Content.Shared.Storage.Components; +using Content.Shared.Whitelist; +using Robust.Server.Containers; +using Content.Server.Popups; +using Content.Server.Fluids.EntitySystems; +using Robust.Server.Audio; +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Chemistry.Components; +using Content.Shared._Scp.Helpers; +using Content.Shared._Scp.Proximity; +using System.Linq; +using Content.Server.Examine; +using Content.Shared.Doors.Components; +using Content.Server.Doors.Systems; +using Content.Shared.Lock; +using Content.Shared._Scp.Other.BunkerMarker; +using Content.Server.Explosion.EntitySystems; +using Robust.Server.GameObjects; +using Content.Shared.Explosion.EntitySystems; + +namespace Content.Server._Scp.Other.ScpClog; + +public sealed class ScpClogSystem : EntitySystem +{ + [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly EyeWatchingSystem _watching = default!; + [Dependency] private readonly ContainerSystem _containerSystem = default!; + [Dependency] private readonly PuddleSystem _puddle = default!; + [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly ExamineSystem _examine = default!; + [Dependency] private readonly ExplosionSystem _explosion = default!; + [Dependency] private readonly DoorSystem _door = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly ScpHelpers _helpers = default!; + [Dependency] private readonly LockSystem _lock = default!; + + private EntityQuery _insideQuery; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnClog); + + _insideQuery = GetEntityQuery(); + } + + private void OnClog(Entity ent, ref ScpClogActionEvent args) + { + if (args.Handled) + return; + + if (IsInContainer(ent, out var storage)) + { + var message = Loc.GetString("scp-cage-suppress-ability", ("container", Name(storage.Value))); + _popup.PopupEntity(message, ent, ent, PopupType.LargeCaution); + + return; + } + + if (_watching.IsWatchedByAny(ent)) + { + var message = Loc.GetString("scp173-fast-movement-too-many-watchers"); + _popup.PopupEntity(message, ent, ent, PopupType.LargeCaution); + return; + } + + var coords = Transform(ent).Coordinates; + + var tempSol = new Solution(); + tempSol.AddReagent(ent.Comp.Reagent, 25); + _puddle.TrySpillAt(coords, tempSol, out _, false); + + _audio.PlayPvs(ent.Comp.ClogSound, ent); + + var total = _helpers.GetAroundSolutionVolume(ent, ent.Comp.Reagent, LineOfSightBlockerLevel.None); + + if (total >= ent.Comp.MinTotalSolutionVolume) + { + var lookup = _lookup.GetEntitiesInRange(coords, ent.Comp.ClogDeconstructEffectRadius, flags: LookupFlags.Dynamic | LookupFlags.Static) + .Where(target => _examine.InRangeUnOccluded(ent, target, ent.Comp.ClogDeconstructEffectRadius)); + + foreach (var target in lookup) + { + if (TryComp(target, out var doorBoltComp) && doorBoltComp.BoltsDown) + _door.SetBoltsDown((target, doorBoltComp), false, predicted: true); + + if (TryComp(target, out var lockComp) && lockComp.Locked) + _lock.Unlock(target, args.Performer, lockComp); + + if (TryComp(target, out var doorComp) && doorComp.State is not DoorState.Open && !HasComp(target)) + _door.StartOpening(target); + } + } + if (total >= ent.Comp.ExtraMinTotalSolutionVolume) + { + _explosion.QueueExplosion(_transform.GetMapCoordinates(ent), SharedExplosionSystem.DefaultExplosionPrototypeId, 300f, 0.6f, 50f, ent); + } + + args.Handled = true; + } + + public bool IsInContainer(Entity ent, [NotNullWhen(true)] out EntityUid? storage) + { + storage = null; + + if (!_insideQuery.TryComp(ent, out var insideEntityStorageComponent)) + return false; + + if (!_containerSystem.TryGetContainingContainer(ent.Owner, out var container)) + return false; + + if (!_whitelist.CheckBoth(container.Owner, ent.Comp.InContainersClogBlacklist, ent.Comp.InContainersClogWhitelist)) + return false; + + storage = insideEntityStorageComponent.Storage; + return true; + } +} diff --git a/Content.Server/_Scp/Research/Artifacts/Effects/_ScpSpecific/Scp173/Blind/ArtifactScp173BlindEveryoneInRangeSystem.cs b/Content.Server/_Scp/Research/Artifacts/Effects/_ScpSpecific/Scp173/Blind/ArtifactScp173BlindEveryoneInRangeSystem.cs index 8ef4dc4925d..2b78f0025e6 100644 --- a/Content.Server/_Scp/Research/Artifacts/Effects/_ScpSpecific/Scp173/Blind/ArtifactScp173BlindEveryoneInRangeSystem.cs +++ b/Content.Server/_Scp/Research/Artifacts/Effects/_ScpSpecific/Scp173/Blind/ArtifactScp173BlindEveryoneInRangeSystem.cs @@ -1,4 +1,4 @@ -using Content.Server._Scp.Scp173; +using Content.Shared._Scp.Other.ScpBlind; using Content.Shared.Xenoarchaeology.Artifact; using Content.Shared.Xenoarchaeology.Artifact.XAE; @@ -6,10 +6,10 @@ namespace Content.Server._Scp.Research.Artifacts.Effects._ScpSpecific.Scp173.Bli public sealed class ArtifactScp173BlindEveryoneInRangeSystem : BaseXAESystem { - [Dependency] private readonly Scp173System _scp173 = default!; + [Dependency] private readonly SharedScpBlindSystem _scpBlind = default!; protected override void OnActivated(Entity ent, ref XenoArtifactNodeActivatedEvent args) { - _scp173.BlindEveryoneInRange(ent, ent.Comp.Time, false); + _scpBlind.BlindEveryoneInRange(ent, ent.Comp.Time, false); } } diff --git a/Content.Server/_Scp/Scp173/Scp173System.cs b/Content.Server/_Scp/Scp173/Scp173System.cs index 9576354cbf0..f47efbabf90 100644 --- a/Content.Server/_Scp/Scp173/Scp173System.cs +++ b/Content.Server/_Scp/Scp173/Scp173System.cs @@ -2,9 +2,6 @@ using System.Linq; using System.Numerics; using Content.Server.Doors.Systems; -using Content.Server.Examine; -using Content.Server.Explosion.EntitySystems; -using Content.Server.Fluids.EntitySystems; using Content.Server.Ghost; using Content.Server.Interaction; using Content.Server.Popups; @@ -14,13 +11,11 @@ using Content.Shared._Scp.Other.DamageOnCollide; using Content.Shared._Scp.Proximity; using Content.Shared._Scp.Scp173; -using Content.Shared.Chemistry.Components; using Content.Shared.Coordinates.Helpers; using Content.Shared.Damage; using Content.Shared.Damage.Systems; using Content.Shared.Doors.Components; using Content.Shared.Examine; -using Content.Shared.Explosion.EntitySystems; using Content.Shared.Light.Components; using Content.Shared.Lock; using Content.Shared.Physics; @@ -43,20 +38,16 @@ public sealed partial class Scp173System : SharedScp173System [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly DamageableSystem _damageable = default!; - [Dependency] private readonly PuddleSystem _puddle = default!; [Dependency] private readonly LockSystem _lock = default!; [Dependency] private readonly DoorSystem _door = default!; - [Dependency] private readonly ExamineSystem _examine = default!; [Dependency] private readonly InteractionSystem _interaction = default!; [Dependency] private readonly PhysicsSystem _physics = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly AudioSystem _audio= default!; - [Dependency] private readonly ExplosionSystem _explosion = default!; [Dependency] private readonly ScpHelpers _helpers = default!; [Dependency] private readonly ScpDamageOnCollideSystem _damageOnCollide = default!; private readonly SoundSpecifier _storageOpenSound = new SoundCollectionSpecifier("MetalBreak"); - private readonly SoundSpecifier _clogSound = new SoundPathSpecifier("/Audio/_Scp/Scp173/clog.ogg"); private const float ToggleDoorStuffChance = 0.35f; @@ -67,7 +58,7 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnStructureDamage); - SubscribeLocalEvent(OnClog); + SubscribeLocalEvent(OnFastMovement); } @@ -173,61 +164,6 @@ private void OnStructureDamage(Entity uid, ref Scp173DamageStru args.Handled = true; } - private void OnClog(Entity ent, ref Scp173ClogAction args) - { - if (args.Handled) - return; - - if (IsInScpCage(ent, out var storage)) - { - var message = Loc.GetString("scp-cage-suppress-ability", ("container", Name(storage.Value))); - _popup.PopupEntity(message, ent, ent, PopupType.LargeCaution); - - return; - } - - if (Watching.IsWatchedByAny(ent)) - { - var message = Loc.GetString("scp173-fast-movement-too-many-watchers"); - _popup.PopupEntity(message, ent, ent, PopupType.LargeCaution); - return; - } - - var coords = Transform(ent).Coordinates; - - var tempSol = new Solution(); - tempSol.AddReagent(Scp173Component.Reagent, 25); - _puddle.TrySpillAt(coords, tempSol, out _, false); - - _audio.PlayPvs(_clogSound, ent); - - var total = _helpers.GetAroundSolutionVolume(ent, Scp173Component.Reagent, LineOfSightBlockerLevel.None); - - if (total >= Scp173Component.MinTotalSolutionVolume) - { - var lookup = _lookup.GetEntitiesInRange(coords, ContainmentRoomSearchRadius, flags: LookupFlags.Dynamic | LookupFlags.Static) - .Where(target => _examine.InRangeUnOccluded(ent, target, ContainmentRoomSearchRadius)); - - foreach (var target in lookup) - { - if (TryComp(target, out var doorBoltComp) && doorBoltComp.BoltsDown) - _door.SetBoltsDown((target, doorBoltComp), false, predicted: true); - - if (TryComp(target, out var lockComp) && lockComp.Locked) - _lock.Unlock(target, args.Performer, lockComp); - - if (TryComp(target, out var doorComp) && doorComp.State is not DoorState.Open && !HasComp(target)) - _door.StartOpening(target); - } - } - if (total >= Scp173Component.ExtraMinTotalSolutionVolume) - { - _explosion.QueueExplosion(_transform.GetMapCoordinates(ent), SharedExplosionSystem.DefaultExplosionPrototypeId, 300f, 0.6f, 50f, ent); - } - - args.Handled = true; - } - /// /// Обработчик способности быстрого перемещения (прыжка) SCP-173. /// Проверяет все условия, запрещающие прыжок, рассчитывает конечную позицию diff --git a/Content.Shared/_Scp/Other/ScpBlind/ScpBlindActionSerializable.cs b/Content.Shared/_Scp/Other/ScpBlind/ScpBlindActionSerializable.cs new file mode 100644 index 00000000000..13d2d23ced1 --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpBlind/ScpBlindActionSerializable.cs @@ -0,0 +1,10 @@ +using Content.Shared.Actions; +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared._Scp.Other.ScpBlind; + +public sealed partial class ScpBlindActionEvent : InstantActionEvent; + +[Serializable, NetSerializable] +public sealed partial class ScpActionStartBlind : SimpleDoAfterEvent; diff --git a/Content.Shared/_Scp/Other/ScpBlind/ScpBlindAllowerComponent.cs b/Content.Shared/_Scp/Other/ScpBlind/ScpBlindAllowerComponent.cs new file mode 100644 index 00000000000..cd12cd2b923 --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpBlind/ScpBlindAllowerComponent.cs @@ -0,0 +1,10 @@ + +using Robust.Shared.GameStates; + +namespace Content.Shared._Scp.Other.ScpBlind; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ScpBlindAllowerComponent : Component +{ + +} diff --git a/Content.Shared/_Scp/Other/ScpBlind/ScpBlindBlockerComponent.cs b/Content.Shared/_Scp/Other/ScpBlind/ScpBlindBlockerComponent.cs new file mode 100644 index 00000000000..5a23bd65915 --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpBlind/ScpBlindBlockerComponent.cs @@ -0,0 +1,10 @@ + +using Robust.Shared.GameStates; + +namespace Content.Shared._Scp.Other.ScpBlind; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ScpBlindBlockerComponent : Component +{ + +} diff --git a/Content.Shared/_Scp/Other/ScpBlind/ScpBlindComponent.cs b/Content.Shared/_Scp/Other/ScpBlind/ScpBlindComponent.cs new file mode 100644 index 00000000000..e94d3f7acd8 --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpBlind/ScpBlindComponent.cs @@ -0,0 +1,50 @@ + +using Content.Shared.Actions.Components; +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._Scp.Other.ScpBlind; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ScpBlindComponent : Component +{ + [DataField] + public EntProtoId ActionProto = "Scp173Blind"; + + /// + /// Время, через которое начнется ослепление после активации способности + /// + [DataField] + public TimeSpan StartBlindTime = TimeSpan.FromSeconds(12f); + + /// + /// Время ослепления после успешного применения способности + /// + [DataField] + public TimeSpan BlindnessTime = TimeSpan.FromSeconds(7); + + [DataField] + public float? MinWatchersToBlind; + + [DataField] + public float SearchAllowerRadius = 8f; + + [DataField] + public float SearchBlockerRadius = 8f; + + [DataField] + public bool MustBeAllowedToBlind = false; + + [DataField] + public bool IgnoreBlockers = false; + + [DataField] + public EntityWhitelist? InContainersBlindWhitelist; + + [DataField] + public EntityWhitelist? InContainersBlindBlacklist; + + [ViewVariables, AutoNetworkedField] + public EntityUid? ActionEnt; +} diff --git a/Content.Shared/_Scp/Other/ScpBlind/SharedScpBlindSystem.cs b/Content.Shared/_Scp/Other/ScpBlind/SharedScpBlindSystem.cs new file mode 100644 index 00000000000..0a768c660c5 --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpBlind/SharedScpBlindSystem.cs @@ -0,0 +1,181 @@ + +using System.Diagnostics.CodeAnalysis; +using Content.Shared._Scp.Blinking; +using Content.Shared._Scp.Helpers; +using Content.Shared._Scp.Proximity; +using Content.Shared._Scp.Watching; +using Content.Shared.Actions; +using Content.Shared.DoAfter; +using Content.Shared.Popups; +using Content.Shared.Storage.Components; +using Content.Shared.Whitelist; +using Robust.Shared.Containers; + +namespace Content.Shared._Scp.Other.ScpBlind; + +public sealed class SharedScpBlindSystem : EntitySystem +{ + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly EyeWatchingSystem _watching = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; + [Dependency] private readonly SharedBlinkingSystem _blinking = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + private EntityQuery _insideQuery; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + + SubscribeLocalEvent(OnStartedBlind); + SubscribeLocalEvent(OnBlind); + + _insideQuery = GetEntityQuery(); + } + + private void OnInit(Entity ent, ref ComponentInit args) + { + var actionEnt = _actionsSystem.AddAction(ent, ent.Comp.ActionProto); + ent.Comp.ActionEnt = actionEnt; + Dirty(ent); + } + + private void OnShutdown(Entity ent, ref ComponentShutdown args) + { + _actionsSystem.RemoveAction(ent.Owner, ent.Comp.ActionEnt); + ent.Comp.ActionEnt = null; + Dirty(ent); + } + + public void OnStartedBlind(Entity ent, ref ScpBlindActionEvent args) + { + if (args.Handled) + return; + + if (!CanBlind(ent)) + return; + + var doAfterEventArgs = new DoAfterArgs(EntityManager, args.Performer, ent.Comp.StartBlindTime, new ScpActionStartBlind(), args.Performer) + { + Hidden = true, + RequireCanInteract = false, + }; + + args.Handled = _doAfter.TryStartDoAfter(doAfterEventArgs); + } + + public void OnBlind(Entity ent, ref ScpActionStartBlind args) + { + if (args.Handled || args.Cancelled) + return; + + if (!CanBlind(ent)) + return; + + // По причине акшена это не предиктится. + // Активация акшена у игрока не предугадывается другими игроками. Параша + BlindEveryoneInRange(ent, ent.Comp.BlindnessTime, false); + args.Handled = true; + } + + public void BlindEveryoneInRange(EntityUid scp, TimeSpan time, bool predicted = true) + { + using var blinkableList = ListPoolEntity.Rent(); + if (!_watching.TryGetAllEntitiesVisibleTo(scp, blinkableList.Value, flags: LookupFlags.Dynamic | LookupFlags.Approximate)) + return; + + foreach (var eye in blinkableList.Value) + { + _blinking.ForceBlind(eye.AsNullable(), time, predicted); + } + + // TODO: Add sound. + } + + private bool CanBlind(Entity ent, bool showPopups = true) + { + if (ent.Comp.MustBeAllowedToBlind && !IsAllowed(ent, ent.Comp.SearchAllowerRadius)) + { + if (showPopups) + _popup.PopupClient(Loc.GetString("scp173-blind-failed-not-in-chamber"), ent, ent); + + return false; + } + + if (IsBlocked(ent, ent.Comp.SearchBlockerRadius) && !ent.Comp.IgnoreBlockers) + { + if (showPopups) + _popup.PopupClient(Loc.GetString("scp173-blind-failed-not-in-chamber"), ent, ent); + + return false; + } + + if (IsInContainer(ent, out var container)) + { + if (showPopups) + _popup.PopupClient(Loc.GetString("scp-cage-suppress-ability", ("container", Name(container.Value))), ent, ent); + + return false; + } + + if (ent.Comp.MinWatchersToBlind == null) // Пропускаем проверку количества смотрящих, если не указан минимум + return true; + + if (!_watching.TryGetWatchers(ent, out var watchers)) + { + if (showPopups) + _popup.PopupClient(Loc.GetString("scp173-blind-failed-too-few-watchers"), ent, ent); + + return false; + } + + if (watchers < ent.Comp.MinWatchersToBlind) + { + if (showPopups) + _popup.PopupClient(Loc.GetString("scp173-blind-failed-too-few-watchers"), ent, ent); + + return false; + } + + return true; + } + + public bool IsInContainer(Entity ent, [NotNullWhen(true)] out EntityUid? storage) + { + storage = null; + + if (!_insideQuery.TryComp(ent, out var insideEntityStorageComponent)) + return false; + + if (!_containerSystem.TryGetContainingContainer(ent.Owner, out var container)) + return false; + + if (!_whitelist.CheckBoth(container.Owner, ent.Comp.InContainersBlindBlacklist, ent.Comp.InContainersBlindWhitelist)) + return false; + + storage = insideEntityStorageComponent.Storage; + return true; + } + + public bool IsAllowed(EntityUid uid, float radius) + { + return _watching.TryGetAnyEntitiesVisibleTo(uid, + LineOfSightBlockerLevel.None, + LookupFlags.Sensors | LookupFlags.Sundries, + radius + ); + } + + public bool IsBlocked(EntityUid uid, float radius) + { + return _watching.TryGetAnyEntitiesVisibleTo(uid, + LineOfSightBlockerLevel.None, + LookupFlags.Sensors | LookupFlags.Sundries, + radius); + } +} diff --git a/Content.Shared/_Scp/Other/ScpClog/ScpClogActionEvent.cs b/Content.Shared/_Scp/Other/ScpClog/ScpClogActionEvent.cs new file mode 100644 index 00000000000..25442fddb17 --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpClog/ScpClogActionEvent.cs @@ -0,0 +1,4 @@ +using Content.Shared.Actions; + +namespace Content.Shared._Scp.Other.ScpClog; +public sealed partial class ScpClogActionEvent : InstantActionEvent; diff --git a/Content.Shared/_Scp/Other/ScpClog/ScpClogComponent.cs b/Content.Shared/_Scp/Other/ScpClog/ScpClogComponent.cs new file mode 100644 index 00000000000..d7a8ff42356 --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpClog/ScpClogComponent.cs @@ -0,0 +1,38 @@ +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Whitelist; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._Scp.Other.ScpClog; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ScpClogComponent : Component +{ + [DataField] + public ProtoId Reagent = "Scp173Reagent"; + + [DataField] + public SoundPathSpecifier ClogSound = new("/Audio/_Scp/Scp173/clog.ogg"); + + [DataField] + public float ClogDeconstructEffectRadius = 8f; + + [DataField] + public EntityWhitelist? InContainersClogWhitelist; + + [DataField] + public EntityWhitelist? InContainersClogBlacklist; + + /// + /// Количество реагента, которое необходимо накопить вокруг, засорение открывало шлюзы вокруг. + /// + [DataField] + public int MinTotalSolutionVolume = 600; + + /// + /// Количество реагента, которое необходимо накопить вокруг, чтобы начать взрываться при засорении + /// + [DataField] + public int ExtraMinTotalSolutionVolume = 900; +} diff --git a/Content.Shared/_Scp/Scp173/SharedScp173System.cs b/Content.Shared/_Scp/Scp173/SharedScp173System.cs index b1905fcaf23..8f9d6872ed4 100644 --- a/Content.Shared/_Scp/Scp173/SharedScp173System.cs +++ b/Content.Shared/_Scp/Scp173/SharedScp173System.cs @@ -37,131 +37,10 @@ public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnStartedBlind); - SubscribeLocalEvent(OnBlind); - _insideQuery = GetEntityQuery(); _scpCageQuery = GetEntityQuery(); } - #region Abillities - - private void OnStartedBlind(Entity ent, ref Scp173BlindAction args) - { - if (args.Handled) - return; - - if (!CanBlind(ent)) - return; - - var doAfterEventArgs = new DoAfterArgs(EntityManager, args.Performer, ent.Comp.StartBlindTime, new Scp173StartBlind(), args.Performer) - { - Hidden = true, - RequireCanInteract = false, - }; - - args.Handled = _doAfter.TryStartDoAfter(doAfterEventArgs); - } - - private void OnBlind(Entity ent, ref Scp173StartBlind args) - { - if (args.Handled || args.Cancelled) - return; - - if (!CanBlind(ent)) - return; - - // По причине акшена это не предиктится. - // Активация акшена у игрока не предугадывается другими игроками. Параша - BlindEveryoneInRange(ent, ent.Comp.BlindnessTime, false); - args.Handled = true; - } - - #endregion - - #region Public API - - public void BlindEveryoneInRange(EntityUid scp, TimeSpan time, bool predicted = true) - { - using var blinkableList = ListPoolEntity.Rent(); - if (!Watching.TryGetAllEntitiesVisibleTo(scp, blinkableList.Value, flags: LookupFlags.Dynamic | LookupFlags.Approximate)) - return; - - foreach (var eye in blinkableList.Value) - { - _blinking.ForceBlind(eye.AsNullable(), time, predicted); - } - - // TODO: Add sound. - } - - /// - /// Находится ли 173 в контейнере для перевозки - /// - public bool IsInScpCage(EntityUid uid, [NotNullWhen(true)] out EntityUid? storage) - { - storage = null; - - if (_insideQuery.TryComp(uid, out var insideEntityStorageComponent) && - _scpCageQuery.HasComp(insideEntityStorageComponent.Storage)) - { - storage = insideEntityStorageComponent.Storage; - return true; - } - - return false; - } - - /// - /// Находится ли 173 в своей камере. Проверяется по наличию рядом спавнера работы - /// - public bool IsContained(EntityUid uid) - { - return Watching.TryGetAnyEntitiesVisibleTo(uid, - LineOfSightBlockerLevel.None, - LookupFlags.Sensors | LookupFlags.Sundries, - ContainmentRoomSearchRadius); - } - - private bool CanBlind(EntityUid uid, bool showPopups = true) - { - if (!IsContained(uid)) - { - if (showPopups) - _popup.PopupClient(Loc.GetString("scp173-blind-failed-not-in-chamber"), uid, uid); - - return false; - } - - if (IsInScpCage(uid, out var cage)) - { - if (showPopups) - _popup.PopupClient(Loc.GetString("scp-cage-suppress-ability", ("container", Name(cage.Value))), uid, uid); - - return false; - } - - if (!Watching.TryGetWatchers(uid, out var watchers)) - { - if (showPopups) - _popup.PopupClient(Loc.GetString("scp173-blind-failed-too-few-watchers"), uid, uid); - - return false; - } - - if (watchers <= 3) - { - if (showPopups) - _popup.PopupClient(Loc.GetString("scp173-blind-failed-too-few-watchers"), uid, uid); - - return false; - } - - return true; - } - - #endregion - #region Jump Helpers /// @@ -200,4 +79,30 @@ protected static void ClampTargetToRange(MapCoordinates performerCoords, ref Map } #endregion + + + public bool IsInScpCage(EntityUid uid, [NotNullWhen(true)] out EntityUid? storage) + { + storage = null; + + if (_insideQuery.TryComp(uid, out var insideEntityStorageComponent) && + _scpCageQuery.HasComp(insideEntityStorageComponent.Storage)) + { + storage = insideEntityStorageComponent.Storage; + return true; + } + + return false; + } + + /// + /// Находится ли 173 в своей камере. Проверяется по наличию рядом спавнера работы + /// + public bool IsContained(EntityUid uid) + { + return Watching.TryGetAnyEntitiesVisibleTo(uid, + LineOfSightBlockerLevel.None, + LookupFlags.Sensors | LookupFlags.Sundries, + ContainmentRoomSearchRadius); + } } diff --git a/Content.Shared/_Scp/Trigger/BlindOnTrigger/BlindOnTriggerSystem.cs b/Content.Shared/_Scp/Trigger/BlindOnTrigger/BlindOnTriggerSystem.cs index b6b102f59c5..7b3ca36b30b 100644 --- a/Content.Shared/_Scp/Trigger/BlindOnTrigger/BlindOnTriggerSystem.cs +++ b/Content.Shared/_Scp/Trigger/BlindOnTrigger/BlindOnTriggerSystem.cs @@ -1,11 +1,11 @@ -using Content.Shared._Scp.Scp173; +using Content.Shared._Scp.Other.ScpBlind; using Content.Shared.Trigger; namespace Content.Shared._Scp.Trigger.BlindOnTrigger; public sealed class BlindOnTriggerSystem : EntitySystem { - [Dependency] private readonly SharedScp173System _scp173 = default!; + [Dependency] private readonly SharedScpBlindSystem _scpBlind = default!; public override void Initialize() { @@ -19,6 +19,6 @@ private void OnTrigger(Entity ent, ref TriggerEvent arg if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) return; - _scp173.BlindEveryoneInRange(ent, ent.Comp.Time, false); + _scpBlind.BlindEveryoneInRange(ent, ent.Comp.Time, false); } } diff --git a/Resources/Prototypes/_Scp/Actions/scp173.yml b/Resources/Prototypes/_Scp/Actions/scp173.yml index 0afebd5fba9..f7d646bf8c2 100644 --- a/Resources/Prototypes/_Scp/Actions/scp173.yml +++ b/Resources/Prototypes/_Scp/Actions/scp173.yml @@ -12,7 +12,7 @@ itemIconStyle: BigAction raiseOnUser: true - type: InstantAction - event: !type:Scp173BlindAction + event: !type:ScpBlindActionEvent - type: entity id: Scp173Clog @@ -28,7 +28,7 @@ itemIconStyle: BigAction raiseOnUser: true - type: InstantAction - event: !type:Scp173ClogAction + event: !type:ScpClogActionEvent - type: SafeTimeRestricted - type: entity diff --git a/Resources/Prototypes/_Scp/Entities/Markers/scp.yml b/Resources/Prototypes/_Scp/Entities/Markers/scp.yml index 92702784837..e8128e7a5d5 100644 --- a/Resources/Prototypes/_Scp/Entities/Markers/scp.yml +++ b/Resources/Prototypes/_Scp/Entities/Markers/scp.yml @@ -16,6 +16,7 @@ - type: PreferredSpawn preferredSpawnTypes: [ Job ] - type: Scp173BlockStructureDamage + - type: ScpBlindAllower - type: entity # Для особых случаев name: SCP-173 structure damage blocker @@ -28,6 +29,7 @@ - sprite: _Scp/Mobs/Scp/scp-173.rsi state: scp-173 - type: Scp173BlockStructureDamage + - type: ScpBlindBlocker - type: entity name: SCP-096 spawner diff --git a/Resources/Prototypes/_Scp/Entities/Mobs/Player/Scp/Main/scp173.yml b/Resources/Prototypes/_Scp/Entities/Mobs/Player/Scp/Main/scp173.yml index 538fea85dda..a1fd06ce93f 100644 --- a/Resources/Prototypes/_Scp/Entities/Mobs/Player/Scp/Main/scp173.yml +++ b/Resources/Prototypes/_Scp/Entities/Mobs/Player/Scp/Main/scp173.yml @@ -23,6 +23,13 @@ containersMoveWhitelist: components: - ScpCage + - type: ScpBlind + mustBeAllowedToBlind: true + minWatchersToBlind: 3 + inContainersBlindBlacklist: + components: + - ScpCage + - type: ScpClog - type: ShowBlinkable - type: XenoArtifact effectsTable: !type:GroupSelector @@ -114,12 +121,6 @@ - type: SpeedModifierContactCapClothing maxContactSprintSlowdown: 1 maxContactWalkSlowdown: 1 - - type: ActionGrant - actions: - - Scp173Blind - - Scp173Clog - - Scp173DamageStructure - - Scp173FastMovement - type: FootstepModifier footstepSoundCollection: collection: FootstepScp173Classic