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/Other/ScpRestrictMovementOnVisibility/ScpRestrictMovementOnVisibilityComponent.cs b/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/ScpRestrictMovementOnVisibilityComponent.cs new file mode 100644 index 00000000000..d445117fcce --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/ScpRestrictMovementOnVisibilityComponent.cs @@ -0,0 +1,17 @@ +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; + + [DataField] + public EntityWhitelist? ContainersMoveBlacklist; + + [DataField] + public float ContainmentRoomSearchRadius = 8f; +} diff --git a/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/SharedRestrictMovementOnVisibilitySystem.cs b/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/SharedRestrictMovementOnVisibilitySystem.cs new file mode 100644 index 00000000000..75bb7a09777 --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpRestrictMovementOnVisibility/SharedRestrictMovementOnVisibilitySystem.cs @@ -0,0 +1,106 @@ +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); + + _insideQuery = GetEntityQuery(); + } + + 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; + } +} diff --git a/Content.Shared/_Scp/Scp173/SharedScp173System.cs b/Content.Shared/_Scp/Scp173/SharedScp173System.cs index 6d79abb9ac0..8f9d6872ed4 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,133 +37,50 @@ public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnAttackAttempt); - - SubscribeLocalEvent(OnDirectionAttempt); - SubscribeLocalEvent(OnMoveAttempt); - SubscribeLocalEvent(OnMoveInput); - SubscribeLocalEvent(OnMove); - - SubscribeLocalEvent(OnStartedBlind); - SubscribeLocalEvent(OnBlind); - _insideQuery = GetEntityQuery(); _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(); - } + #region Jump Helpers - private void OnMoveAttempt(Entity ent, ref UpdateCanMoveEvent args) + /// + /// Проверяет, является ли сущность непроходимым препятствием. + /// Используется для определения, нужно ли остановить прыжок SCP-173 при столкновении. + /// + protected bool IsImpassableObstacle(EntityUid entity) { - // В клетке можно двигаться - if (IsInScpCage(ent, out _)) - return; - - if (!Watching.IsWatchedByAny(ent, useTimeCompensation: true)) - return; + if (!TryComp(entity, out var physics)) + return false; - args.Cancel(); - } + if (!physics.Hard) + return false; - private void OnMoveInput(Entity ent, ref MoveInputEvent args) - { - // Метод подвязанный на MoveInputEvent так же нужен, вместе с методом на MoveEvent - // Этот метод исправляет проблему, когда 173 должен мочь двинуться, но ему об этом никто не сказал - // То есть последний вопрос от 173 МОГУ ЛИ Я ДВИНУТЬСЯ был когда он еще мог двинуться, через MoveEvent - // Потом он перестал мочь, и следственно больше НЕ МОЖЕТ задать вопрос, может они двинуться - // Это фикслось в игре сменой направления спрайта мышкой - // Но данный метод как раз будет спрашивать у 173, может ли он сдвинуться, когда как раз не двигается - _blocker.UpdateCanMove(ent); - } + var layer = (CollisionGroup) physics.CollisionLayer; - private void OnMove(Entity ent, ref MoveEvent args) - { - _blocker.UpdateCanMove(ent); + return layer.HasFlag(CollisionGroup.WallLayer) + || layer.HasFlag(CollisionGroup.GlassAirlockLayer) + || layer.HasFlag(CollisionGroup.HumanoidBlockLayer); } - #endregion - - #region Abillities - - private void OnStartedBlind(Entity ent, ref Scp173BlindAction args) + /// + /// Ограничивает координаты цели прыжка до максимальной дальности. + /// Если цель дальше , координаты обрезаются. + /// + protected static void ClampTargetToRange(MapCoordinates performerCoords, ref MapCoordinates targetCoords, float maxRange) { - if (args.Handled) - return; - - if (!CanBlind(ent)) - return; + var direction = targetCoords.Position - performerCoords.Position; + var distance = direction.Length(); - var doAfterEventArgs = new DoAfterArgs(EntityManager, args.Performer, ent.Comp.StartBlindTime, new Scp173StartBlind(), args.Performer) + if (distance > maxRange) { - 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; + direction = Vector2.Normalize(direction) * maxRange; + targetCoords = performerCoords.Offset(direction); + } } #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; @@ -190,82 +105,4 @@ public bool IsContained(EntityUid uid) 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 - - /// - /// Проверяет, является ли сущность непроходимым препятствием. - /// Используется для определения, нужно ли остановить прыжок SCP-173 при столкновении. - /// - protected bool IsImpassableObstacle(EntityUid entity) - { - if (!TryComp(entity, out var physics)) - return false; - - if (!physics.Hard) - return false; - - var layer = (CollisionGroup) physics.CollisionLayer; - - return layer.HasFlag(CollisionGroup.WallLayer) - || layer.HasFlag(CollisionGroup.GlassAirlockLayer) - || layer.HasFlag(CollisionGroup.HumanoidBlockLayer); - } - - /// - /// Ограничивает координаты цели прыжка до максимальной дальности. - /// Если цель дальше , координаты обрезаются. - /// - protected static void ClampTargetToRange(MapCoordinates performerCoords, ref MapCoordinates targetCoords, float maxRange) - { - var direction = targetCoords.Position - performerCoords.Position; - var distance = direction.Length(); - - if (distance > maxRange) - { - direction = Vector2.Normalize(direction) * maxRange; - targetCoords = performerCoords.Offset(direction); - } - } - - #endregion } 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 c76d5713a27..a1fd06ce93f 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,17 @@ - type: Scp class: Euclid - type: Scp173 + - type: ScpRestrictMovementOnVisibility + containersMoveWhitelist: + components: + - ScpCage + - type: ScpBlind + mustBeAllowedToBlind: true + minWatchersToBlind: 3 + inContainersBlindBlacklist: + components: + - ScpCage + - type: ScpClog - type: ShowBlinkable - type: XenoArtifact effectsTable: !type:GroupSelector @@ -110,12 +121,6 @@ - type: SpeedModifierContactCapClothing maxContactSprintSlowdown: 1 maxContactWalkSlowdown: 1 - - type: ActionGrant - actions: - - Scp173Blind - - Scp173Clog - - Scp173DamageStructure - - Scp173FastMovement - type: FootstepModifier footstepSoundCollection: collection: FootstepScp173Classic