diff --git a/Content.Client/_Scp/Other/ScpOnSoundVisibility/ActiveScpOnSoundVisibilityComponent.cs b/Content.Client/_Scp/Other/ScpOnSoundVisibility/ActiveScpOnSoundVisibilityComponent.cs new file mode 100644 index 00000000000..b18dc0967df --- /dev/null +++ b/Content.Client/_Scp/Other/ScpOnSoundVisibility/ActiveScpOnSoundVisibilityComponent.cs @@ -0,0 +1,19 @@ +using Content.Shared._Scp.Other.ScpOnSoundVisibility; + +namespace Content.Client._Scp.Other.ScpOnSoundVisibility; + +[RegisterComponent] +public sealed partial class ActiveScpOnSoundVisibilityComponent : Component +{ + [ViewVariables] + public float VisibilityAcc = ScpOnSoundVisibilityComponent.InitialVisibilityAcc; + + public float HideTime = ScpOnSoundVisibilityComponent.DefaultHideTime; + + public float MinValue = ScpOnSoundVisibilityComponent.DefaultMinValue; + + public float MaxValue = ScpOnSoundVisibilityComponent.DefaultMaxValue; + + [ViewVariables] + public bool OnCollide; +} diff --git a/Content.Client/_Scp/Scp939/Scp939HudSystem.Overlay.cs b/Content.Client/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityHudSystem.Overlay.cs similarity index 50% rename from Content.Client/_Scp/Scp939/Scp939HudSystem.Overlay.cs rename to Content.Client/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityHudSystem.Overlay.cs index e14458dfc95..d1e531a89fa 100644 --- a/Content.Client/_Scp/Scp939/Scp939HudSystem.Overlay.cs +++ b/Content.Client/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityHudSystem.Overlay.cs @@ -1,28 +1,28 @@ -using Content.Shared._Scp.Scp939; +using Content.Shared._Scp.Other.ScpOnSoundVisibility; using Robust.Client.Graphics; using Robust.Shared.Player; -namespace Content.Client._Scp.Scp939; +namespace Content.Client._Scp.Other.ScpOnSoundVisibility; -public sealed partial class Scp939HudSystem +public sealed partial class ScpOnSoundVisibilityHudSystem { [Dependency] private readonly IOverlayManager _overlayManager = default!; private void InitializeOverlay() { - SubscribeLocalEvent(OnPlayerAttached); - SubscribeLocalEvent(OnPlayerDetached); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); } - private void OnPlayerAttached(Entity ent, ref LocalPlayerAttachedEvent args) + private void OnPlayerAttached(Entity ent, ref LocalPlayerAttachedEvent args) { - _scp939Component = ent.Comp; AddOverlays(); } - private void OnPlayerDetached(Entity ent, ref LocalPlayerDetachedEvent args) + private void OnPlayerDetached(Entity ent, ref LocalPlayerDetachedEvent args) { - _scp939Component = null; + ClearLocalActiveVisibility(); + RemoveOverlays(); } private void AddOverlays() @@ -44,7 +44,7 @@ private void RemoveOverlays() _overlayManager.RemoveOverlay(_setAlphaOverlay); _overlayManager.RemoveOverlay(_resetAlphaOverlay); - CachedBaseAlphas.Clear(); + RestoreCachedBaseAlphas(); _overlaysPresented = false; } } diff --git a/Content.Client/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityHudSystem.cs b/Content.Client/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityHudSystem.cs new file mode 100644 index 00000000000..54b6c29e0f3 --- /dev/null +++ b/Content.Client/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityHudSystem.cs @@ -0,0 +1,403 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Client.Overlays; +using Content.Client.SSDIndicator; +using Content.Shared._Scp.Other.ScpOnSoundVisibility; +using Content.Shared.Examine; +using Content.Shared.Inventory.Events; +using Content.Shared.Movement.Components; +using Content.Shared.Standing; +using Content.Shared.StatusIcon.Components; +using Content.Shared.Throwing; +using Content.Shared.Weapons.Melee.Events; +using Content.Shared.Whitelist; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Events; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Client._Scp.Other.ScpOnSoundVisibility; + +public sealed partial class ScpOnSoundVisibilityHudSystem : EquipmentHudSystem +{ + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly SpriteSystem _sprite = default!; + + public readonly List<(Entity Ent, float BaseAlpha)> CachedBaseAlphas = new(64); + + private EntityQuery _eyeQuery; + private EntityQuery _visibilityQuery; + private EntityQuery _movementSpeedQuery; + private EntityQuery _physicsQuery; + private EntityQuery _viewerQuery; + + private ScpOnSoundVisibilitySetAlphaOverlay _setAlphaOverlay = default!; + private ScpOnSoundVisibilityResetAlphaOverlay _resetAlphaOverlay = default!; + + private bool _overlaysPresented; + private TimeSpan _nextUpdateTime; + private readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(0.05); + private readonly HashSet _visibilityTargetBuffer = []; + private readonly List _visibilityRemovalQueue = []; + + public override void Initialize() + { + base.Initialize(); + + InitializeOverlay(); + + SubscribeLocalEvent(OnGetStatusIcons, after: [typeof(SSDIndicatorSystem)]); + SubscribeLocalEvent(OnExamine); + + SubscribeLocalEvent(OnStartCollide); + SubscribeLocalEvent(OnEndCollide); + + SubscribeLocalEvent(OnMove); + + SubscribeLocalEvent(OnThrow); + SubscribeLocalEvent(OnStood); + SubscribeLocalEvent(OnMeleeAttack); + SubscribeLocalEvent(OnDown); + + SubscribeNetworkEvent(OnVisibilityTargets); + + _eyeQuery = GetEntityQuery(); + _visibilityQuery = GetEntityQuery(); + _movementSpeedQuery = GetEntityQuery(); + _physicsQuery = GetEntityQuery(); + _viewerQuery = GetEntityQuery(); + + _setAlphaOverlay = new(); + _resetAlphaOverlay = new(); + } + + public override void Shutdown() + { + RestoreCachedBaseAlphas(); + RemoveOverlays(); + + _setAlphaOverlay.Dispose(); + _resetAlphaOverlay.Dispose(); + + base.Shutdown(); + } + + private void OnExamine(Entity ent, ref ExamineAttemptEvent args) + { + if (!IsActive) + return; + + if (!TryGetLocalViewer(out var viewer)) + return; + + var visibility = GetVisibility(ent); + + if (visibility < viewer.Value.Comp.ExamineHideThreshold) + args.Cancel(); + } + + private void OnGetStatusIcons(Entity ent, ref GetStatusIconsEvent args) + { + if (!IsActive) + return; + + if (!TryGetLocalViewer(out var viewer)) + return; + + var visibility = GetVisibility(ent); + + if (visibility <= viewer.Value.Comp.StatusIconClearThreshold) + args.StatusIcons.Clear(); + } + + protected override void UpdateInternal(RefreshEquipmentHudEvent args) + { + base.UpdateInternal(args); + + AddOverlays(); + } + + protected override void DeactivateInternal() + { + base.DeactivateInternal(); + + _nextUpdateTime = TimeSpan.Zero; + + RestoreCachedBaseAlphas(); + RemoveOverlays(); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (!IsActive) + return; + + if (_timing.RealTime < _nextUpdateTime) + return; + + if (!TryGetLocalViewer(out var viewer)) + return; + + var delta = (float)(_timing.RealTime - (_nextUpdateTime - UpdateInterval)).TotalSeconds; + _nextUpdateTime = _timing.RealTime + UpdateInterval; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var visibilityEnt, out var visibilityComponent)) + { + if (visibilityComponent.OnCollide) + continue; + + if (viewer.Value.Owner == visibilityEnt) + continue; + + if (visibilityComponent.VisibilityAcc >= visibilityComponent.HideTime) + continue; + + visibilityComponent.VisibilityAcc = MathF.Min(visibilityComponent.VisibilityAcc + delta, visibilityComponent.HideTime); + } + } + + public bool CanDraw(in OverlayDrawArgs args) + { + if (!IsActive) + return false; + + if (_player.LocalEntity is not { } player) + return false; + + if (!_eyeQuery.TryComp(player, out var eye)) + return false; + + return args.Viewport.Eye == eye.Eye; + } + + public void RestoreCachedBaseAlphas() + { + foreach (var (ent, baseAlpha) in CachedBaseAlphas) + { + if (!EntityManager.EntityExists(ent)) + continue; + + _sprite.SetColor(ent.AsNullable(), ent.Comp.Color.WithAlpha(baseAlpha)); + } + + CachedBaseAlphas.Clear(); + } + + public static float GetVisibility(Entity ent) + { + var acc = ent.Comp.VisibilityAcc; + + if (acc > ent.Comp.HideTime) + return 0; + + return Math.Clamp(1f - (acc / ent.Comp.HideTime), 0f, 1f); + } + + private void OnMove(Entity ent, ref MoveEvent args) + { + if (!IsActive) + return; + + if (!TryGetLocalViewer(out var viewer)) + return; + + if (ent.Comp.OnCollide) + return; + + if (ent.Owner == viewer.Value.Owner) + return; + + // В зависимости от наличие защит или проблем со зрением изменяется то, насколько хорошо мы видим жертву + if (ModifyAcc(ent, out var modifier)) // Если зрение затруднено + { + ent.Comp.VisibilityAcc *= modifier; + } + else if (_whitelist.IsWhitelistPass(viewer.Value.Comp.Protections, ent)) // Если имеется защита(тихое хождение) + { + return; + } + else // Если со зрением все ок + { + ent.Comp.VisibilityAcc = 0; + } + + if (!_movementSpeedQuery.TryComp(ent, out var speedModifierComponent) + || !_physicsQuery.TryComp(ent, out var physicsComponent)) + { + return; + } + + var currentVelocity = physicsComponent.LinearVelocity.Length(); + + if (speedModifierComponent.BaseWalkSpeed > currentVelocity) + ent.Comp.VisibilityAcc = ent.Comp.HideTime / 2f; + } + + private void OnStartCollide(Entity ent, ref StartCollideEvent args) + { + if (!IsActive) + return; + + ent.Comp.OnCollide = true; + MobDidSomething(ent); + } + + private void OnEndCollide(Entity ent, ref EndCollideEvent args) + { + if (!IsActive) + return; + + ent.Comp.OnCollide = false; + MobDidSomething(ent); + } + + private void OnThrow(Entity ent, ref ThrowEvent args) + { + if (!IsActive) + return; + + MobDidSomething(ent); + } + + private void OnStood(Entity ent, ref StoodEvent args) + { + if (!IsActive) + return; + + MobDidSomething(ent); + } + + private void OnMeleeAttack(Entity ent, ref MeleeAttackEvent args) + { + if (!IsActive) + return; + + MobDidSomething(ent); + } + + private void OnDown(Entity ent, ref DownedEvent args) + { + if (!IsActive) + return; + + MobDidSomething(ent); + } + + private void MobDidSomething(Entity ent) + { + ent.Comp.VisibilityAcc = ScpOnSoundVisibilityComponent.InitialVisibilityAcc; + } + + // TODO: Переделать под статус эффект и добавить его в панель статус эффектов, а то непонятно игруну + private bool ModifyAcc(Entity ent, out float modifier) + { + // 1 = отсутствие модификатора + modifier = 1f; + + if (!TryGetLocalViewer(out var viewer)) + return false; + + if (!viewer.Value.Comp.PoorEyesight) + return false; + + modifier = _random.NextFloat(ent.Comp.MinValue, ent.Comp.MaxValue); + + return true; + } + + private void OnVisibilityTargets(ScpOnSoundVisibilityTargetsEvent ev) + { + if (_player.LocalEntity is not { Valid: true } localPlayer) + return; + + if (GetEntity(ev.Viewer) is not { Valid: true } viewer || viewer != localPlayer) + return; + + _visibilityTargetBuffer.Clear(); + + foreach (var netTarget in ev.Targets) + { + if (GetEntity(netTarget) is not { Valid: true } target) + continue; + + if (!_visibilityQuery.TryComp(target, out var visibility)) + continue; + + _visibilityTargetBuffer.Add(target); + EnsureLocalActiveVisibility((target, visibility)); + } + + _visibilityRemovalQueue.Clear(); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out _)) + { + if (_visibilityTargetBuffer.Contains(uid)) + continue; + + _visibilityRemovalQueue.Add(uid); + } + + foreach (var uid in _visibilityRemovalQueue) + { + RemComp(uid); + } + + _visibilityRemovalQueue.Clear(); + _visibilityTargetBuffer.Clear(); + } + + private void EnsureLocalActiveVisibility(Entity ent) + { + if (!TryComp(ent, out var active)) + { + active = AddComp(ent); + } + + active.HideTime = ent.Comp.HideTime; + active.MinValue = ent.Comp.MinValue; + active.MaxValue = ent.Comp.MaxValue; + } + + public void ClearLocalActiveVisibility() + { + RestoreCachedBaseAlphas(); + _visibilityRemovalQueue.Clear(); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out _)) + { + _visibilityRemovalQueue.Add(uid); + } + + foreach (var uid in _visibilityRemovalQueue) + { + RemComp(uid); + } + + _visibilityRemovalQueue.Clear(); + } + + private bool TryGetLocalViewer([NotNullWhen(true)] out Entity? ent) + { + ent = null; + var localPlayer = _player.LocalEntity; + + if (localPlayer == null) + return false; + + if (!_viewerQuery.TryComp(localPlayer, out var comp)) + return false; + + ent = (localPlayer.Value, comp); + return true; + } +} diff --git a/Content.Client/_Scp/Scp939/Scp939ResetAlphaOverlay.cs b/Content.Client/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityResetAlphaOverlay.cs similarity index 63% rename from Content.Client/_Scp/Scp939/Scp939ResetAlphaOverlay.cs rename to Content.Client/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityResetAlphaOverlay.cs index 2eb83579c89..3538ebe2a47 100644 --- a/Content.Client/_Scp/Scp939/Scp939ResetAlphaOverlay.cs +++ b/Content.Client/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityResetAlphaOverlay.cs @@ -1,21 +1,21 @@ using Robust.Client.Graphics; using Robust.Shared.Enums; -namespace Content.Client._Scp.Scp939; +namespace Content.Client._Scp.Other.ScpOnSoundVisibility; -public sealed class Scp939ResetAlphaOverlay : Overlay +public sealed class ScpOnSoundVisibilityResetAlphaOverlay : Overlay { [Dependency] private readonly IEntityManager _ent = default!; - private readonly Scp939HudSystem _hud; + private readonly ScpOnSoundVisibilityHudSystem _hud; public override OverlaySpace Space => OverlaySpace.WorldSpace; - public Scp939ResetAlphaOverlay() + public ScpOnSoundVisibilityResetAlphaOverlay() { IoCManager.InjectDependencies(this); - _hud = _ent.System(); + _hud = _ent.System(); } protected override bool BeforeDraw(in OverlayDrawArgs args) diff --git a/Content.Client/_Scp/Scp939/Scp939SetAlphaOverlay.cs b/Content.Client/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilitySetAlphaOverlay.cs similarity index 70% rename from Content.Client/_Scp/Scp939/Scp939SetAlphaOverlay.cs rename to Content.Client/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilitySetAlphaOverlay.cs index d081f9afa4b..3fcba67ddf4 100644 --- a/Content.Client/_Scp/Scp939/Scp939SetAlphaOverlay.cs +++ b/Content.Client/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilitySetAlphaOverlay.cs @@ -1,25 +1,25 @@ -using Content.Shared._Scp.Scp939; +using Content.Shared._Scp.Other.ScpOnSoundVisibility; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Shared.Enums; -namespace Content.Client._Scp.Scp939; +namespace Content.Client._Scp.Other.ScpOnSoundVisibility; -public sealed class Scp939SetAlphaOverlay : Overlay +public sealed class ScpOnSoundVisibilitySetAlphaOverlay : Overlay { [Dependency] private readonly IEntityManager _ent = default!; - private readonly Scp939HudSystem _hud; + private readonly ScpOnSoundVisibilityHudSystem _hud; private readonly TransformSystem _transform; private readonly SpriteSystem _sprite; public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities; - public Scp939SetAlphaOverlay() + public ScpOnSoundVisibilitySetAlphaOverlay() { IoCManager.InjectDependencies(this); - _hud = _ent.System(); + _hud = _ent.System(); _transform = _ent.System(); _sprite = _ent.System(); } @@ -33,7 +33,7 @@ protected override void Draw(in OverlayDrawArgs args) { _hud.CachedBaseAlphas.Clear(); - var query = _ent.EntityQueryEnumerator(); + var query = _ent.EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var visibility, out var sprite, out var xform)) { if (xform.MapID != args.MapId || !sprite.Visible) @@ -43,7 +43,7 @@ protected override void Draw(in OverlayDrawArgs args) if (!args.WorldBounds.Contains(worldPosition)) continue; - var targetAlpha = Scp939HudSystem.GetVisibility((uid, visibility)); + var targetAlpha = ScpOnSoundVisibilityHudSystem.GetVisibility((uid, visibility)); if (MathF.Abs(sprite.Color.A - targetAlpha) <= 0.01f) continue; diff --git a/Content.Client/_Scp/Scp939/Scp939HudSystem.Visibility.cs b/Content.Client/_Scp/Scp939/Scp939HudSystem.Visibility.cs deleted file mode 100644 index dc111bf2bfe..00000000000 --- a/Content.Client/_Scp/Scp939/Scp939HudSystem.Visibility.cs +++ /dev/null @@ -1,162 +0,0 @@ -using Content.Shared._Scp.Scp939; -using Content.Shared.Standing; -using Content.Shared.Throwing; -using Content.Shared.Weapons.Melee.Events; -using Robust.Shared.Physics.Events; -using Robust.Shared.Random; - -namespace Content.Client._Scp.Scp939; - -public sealed partial class Scp939HudSystem -{ - [Dependency] private readonly IRobustRandom _random = default!; - - private void InitializeVisibility() - { - SubscribeLocalEvent((Entity ent, ref StartCollideEvent args) - => OnCollide(ent, args.OtherEntity)); - SubscribeLocalEvent((Entity ent, ref EndCollideEvent args) - => OnCollide(ent, args.OtherEntity)); - SubscribeLocalEvent(OnVisibilityStateUpdated); - - SubscribeLocalEvent(OnMove); - - SubscribeLocalEvent(OnThrow); - SubscribeLocalEvent(OnStood); - SubscribeLocalEvent(OnMeleeAttack); - SubscribeLocalEvent(OnDown); - } - - private void OnVisibilityStateUpdated(Entity ent, ref AfterAutoHandleStateEvent args) - { - if (ent.Comp.LastHandledVisibilityResetCounter == ent.Comp.VisibilityResetCounter) - return; - - ent.Comp.LastHandledVisibilityResetCounter = ent.Comp.VisibilityResetCounter; - ent.Comp.VisibilityAcc = Scp939VisibilityComponent.InitialVisibilityAcc; - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - if (!IsActive) - return; - - _lastUpdateTime += frameTime; - if (_lastUpdateTime < UpdateInterval) - return; - - var delta = _lastUpdateTime; - _lastUpdateTime = 0f; - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out _, out var visibilityComponent)) - { - if (visibilityComponent.VisibilityAcc >= visibilityComponent.HideTime) - continue; - - visibilityComponent.VisibilityAcc = MathF.Min(visibilityComponent.VisibilityAcc + delta, visibilityComponent.HideTime); - } - } - - private void OnMove(Entity ent, ref MoveEvent args) - { - if (!IsActive) - return; - - // В зависимости от наличие защит или проблем со зрением у 939 изменяется то, насколько хорошо мы видим жертву - if (ModifyAcc(ent.Comp, out var modifier)) // Если зрение затруднено - { - ent.Comp.VisibilityAcc *= modifier; - } - else if (_scp939ProtectionQuery.HasComp(ent)) // Если имеется защита(тихое хождение) - { - return; - } - else // Если со зрением все ок - { - ent.Comp.VisibilityAcc = 0; - } - - if (!_movementSpeedQuery.TryComp(ent, out var speedModifierComponent) - || !_physicsQuery.TryComp(ent, out var physicsComponent)) - { - return; - } - - var currentVelocity = physicsComponent.LinearVelocity.Length(); - - if (speedModifierComponent.BaseWalkSpeed > currentVelocity) - ent.Comp.VisibilityAcc = ent.Comp.HideTime / 2f; - } - - - private void OnCollide(Entity ent, EntityUid otherEntity) - { - if (!IsActive) - return; - - if (!HasComp(otherEntity)) - return; - - MobDidSomething(ent); - } - - private void OnThrow(Entity ent, ref ThrowEvent args) - { - if (!IsActive) - return; - - MobDidSomething(ent); - } - - private void OnStood(Entity ent, ref StoodEvent args) - { - if (!IsActive) - return; - - MobDidSomething(ent); - } - - private void OnMeleeAttack(Entity ent, ref MeleeAttackEvent args) - { - if (!IsActive) - return; - - MobDidSomething(ent); - } - - private void OnDown(Entity ent, ref DownedEvent args) - { - if (!IsActive) - return; - - MobDidSomething(ent); - } - - private void MobDidSomething(Entity ent) - { - ent.Comp.VisibilityAcc = Scp939VisibilityComponent.InitialVisibilityAcc; - } - - // TODO: Переделать под статус эффект и добавить его в панель статус эффектов, а то непонятно игруну - /// - /// Если вдруг собачка плохо видит - /// - private bool ModifyAcc(ActiveScp939VisibilityComponent visibilityComponent, out int modifier) - { - // 1 = отсутствие модификатора - modifier = 1; - - if (_scp939Component == null) - return false; - - if (!_scp939Component.PoorEyesight) - return false; - - modifier = _random.Next(visibilityComponent.MinValue, visibilityComponent.MaxValue); - - return true; - } -} diff --git a/Content.Client/_Scp/Scp939/Scp939HudSystem.cs b/Content.Client/_Scp/Scp939/Scp939HudSystem.cs deleted file mode 100644 index a0136d8844b..00000000000 --- a/Content.Client/_Scp/Scp939/Scp939HudSystem.cs +++ /dev/null @@ -1,149 +0,0 @@ -using Content.Client.Overlays; -using Content.Client.SSDIndicator; -using Content.Client.Stealth; -using Content.Shared._Scp.Scp939; -using Content.Shared._Scp.Scp939.Protection; -using Content.Shared.Examine; -using Content.Shared.Inventory.Events; -using Content.Shared.Movement.Components; -using Content.Shared.StatusIcon.Components; -using Robust.Client.GameObjects; -using Robust.Client.Graphics; -using Robust.Client.Player; -using Robust.Shared.Physics.Components; - -namespace Content.Client._Scp.Scp939; - -public sealed partial class Scp939HudSystem : EquipmentHudSystem -{ - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly SpriteSystem _sprite = default!; - - internal readonly List<(Entity Ent, float BaseAlpha)> CachedBaseAlphas = new(64); - - private Scp939SetAlphaOverlay _setAlphaOverlay = default!; - private Scp939ResetAlphaOverlay _resetAlphaOverlay = default!; - - // TODO: Выделить значения плохого зрения в отдельный компонент, не связанный с 939 - private Scp939Component? _scp939Component; - - private EntityQuery _eyeQuery; - private EntityQuery _scp939ProtectionQuery; - private EntityQuery _movementSpeedQuery; - private EntityQuery _physicsQuery; - - private bool _overlaysPresented; - private float _lastUpdateTime; - - private const float UpdateInterval = 0.05f; - - public override void Initialize() - { - base.Initialize(); - - InitializeVisibility(); - InitializeOverlay(); - - SubscribeLocalEvent(OnGetStatusIcons, after: [typeof(SSDIndicatorSystem)] ); - SubscribeLocalEvent(OnExamine); - - _eyeQuery = GetEntityQuery(); - _scp939ProtectionQuery = GetEntityQuery(); - _movementSpeedQuery = GetEntityQuery(); - _physicsQuery = GetEntityQuery(); - - _setAlphaOverlay = new(); - _resetAlphaOverlay = new(); - - UpdatesAfter.Add(typeof(StealthSystem)); - } - - public override void Shutdown() - { - RestoreCachedBaseAlphas(); - RemoveOverlays(); - - _setAlphaOverlay.Dispose(); - _resetAlphaOverlay.Dispose(); - - base.Shutdown(); - } - - private void OnExamine(Entity ent, ref ExamineAttemptEvent args) - { - if (!IsActive) - return; - - var visibility = GetVisibility(ent); - - if (visibility < 0.2f) - args.Cancel(); - } - - private void OnGetStatusIcons(Entity ent, ref GetStatusIconsEvent args) - { - if (!IsActive) - return; - - var visibility = GetVisibility(ent); - - if (visibility <= 0.5f) - args.StatusIcons.Clear(); - } - - protected override void UpdateInternal(RefreshEquipmentHudEvent args) - { - base.UpdateInternal(args); - - _scp939Component = args.Components.Count > 0 ? args.Components[0] : null; - AddOverlays(); - } - - protected override void DeactivateInternal() - { - base.DeactivateInternal(); - - _scp939Component = null; - _lastUpdateTime = 0f; - - RestoreCachedBaseAlphas(); - RemoveOverlays(); - } - - internal bool CanDraw(in OverlayDrawArgs args) - { - if (!IsActive) - return false; - - if (_playerManager.LocalEntity is not { } player) - return false; - - if (!_eyeQuery.TryComp(player, out var eye)) - return false; - - return args.Viewport.Eye == eye.Eye; - } - - internal void RestoreCachedBaseAlphas() - { - foreach (var (ent, baseAlpha) in CachedBaseAlphas) - { - if (!EntityManager.EntityExists(ent)) - continue; - - _sprite.SetColor(ent.AsNullable(), ent.Comp.Color.WithAlpha(baseAlpha)); - } - - CachedBaseAlphas.Clear(); - } - - internal static float GetVisibility(Entity ent) - { - var acc = ent.Comp.VisibilityAcc; - - if (acc > ent.Comp.HideTime) - return 0; - - return Math.Clamp(1f - (acc / ent.Comp.HideTime), 0f, 1f); - } -} diff --git a/Content.Client/_Scp/Scp939/Scp939Visualizer.cs b/Content.Client/_Scp/Scp939/Scp939Visualizer.cs deleted file mode 100644 index 06753acfc62..00000000000 --- a/Content.Client/_Scp/Scp939/Scp939Visualizer.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Shared._Scp.Scp939; -using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; -using Robust.Client.GameObjects; - -namespace Content.Client._Scp.Scp939; - -public sealed class Scp939Visualizer : VisualizerSystem -{ - [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; - - protected override void OnAppearanceChange(EntityUid uid, Scp939Component component, ref AppearanceChangeEvent args) - { - base.OnAppearanceChange(uid, component, ref args); - - UpdateSprite(uid, args.Component, args.Sprite); - } - - private void UpdateSprite(EntityUid uid, AppearanceComponent appearanceComponent, SpriteComponent? spriteComponent = null, MobStateComponent? mobStateComponent = null) - { - if (!Resolve(uid, ref spriteComponent) || - !Resolve(uid, ref mobStateComponent) || - !spriteComponent.LayerMapTryGet(Scp939Layers.Base, out var layerId)) - { - return; - } - - if (mobStateComponent.CurrentState is MobState.Dead or MobState.Critical) - { - spriteComponent.LayerSetState(layerId, "dead"); - return; - } - - spriteComponent.LayerSetState(layerId, "alive"); - - if (_appearanceSystem.TryGetData(uid, Scp939Visuals.Sleeping, out var sleeping) && sleeping) - { - spriteComponent.LayerSetState(layerId, "asleep"); - } - } -} diff --git a/Content.Server/_Scp/Other/ScpHealOnSleep/ScpHealOnSleepComponent.cs b/Content.Server/_Scp/Other/ScpHealOnSleep/ScpHealOnSleepComponent.cs new file mode 100644 index 00000000000..09c11fed093 --- /dev/null +++ b/Content.Server/_Scp/Other/ScpHealOnSleep/ScpHealOnSleepComponent.cs @@ -0,0 +1,44 @@ +using Content.Shared.Actions.Components; +using Content.Shared.Damage; +using Content.Shared.Mobs; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; + +namespace Content.Server._Scp.Other.ScpSleep; + +[RegisterComponent] +public sealed partial class ScpHealOnSleepComponent : Component +{ + [DataField] + public EntProtoId ActionProto = "ScpSleepAction"; + + [DataField] + public bool AddAction = true; + + [DataField] + public EntProtoId StatusEffect = "StatusEffectForcedSleeping"; + + [DataField] + public SoundSpecifier? CritSound; + + [DataField] + public List? HibernationStates; + + [DataField] + public DamageSpecifier? HibernationHealingRate; + + [DataField] + public TimeSpan HibernationDurationOnHibernationState = TimeSpan.FromSeconds(360); + + [DataField] + public TimeSpan HibernationDuration = TimeSpan.FromSeconds(60); + + [DataField] + public int BaseBloodRefreshAmount = 1; + + [DataField] + public int FellAsleepBloodRefreshAmount = 20; + + [ViewVariables] + public EntityUid? ActionEnt; +} diff --git a/Content.Server/_Scp/Other/ScpHealOnSleep/ScpHealOnSleepSystem.cs b/Content.Server/_Scp/Other/ScpHealOnSleep/ScpHealOnSleepSystem.cs new file mode 100644 index 00000000000..1653d47b32f --- /dev/null +++ b/Content.Server/_Scp/Other/ScpHealOnSleep/ScpHealOnSleepSystem.cs @@ -0,0 +1,114 @@ +using Content.Server.Actions; +using Content.Shared._Scp.Other.ScpSleep; +using Content.Shared._Scp.Other.Events; +using Content.Shared.Bed.Sleep; +using Content.Shared.Body.Components; +using Content.Shared.Damage.Systems; +using Content.Shared.Mobs; +using Content.Shared.StatusEffectNew; +using Robust.Server.Audio; +using Robust.Server.GameObjects; + +namespace Content.Server._Scp.Other.ScpSleep; + +public sealed class ScpHealOnSleepSystem : EntitySystem +{ + [Dependency] private readonly ActionsSystem _actions = default!; + [Dependency] private readonly SleepingSystem _sleeping = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly AppearanceSystem _appearance = default!; + [Dependency] private readonly AudioSystem _audio = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnShutdown); + + SubscribeLocalEvent(OnSleepAction); + SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnSleepChanged); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + if (!ent.Comp.AddAction) + return; + + var actionEnt = _actions.AddAction(ent, ent.Comp.ActionProto); + ent.Comp.ActionEnt = actionEnt; + } + + private void OnShutdown(Entity ent, ref ComponentShutdown args) + { + _actions.RemoveAction(ent.Owner, ent.Comp.ActionEnt); + ent.Comp.ActionEnt = null; + } + + private void OnSleepAction(Entity ent, ref ScpSleepActionEvent args) + { + args.Handled = TrySleep(ent, ent.Comp.HibernationDuration); + } + + private void OnMobStateChanged(Entity ent, ref MobStateChangedEvent args) + { + if (ent.Comp.HibernationStates == null) + return; + + if (!ent.Comp.HibernationStates.Contains(args.NewMobState)) + return; + + if (!TrySleep(ent, ent.Comp.HibernationDurationOnHibernationState)) + return; + + if (ent.Comp.CritSound == null) + return; + + _audio.PlayPvs(ent.Comp.CritSound, ent); + } + + private void OnSleepChanged(Entity ent, ref SleepStateChangedEvent args) + { + if (TryComp(ent, out var bloodstreamComponent)) + { + if (args.FellAsleep) + bloodstreamComponent.BloodRefreshAmount = ent.Comp.FellAsleepBloodRefreshAmount; + else + bloodstreamComponent.BloodRefreshAmount = ent.Comp.BaseBloodRefreshAmount; + + DirtyField(ent, bloodstreamComponent, nameof(BloodstreamComponent.BloodRefreshAmount)); + } + + _appearance.SetData(ent, ScpHealOnSleepVisuals.Sleeping, args.FellAsleep); + } + + public bool TrySleep(Entity ent, TimeSpan hibernationDuration) + { + if (HasComp(ent)) + return false; + + if (!_sleeping.TrySleeping(ent.Owner)) + return false; + + if (!_statusEffects.TryAddStatusEffectDuration(ent, ent.Comp.StatusEffect, hibernationDuration)) + return false; + + return true; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var querySleeping = EntityQueryEnumerator(); + while (querySleeping.MoveNext(out var uid, out var scpSleepComponent, out _)) + { + if (scpSleepComponent.HibernationHealingRate == null) + continue; + + _damageable.TryChangeDamage(uid, scpSleepComponent.HibernationHealingRate * frameTime); + } + } +} diff --git a/Content.Server/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilitySystem.cs b/Content.Server/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilitySystem.cs new file mode 100644 index 00000000000..85f1010c23b --- /dev/null +++ b/Content.Server/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilitySystem.cs @@ -0,0 +1,176 @@ +using Content.Server.Popups; +using Content.Shared._Scp.Other.ScpOnSoundVisibility; +using Content.Shared.Flash; +using Content.Shared.Popups; +using Robust.Server.Player; +using Robust.Shared.Map; +using Robust.Shared.Player; +using Robust.Shared.Timing; + +namespace Content.Server._Scp.Other.ScpOnSoundVisibility; + +public sealed partial class ScpOnSoundVisibilitySystem : EntitySystem +{ + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly EntityLookupSystem _entityLookup = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + private static readonly TimeSpan VisibilityRefreshInterval = TimeSpan.FromSeconds(0.2f); + + private TimeSpan _nextVisibilityRefresh = TimeSpan.Zero; + private readonly Dictionary> _viewerActiveTargets = []; + private readonly HashSet> _visibilityCandidates = []; + private readonly HashSet _viewerTargetsBuffer = []; + private readonly List _staleViewers = []; + private readonly List _netTargetsBuffer = []; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnFlash); + } + + private void OnFlash(Entity ent, ref AfterFlashedEvent args) + { + if (!ent.Comp.PoorEyesOnFlash) + return; + + ent.Comp.PoorEyesight = true; + ent.Comp.PoorEyesightTimeStart = _timing.CurTime; + + if (ent.Comp.OnFlashMessage != null) + { + var message = Loc.GetString(ent.Comp.OnFlashMessage, ("time", ent.Comp.PoorEyesightTime)); + _popup.PopupEntity(message, ent, ent, PopupType.MediumCaution); + } + + EnsureComp(ent); + DirtyFields(ent, ent.Comp, null, [ + nameof(ScpOnSoundVisibilityViewerComponent.PoorEyesight), + nameof(ScpOnSoundVisibilityViewerComponent.PoorEyesightTimeStart) + ]); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + UpdateVisibilityTargets(); + + var querySimple = EntityQueryEnumerator(); + while (querySimple.MoveNext(out var uid, out _, out var viewerComp)) + { + if (!viewerComp.PoorEyesight) + continue; + + if (viewerComp.PoorEyesightTimeStart == null) + continue; + + var timeDifference = _timing.CurTime - viewerComp.PoorEyesightTimeStart.Value; + + if (timeDifference > viewerComp.PoorEyesightTime) + { + viewerComp.PoorEyesight = false; + viewerComp.PoorEyesightTimeStart = null; + + RemCompDeferred(uid); + DirtyFields(uid, viewerComp, null, [ + nameof(ScpOnSoundVisibilityViewerComponent.PoorEyesight), + nameof(ScpOnSoundVisibilityViewerComponent.PoorEyesightTimeStart) + ]); + } + } + } + + public void UpdateVisibilityTargets() + { + if (_timing.CurTime < _nextVisibilityRefresh) + return; + + _nextVisibilityRefresh = _timing.CurTime + VisibilityRefreshInterval; + _staleViewers.Clear(); + _staleViewers.AddRange(_viewerActiveTargets.Keys); + + var viewerQuery = EntityQueryEnumerator(); + while (viewerQuery.MoveNext(out var uid, out var viewer, out var xform)) + { + _staleViewers.Remove(uid); + + if (!_player.TryGetSessionByEntity(uid, out var session)) + { + _viewerActiveTargets.Remove(uid); + continue; + } + + _viewerTargetsBuffer.Clear(); + + if (xform.MapID != MapId.Nullspace) + { + _visibilityCandidates.Clear(); + _entityLookup.GetEntitiesInRange(xform.Coordinates, + viewer.VisibilityActivationRange, + _visibilityCandidates, + LookupFlags.Dynamic | LookupFlags.Approximate); + + foreach (var target in _visibilityCandidates) + { + _viewerTargetsBuffer.Add(target); + } + + _visibilityCandidates.Clear(); + } + + SyncViewerTargets((uid, viewer), session, _viewerTargetsBuffer); + } + + foreach (var viewer in _staleViewers) + { + ClearViewerTargets(viewer); + } + + _viewerTargetsBuffer.Clear(); + _staleViewers.Clear(); + } + + private void SyncViewerTargets( + Entity viewer, + ICommonSession session, + HashSet nextTargets) + { + if (!_viewerActiveTargets.TryGetValue(viewer, out var currentTargets)) + { + currentTargets = []; + _viewerActiveTargets[viewer] = currentTargets; + } + + if (currentTargets.SetEquals(nextTargets)) + return; + + _netTargetsBuffer.Clear(); + foreach (var target in nextTargets) + { + _netTargetsBuffer.Add(GetNetEntity(target)); + } + + RaiseNetworkEvent( + new ScpOnSoundVisibilityTargetsEvent(GetNetEntity(viewer.Owner), _netTargetsBuffer.ToArray()), + session); + + currentTargets.Clear(); + currentTargets.UnionWith(nextTargets); + _netTargetsBuffer.Clear(); + } + + private void ClearViewerTargets(EntityUid viewer) + { + if (!_viewerActiveTargets.Remove(viewer, out _)) + return; + + if (!_player.TryGetSessionByEntity(viewer, out var session)) + return; + + RaiseNetworkEvent(new ScpOnSoundVisibilityTargetsEvent(GetNetEntity(viewer), []), session); + } +} diff --git a/Content.Server/_Scp/Other/ScpRememberPhrase/ScpRememberPhraseSystem.cs b/Content.Server/_Scp/Other/ScpRememberPhrase/ScpRememberPhraseSystem.cs new file mode 100644 index 00000000000..15db81ffc51 --- /dev/null +++ b/Content.Server/_Scp/Other/ScpRememberPhrase/ScpRememberPhraseSystem.cs @@ -0,0 +1,97 @@ +using Content.Server.Actions; +using Content.Server.Chat.Systems; +using Content.Shared._Scp.Other.ScpRememberPhrase; +using Content.Shared._Scp.Other.Events; +using Content.Shared._Sunrise.TTS; +using Content.Shared.Chat; +using Content.Shared.IdentityManagement; +using Robust.Shared.Random; +using Content.Shared.Speech; +using Content.Shared.Speech.Components; + +namespace Content.Server._Scp.Other.ScpRememberPhrase; + +public sealed class ScpRememberPhraseSystem : EntitySystem +{ + [Dependency] private readonly ActionsSystem _actionsSystem = default!; + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnShutdown); + + SubscribeLocalEvent(OnListen); + SubscribeLocalEvent(OnMimic); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + var actionEnt = _actionsSystem.AddAction(ent, ent.Comp.ActionProto); + ent.Comp.ActionEnt = actionEnt; + + EnsureComp(ent); + } + + private void OnShutdown(Entity ent, ref ComponentShutdown args) + { + _actionsSystem.RemoveAction(ent.Owner, ent.Comp.ActionEnt); + ent.Comp.ActionEnt = null; + } + + private void OnListen(Entity ent, ref ListenEvent args) + { + TryRememberPhrase(ent, args.Source, args.Message); + } + + private void OnMimic(Entity ent, ref ScpRememberPhraseActionEvent args) + { + if (ent.Comp.RememberedMessages.Count == 0) + return; + + var messagePair = _random.Pick(ent.Comp.RememberedMessages); + + if (TryComp(ent, out var ttsComponent)) + { + ttsComponent.VoicePrototypeId = messagePair.TtsVoice; + Dirty(ent, ttsComponent); + } + + _chat.TrySendInGameICMessage(ent, + messagePair.Message, + InGameICChatType.Speak, + ChatTransmitRange.Normal, + nameOverride: messagePair.SpeakerName, + ignoreActionBlocker: true); + + args.Handled = true; + } + + /// + /// Запоминание последних сказанных в округе + /// + public void TryRememberPhrase(Entity ent, EntityUid speaker, string message) + { + string? voicePrototype = null; + + if (ent.Owner == speaker) + return; + + if (TryComp(speaker, out var ttsComponent)) + voicePrototype = ttsComponent.VoicePrototypeId; + + if (ent.Comp.RememberedMessages.Count >= ent.Comp.MaxRememberedMessages) + ent.Comp.RememberedMessages.RemoveAt(0); + + var username = Identity.Name(speaker, EntityManager); + ent.Comp.RememberedMessages.Add(new RememberedMessage + { + Message = message, + SpeakerName = username, + TtsVoice = voicePrototype + }); + } +} diff --git a/Content.Server/_Scp/Research/Artifacts/Effects/_ScpSpecific/Scp939/Sleep/ArtifactScp939SleepComponent.cs b/Content.Server/_Scp/Research/Artifacts/Effects/_ScpSpecific/Scp939/Sleep/ArtifactScp939SleepComponent.cs index 10afd8777d4..062e7a5dba0 100644 --- a/Content.Server/_Scp/Research/Artifacts/Effects/_ScpSpecific/Scp939/Sleep/ArtifactScp939SleepComponent.cs +++ b/Content.Server/_Scp/Research/Artifacts/Effects/_ScpSpecific/Scp939/Sleep/ArtifactScp939SleepComponent.cs @@ -4,8 +4,8 @@ public sealed partial class ArtifactScp939SleepComponent : Component { [DataField] - public float MinSleepTime = 20f; + public TimeSpan MinSleepTime = TimeSpan.FromSeconds(20); [DataField] - public float MaxSleepTime = 80f; + public TimeSpan MaxSleepTime = TimeSpan.FromSeconds(80); } diff --git a/Content.Server/_Scp/Research/Artifacts/Effects/_ScpSpecific/Scp939/Sleep/ArtifactScp939SleepSystem.cs b/Content.Server/_Scp/Research/Artifacts/Effects/_ScpSpecific/Scp939/Sleep/ArtifactScp939SleepSystem.cs index b856b0b4609..e6a16dd1805 100644 --- a/Content.Server/_Scp/Research/Artifacts/Effects/_ScpSpecific/Scp939/Sleep/ArtifactScp939SleepSystem.cs +++ b/Content.Server/_Scp/Research/Artifacts/Effects/_ScpSpecific/Scp939/Sleep/ArtifactScp939SleepSystem.cs @@ -1,23 +1,23 @@ -using Content.Server._Scp.Scp939; -using Content.Shared._Scp.Scp939; +using Content.Server._Scp.Other.ScpSleep; using Content.Shared.Xenoarchaeology.Artifact; using Content.Shared.Xenoarchaeology.Artifact.XAE; using Robust.Shared.Random; +using Content.Shared._Scp.Other.ScpSleep; namespace Content.Server._Scp.Research.Artifacts.Effects._ScpSpecific.Scp939.Sleep; public sealed class ArtifactScp939SleepSystem : BaseXAESystem { - [Dependency] private readonly Scp939System _scp939 = default!; + [Dependency] private readonly ScpHealOnSleepSystem _scpSleep = default!; [Dependency] private readonly IRobustRandom _random = default!; protected override void OnActivated(Entity ent, ref XenoArtifactNodeActivatedEvent args) { - if (!TryComp(ent, out var scp939Component)) + if (!TryComp(ent, out var scpSleepComponent)) return; - var time = _random.NextFloat(ent.Comp.MinSleepTime, ent.Comp.MaxSleepTime); + var time = _random.Next(ent.Comp.MinSleepTime, ent.Comp.MaxSleepTime); - _scp939.TrySleep((ent, scp939Component), time); + _scpSleep.TrySleep((ent, scpSleepComponent), time); } } diff --git a/Content.Server/_Scp/Scp939/Scp939System.Actions.cs b/Content.Server/_Scp/Scp939/Scp939System.Actions.cs deleted file mode 100644 index fd97893262d..00000000000 --- a/Content.Server/_Scp/Scp939/Scp939System.Actions.cs +++ /dev/null @@ -1,121 +0,0 @@ -using Content.Server.Chat.Systems; -using Content.Server.Examine; -using Content.Shared._Scp.Helpers; -using Content.Shared._Scp.Scp939; -using Content.Shared._Scp.ScpMask; -using Content.Shared._Sunrise.TTS; -using Content.Shared.Bed.Sleep; -using Content.Shared.Chat; -using Content.Shared.Coordinates.Helpers; -using Content.Shared.IdentityManagement; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; - -namespace Content.Server._Scp.Scp939; - -public sealed partial class Scp939System -{ - [Dependency] private readonly ExamineSystem _examine = default!; - [Dependency] private readonly EntityLookupSystem _entityLookup = default!; - [Dependency] private readonly ChatSystem _chat = default!; - [Dependency] private readonly ScpMaskSystem _scpMask = default!; - [Dependency] private readonly IRobustRandom _random = default!; - - private static readonly EntProtoId StatusEffect = "StatusEffectForcedSleeping"; - - private void InitializeActions() - { - SubscribeLocalEvent(OnSleepAction); - SubscribeLocalEvent(OnGasAction); - SubscribeLocalEvent(OnMimic); - } - - private void OnSleepAction(Entity ent, ref Scp939SleepAction args) - { - args.Handled = TrySleep(ent); - } - - public bool TrySleep(Entity ent, float hibernationDuration = 0) - { - if (HasComp(ent)) - return false; - - if (!_sleepingSystem.TrySleeping(ent.Owner)) - return false; - - hibernationDuration = hibernationDuration == 0 ? ent.Comp.HibernationDuration : hibernationDuration; - _statusEffects.TryAddStatusEffectDuration(ent, StatusEffect, TimeSpan.FromSeconds(hibernationDuration)); - - return true; - } - - private void OnGasAction(Entity ent, ref Scp939GasAction args) - { - if (_scpMask.TryGetScpMask(ent, out var scpMask)) - { - _scpMask.TryCreatePopup(ent, scpMask); - return; - } - - var xform = Transform(ent); - var smokeEntity = Spawn(ent.Comp.SmokeProtoId, xform.Coordinates.SnapToGrid()); - - _smokeSystem.StartSmoke(smokeEntity, ent.Comp.SmokeSolution, ent.Comp.SmokeDuration, ent.Comp.SmokeSpreadRadius); - - args.Handled = true; - } - - private void OnMimic(Entity ent, ref Scp939MimicActionEvent args) - { - if (ent.Comp.RememberedMessages.Count == 0) - return; - - var messagePair = _random.Pick(ent.Comp.RememberedMessages); - - if (TryComp(ent, out var ttsComponent)) - { - ttsComponent.VoicePrototypeId = messagePair.Value.Value; - Dirty(ent, ttsComponent); - } - - _chat.TrySendInGameICMessage(ent, - messagePair.Key, - InGameICChatType.Speak, - ChatTransmitRange.Normal, - nameOverride: messagePair.Value.Key, - ignoreActionBlocker: true); - - args.Handled = true; - } - - /// - /// Запоминание последних сказанных возле 939 слов - /// - private void TryRememberPhrase(Entity ent, string message) - { - using var scp939Set = HashSetPoolEntity.Rent(); - _entityLookup.GetEntitiesInRange(Transform(ent).Coordinates, 16f, scp939Set.Value, LookupFlags.Dynamic | LookupFlags.Approximate); - if (scp939Set.Value.Count == 0) - return; - - string? voicePrototype = null; - - if (TryComp(ent, out var ttsComponent)) - voicePrototype = ttsComponent.VoicePrototypeId; - - foreach (var scp in scp939Set.Value) - { - if (!_examine.InRangeUnOccluded(ent, scp)) - continue; - - if (scp.Comp.RememberedMessages.Count >= scp.Comp.MaxRememberedMessages) - { - var randomKey = _random.Pick(scp.Comp.RememberedMessages.Keys); - scp.Comp.RememberedMessages.Remove(randomKey); - } - - var username = Identity.Name(ent, EntityManager); - scp.Comp.RememberedMessages.TryAdd(message, new(username, voicePrototype)); - } - } -} diff --git a/Content.Server/_Scp/Scp939/Scp939System.Visibility.cs b/Content.Server/_Scp/Scp939/Scp939System.Visibility.cs deleted file mode 100644 index e8817310dbc..00000000000 --- a/Content.Server/_Scp/Scp939/Scp939System.Visibility.cs +++ /dev/null @@ -1,168 +0,0 @@ -using Content.Server.Popups; -using Content.Shared._Scp.Scp939; -using Content.Shared.Chat; -using Content.Shared.Flash; -using Content.Shared.Item; -using Content.Shared.Mobs.Components; -using Content.Shared.Popups; -using Content.Shared.Weapons.Ranged.Systems; -using Robust.Shared.Map; -using Robust.Shared.Timing; - -namespace Content.Server._Scp.Scp939; - -public sealed partial class Scp939System -{ - [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly IGameTiming _timing = default!; - - private static readonly TimeSpan VisibilityRefreshInterval = TimeSpan.FromSeconds(0.2f); - - private TimeSpan _nextVisibilityRefresh = TimeSpan.Zero; - private readonly HashSet _visibilityActiveTargets = []; - private readonly HashSet> _visibilityCandidates = []; - private readonly List _visibilityRemovalQueue = []; - - private EntityQuery _activeQuery; - - private void InitializeVisibility() - { - SubscribeLocalEvent(OnMobStartup); - - SubscribeLocalEvent(OnTargetSpoke); - SubscribeLocalEvent(OnTargetEmote); - SubscribeLocalEvent(OnShot); - - SubscribeLocalEvent(OnFlash); - - _activeQuery = GetEntityQuery(); - } - - private void OnFlash(Entity ent, ref AfterFlashedEvent args) - { - ent.Comp.PoorEyesight = true; - ent.Comp.PoorEyesightTimeStart = _timing.CurTime; - - var message = Loc.GetString("scp939-flashed", ("time", ent.Comp.PoorEyesightTime)); - _popup.PopupEntity(message, ent, ent, PopupType.MediumCaution); - - DirtyFields(ent, - ent.Comp, - null, - nameof(Scp939Component.PoorEyesight), - nameof(Scp939Component.PoorEyesightTimeStart)); - } - - private void OnTargetEmote(Entity ent, ref EmoteEvent args) - { - MobDidSomething(ent); - } - - private void OnShot(Entity ent, ref GunShotEvent args) - { - if (!_activeQuery.TryComp(args.User, out var visibilityComponent)) - return; - - MobDidSomething((args.User, visibilityComponent)); - } - - private void OnMobStartup(Entity ent, ref ComponentStartup args) - { - if (HasComp(ent)) - return; - - EnsureComp(ent); - } - - private void OnTargetSpoke(Entity ent, ref EntitySpokeEvent args) - { - MobDidSomething(ent); - TryRememberPhrase(ent, args.Message); - } - - private void MobDidSomething(Entity ent) - { - ent.Comp.VisibilityResetCounter++; - DirtyField(ent, ent.Comp, nameof(ActiveScp939VisibilityComponent.VisibilityResetCounter)); - } - - private void UpdateVisibilityTargets() - { - if (_timing.CurTime < _nextVisibilityRefresh) - return; - - _nextVisibilityRefresh = _timing.CurTime + VisibilityRefreshInterval; - _visibilityActiveTargets.Clear(); - - var scpQuery = EntityQueryEnumerator(); - while (scpQuery.MoveNext(out var uid, out var scp939, out var xform)) - { - if (xform.MapID == MapId.Nullspace) - continue; - - _visibilityCandidates.Clear(); - _entityLookup.GetEntitiesInRange(xform.Coordinates, - scp939.VisibilityActivationRange, - _visibilityCandidates, - LookupFlags.Dynamic | LookupFlags.Approximate); - - foreach (var target in _visibilityCandidates) - { - if (target.Owner == uid) - continue; - - _visibilityActiveTargets.Add(target); - EnsureActiveVisibility(target); - } - } - - _visibilityRemovalQueue.Clear(); - - var activeQuery = EntityQueryEnumerator(); - while (activeQuery.MoveNext(out var uid, out _)) - { - if (_visibilityActiveTargets.Contains(uid)) - continue; - - _visibilityRemovalQueue.Add(uid); - } - - foreach (var uid in _visibilityRemovalQueue) - { - RemComp(uid); - } - - _visibilityCandidates.Clear(); - _visibilityRemovalQueue.Clear(); - } - - private void EnsureActiveVisibility(Entity ent) - { - if (!_activeQuery.TryComp(ent, out var active)) - { - active = AddComp(ent); - active.HideTime = ent.Comp.HideTime; - active.MinValue = ent.Comp.MinValue; - active.MaxValue = ent.Comp.MaxValue; - return; - } - - if (!MathHelper.CloseTo(active.HideTime, ent.Comp.HideTime)) - { - active.HideTime = ent.Comp.HideTime; - DirtyField(ent, active, nameof(ActiveScp939VisibilityComponent.HideTime)); - } - - if (active.MinValue != ent.Comp.MinValue) - { - active.MinValue = ent.Comp.MinValue; - DirtyField(ent, active, nameof(ActiveScp939VisibilityComponent.MinValue)); - } - - if (active.MaxValue != ent.Comp.MaxValue) - { - active.MaxValue = ent.Comp.MaxValue; - DirtyField(ent, active, nameof(ActiveScp939VisibilityComponent.MaxValue)); - } - } -} diff --git a/Content.Server/_Scp/Scp939/Scp939System.cs b/Content.Server/_Scp/Scp939/Scp939System.cs deleted file mode 100644 index f671db25756..00000000000 --- a/Content.Server/_Scp/Scp939/Scp939System.cs +++ /dev/null @@ -1,109 +0,0 @@ -using Content.Server.Actions; -using Content.Server.Fluids.EntitySystems; -using Content.Shared._Scp.Scp939; -using Content.Shared.Bed.Sleep; -using Content.Shared.Body.Components; -using Content.Shared.Damage.Systems; -using Content.Shared.Mobs; -using Content.Shared.StatusEffectNew; -using Robust.Server.Audio; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; - -namespace Content.Server._Scp.Scp939; - -public sealed partial class Scp939System : EntitySystem -{ - [Dependency] private readonly SmokeSystem _smokeSystem = default!; - [Dependency] private readonly SleepingSystem _sleepingSystem = default!; - [Dependency] private readonly ActionsSystem _actionsSystem = default!; - [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; - [Dependency] private readonly DamageableSystem _damageableSystem = default!; - [Dependency] private readonly AppearanceSystem _appearanceSystem = default!; - [Dependency] private readonly AudioSystem _audio = default!; - - private readonly SoundSpecifier _critSound = new SoundPathSpecifier("/Audio/_Scp/Scp939/crit.ogg"); - - public override void Initialize() - { - base.Initialize(); - InitializeActions(); - - SubscribeLocalEvent(OnInit); - - SubscribeLocalEvent(OnSleepChanged); - SubscribeLocalEvent(OnMobStateChanged); - - - InitializeVisibility(); - } - - private void OnMobStateChanged(Entity ent, ref MobStateChangedEvent args) - { - if (args.NewMobState != MobState.Critical) - return; - - TrySleep(ent, 360f); - _audio.PlayPvs(_critSound, ent); - } - - private void OnSleepChanged(Entity ent, ref SleepStateChangedEvent args) - { - if (TryComp(ent, out var bloodstreamComponent)) - { - if (args.FellAsleep) - bloodstreamComponent.BloodRefreshAmount = 20; - else - bloodstreamComponent.BloodRefreshAmount = 1; - - Dirty(ent, bloodstreamComponent); - } - - _appearanceSystem.SetData(ent, Scp939Visuals.Sleeping, args.FellAsleep); - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - UpdateVisibilityTargets(); - - var querySleeping = EntityQueryEnumerator(); - while (querySleeping.MoveNext(out var uid, out var scp939Component, out _)) - { - _damageableSystem.TryChangeDamage(uid, scp939Component.HibernationHealingRate * frameTime); - } - - var querySimple = EntityQueryEnumerator(); - while (querySimple.MoveNext(out var uid, out var scp939Component)) - { - if (!scp939Component.PoorEyesight) - continue; - - if (scp939Component.PoorEyesightTimeStart == null) - continue; - - var timeDifference = _timing.CurTime - scp939Component.PoorEyesightTimeStart.Value; - - if (timeDifference > TimeSpan.FromSeconds(scp939Component.PoorEyesightTime)) - { - scp939Component.PoorEyesight = false; - scp939Component.PoorEyesightTimeStart = null; - - DirtyFields(uid, - scp939Component, - null, - nameof(Scp939Component.PoorEyesight), - nameof(Scp939Component.PoorEyesightTimeStart)); - } - } - } - - private void OnInit(Entity ent, ref ComponentInit args) - { - foreach (var action in ent.Comp.Actions) - { - _actionsSystem.AddAction(ent, action); - } - } -} diff --git a/Content.Shared/_Scp/Other/Events/ScpSystemsEvents.cs b/Content.Shared/_Scp/Other/Events/ScpSystemsEvents.cs new file mode 100644 index 00000000000..469666c3459 --- /dev/null +++ b/Content.Shared/_Scp/Other/Events/ScpSystemsEvents.cs @@ -0,0 +1,7 @@ +using Content.Shared.Actions; + +namespace Content.Shared._Scp.Other.Events; + +public sealed partial class ScpRememberPhraseActionEvent : InstantActionEvent; + +public sealed partial class ScpSleepActionEvent : InstantActionEvent; diff --git a/Content.Shared/_Scp/Other/Events/ScpTriggerEvents.cs b/Content.Shared/_Scp/Other/Events/ScpTriggerEvents.cs new file mode 100644 index 00000000000..89903cc41b2 --- /dev/null +++ b/Content.Shared/_Scp/Other/Events/ScpTriggerEvents.cs @@ -0,0 +1,9 @@ +using Content.Shared.Actions; + +namespace Content.Shared._Scp.Other.Events; + +public sealed partial class ScpTriggerOnActionEvent : InstantActionEvent +{ + [DataField] + public string? KeyOut; +} diff --git a/Content.Shared/_Scp/Other/ScpHealOnSleep/SharedScpHealOnSleepData.cs b/Content.Shared/_Scp/Other/ScpHealOnSleep/SharedScpHealOnSleepData.cs new file mode 100644 index 00000000000..2d25165f367 --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpHealOnSleep/SharedScpHealOnSleepData.cs @@ -0,0 +1,15 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared._Scp.Other.ScpSleep; + +[Serializable, NetSerializable] +public enum ScpHealOnSleepLayers : byte +{ + Base = 0 +} + +[Serializable, NetSerializable] +public enum ScpHealOnSleepVisuals : byte +{ + Sleeping = 0, +} diff --git a/Content.Shared/_Scp/Other/ScpOnSoundVisibility/ActiveScpPoorEyesightComponent.cs b/Content.Shared/_Scp/Other/ScpOnSoundVisibility/ActiveScpPoorEyesightComponent.cs new file mode 100644 index 00000000000..cc525f0353b --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpOnSoundVisibility/ActiveScpPoorEyesightComponent.cs @@ -0,0 +1,5 @@ + +namespace Content.Shared._Scp.Other.ScpOnSoundVisibility; + +[RegisterComponent] +public sealed partial class ActiveScpPoorEyesightComponent : Component; diff --git a/Content.Shared/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityComponent.cs b/Content.Shared/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityComponent.cs new file mode 100644 index 00000000000..d038621e817 --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityComponent.cs @@ -0,0 +1,21 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._Scp.Other.ScpOnSoundVisibility; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ScpOnSoundVisibilityComponent : Component +{ + public const float InitialVisibilityAcc = 0.001f; + public const float DefaultHideTime = 2.5f; + public const float DefaultMinValue = 40f; + public const float DefaultMaxValue = 400f; + + [DataField] + public float HideTime = DefaultHideTime; + + [DataField] + public float MinValue = DefaultMinValue; + + [DataField] + public float MaxValue = DefaultMaxValue; +} diff --git a/Content.Shared/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityEvents.cs b/Content.Shared/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityEvents.cs new file mode 100644 index 00000000000..53a6edeea5e --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityEvents.cs @@ -0,0 +1,10 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared._Scp.Other.ScpOnSoundVisibility; + +[Serializable, NetSerializable] +public sealed class ScpOnSoundVisibilityTargetsEvent(NetEntity viewer, NetEntity[] targets) : EntityEventArgs +{ + public readonly NetEntity Viewer = viewer; + public readonly NetEntity[] Targets = targets; +} diff --git a/Content.Shared/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityProtectionComponent.cs b/Content.Shared/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityProtectionComponent.cs new file mode 100644 index 00000000000..b3c8250f780 --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityProtectionComponent.cs @@ -0,0 +1,7 @@ + +using Robust.Shared.GameStates; + +namespace Content.Shared._Scp.Other.ScpOnSoundVisibility; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ScpOnSoundVisibilityProtectionComponent : Component; diff --git a/Content.Shared/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityViewerComponent.cs b/Content.Shared/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityViewerComponent.cs new file mode 100644 index 00000000000..f2aca7ae6f4 --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpOnSoundVisibility/ScpOnSoundVisibilityViewerComponent.cs @@ -0,0 +1,39 @@ + +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared._Scp.Other.ScpOnSoundVisibility; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true)] +public sealed partial class ScpOnSoundVisibilityViewerComponent : Component +{ + [DataField] + public bool PoorEyesOnFlash; + + [DataField, AutoNetworkedField] + public bool PoorEyesight; + + [DataField] + public TimeSpan PoorEyesightTime = TimeSpan.FromSeconds(10f); + + [ViewVariables, AutoNetworkedField] + public TimeSpan? PoorEyesightTimeStart; // Когда начали плохо видеть + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float VisibilityActivationRange = 20f; + + [DataField] + public LocId? OnFlashMessage; + + [DataField] + public EntityWhitelist Protections = new() + { + Components = ["ScpOnSoundVisibilityProtection"] + }; + + [DataField] + public float ExamineHideThreshold = 0.2f; + + [DataField] + public float StatusIconClearThreshold = 0.5f; +} diff --git a/Content.Shared/_Scp/Other/ScpRememberPhrase/ScpRememberPhraseComponent.cs b/Content.Shared/_Scp/Other/ScpRememberPhrase/ScpRememberPhraseComponent.cs new file mode 100644 index 00000000000..a62e3f50451 --- /dev/null +++ b/Content.Shared/_Scp/Other/ScpRememberPhrase/ScpRememberPhraseComponent.cs @@ -0,0 +1,31 @@ + +using Content.Shared.Actions.Components; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._Scp.Other.ScpRememberPhrase; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ScpRememberPhraseComponent : Component +{ + [DataField] + public EntProtoId ActionProto = "RememberPhraseAction"; + + [DataField] + public int MaxRememberedMessages = 20; + + /// + /// Запомненые объектом слова. Ключ - сказанная фраза, значение - пара, в которой ключ имя сказавшего и значение прототип его ттса + /// + [ViewVariables] + public List RememberedMessages = []; + + [ViewVariables] + public EntityUid? ActionEnt; +} + +public record struct RememberedMessage( + string Message, + string SpeakerName, + string? TtsVoice +); diff --git a/Content.Shared/_Scp/Scp939/ActiveScp939VisibilityComponent.cs b/Content.Shared/_Scp/Scp939/ActiveScp939VisibilityComponent.cs deleted file mode 100644 index d28eeba9bf6..00000000000 --- a/Content.Shared/_Scp/Scp939/ActiveScp939VisibilityComponent.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared._Scp.Scp939; - -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(raiseAfterAutoHandleState: true, fieldDeltas: true)] -public sealed partial class ActiveScp939VisibilityComponent : Component -{ - [ViewVariables] - public float VisibilityAcc = Scp939VisibilityComponent.InitialVisibilityAcc; - - [AutoNetworkedField] - public uint VisibilityResetCounter; - - [DataField, AutoNetworkedField] - public float HideTime = Scp939VisibilityComponent.DefaultHideTime; - - [DataField, AutoNetworkedField] - public int MinValue = Scp939VisibilityComponent.DefaultMinValue; - - [DataField, AutoNetworkedField] - public int MaxValue = Scp939VisibilityComponent.DefaultMaxValue; - - [NonSerialized] - public uint LastHandledVisibilityResetCounter; -} diff --git a/Content.Shared/_Scp/Scp939/Protection/Scp939Protection.cs b/Content.Shared/_Scp/Scp939/Protection/Scp939ProtectionComponent.cs similarity index 100% rename from Content.Shared/_Scp/Scp939/Protection/Scp939Protection.cs rename to Content.Shared/_Scp/Scp939/Protection/Scp939ProtectionComponent.cs diff --git a/Content.Shared/_Scp/Scp939/Scp939Component.cs b/Content.Shared/_Scp/Scp939/Scp939Component.cs index 56d5ed483fc..a188f254866 100644 --- a/Content.Shared/_Scp/Scp939/Scp939Component.cs +++ b/Content.Shared/_Scp/Scp939/Scp939Component.cs @@ -1,80 +1,9 @@ -using Content.Shared.Chemistry.Components; -using Content.Shared.Damage; using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; namespace Content.Shared._Scp.Scp939; -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true)] -public sealed partial class Scp939Component : Component -{ - [DataField] - public Solution SmokeSolution = new([new("АМН-С227", 40)]); - - [DataField] - public float SmokeDuration = 30.0f; - - [DataField] - public int SmokeSpreadRadius = 10; - - [DataField] - public EntProtoId SmokeProtoId = "АМН-С227Smoke"; - - [DataField] - public List Actions = new() - { - "Scp939Mimic", - "Scp939Smoke", - "Scp939Sleep", - }; - - [DataField] - public float HibernationDuration = 60f; - - [DataField] - public DamageSpecifier HibernationHealingRate = new() - { - DamageDict = new() - { - { "Blunt", -20f }, - { "Slash", -20f }, - { "Piercing", -20f }, - { "Heat", -20f }, - { "Shock", -20f }, - { "Bloodloss", -20f}, - { "Genetic", -20f }, - { "Toxin", -20f }, - { "Airloss", -20f }, - { "Asphyxiation", -20f }, - { "Poison", -20f }, - { "Radiation", -20f }, - { "Cellular", -20f} - } - }; - - #region Vision - - [DataField, AutoNetworkedField] - public bool PoorEyesight; - - [DataField, AutoNetworkedField] - public float PoorEyesightTime = 10f; // Секунды - - [AutoNetworkedField] - public TimeSpan? PoorEyesightTimeStart; // Когда начали плохо видеть - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float VisibilityActivationRange = 20f; - - #endregion - - [DataField] - public int MaxRememberedMessages = 20; - - /// - /// Запомненые объектом слова. Ключ - сказанная фраза, значение - пара, в которой ключ имя сказавшего и значение прототип его ттса - /// - [ViewVariables] - public Dictionary> RememberedMessages = new(); - -} +/// +/// Пометочный компонент для вайт-листов +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class Scp939Component : Component; diff --git a/Content.Shared/_Scp/Scp939/Scp939VisabilityComponent.cs b/Content.Shared/_Scp/Scp939/Scp939VisabilityComponent.cs deleted file mode 100644 index 94a85140e37..00000000000 --- a/Content.Shared/_Scp/Scp939/Scp939VisabilityComponent.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Content.Shared._Scp.Scp939; - -[RegisterComponent] -public sealed partial class Scp939VisibilityComponent : Component -{ - public const float InitialVisibilityAcc = 0.001f; - public const float DefaultHideTime = 2.5f; - public const int DefaultMinValue = 40; - public const int DefaultMaxValue = 400; - - [DataField] - public float HideTime = DefaultHideTime; - - [DataField] - public int MinValue = DefaultMinValue; - - [DataField] - public int MaxValue = DefaultMaxValue; -} diff --git a/Content.Shared/_Scp/Scp939/SharedScp939Data.cs b/Content.Shared/_Scp/Scp939/SharedScp939Data.cs index 35729d80ffa..fc8ee533f0e 100644 --- a/Content.Shared/_Scp/Scp939/SharedScp939Data.cs +++ b/Content.Shared/_Scp/Scp939/SharedScp939Data.cs @@ -1,12 +1,5 @@ -using Content.Shared.Actions; using Robust.Shared.Serialization; -namespace Content.Shared._Scp.Scp939; - -public sealed partial class Scp939GasAction : InstantActionEvent; -public sealed partial class Scp939SleepAction : InstantActionEvent; -public sealed partial class Scp939MimicActionEvent : InstantActionEvent; - [Serializable, NetSerializable] public enum Scp939Layers : byte { diff --git a/Content.Shared/_Scp/ScpMask/ScpMaskCheckerComponent.cs b/Content.Shared/_Scp/ScpMask/ScpMaskCheckerComponent.cs new file mode 100644 index 00000000000..36ff88559ca --- /dev/null +++ b/Content.Shared/_Scp/ScpMask/ScpMaskCheckerComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._Scp.ScpMask; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ScpMaskCheckerComponent : Component +{ + [DataField] + public bool BlockTriggers = true; +} diff --git a/Content.Shared/_Scp/ScpMask/ScpMaskSystem.Checker.cs b/Content.Shared/_Scp/ScpMask/ScpMaskSystem.Checker.cs new file mode 100644 index 00000000000..e054a3641e7 --- /dev/null +++ b/Content.Shared/_Scp/ScpMask/ScpMaskSystem.Checker.cs @@ -0,0 +1,23 @@ +using Content.Shared.Trigger; + +namespace Content.Shared._Scp.ScpMask; + +public sealed partial class ScpMaskSystem +{ + private void InitializeChecker() + { + SubscribeLocalEvent(OnAttemptTrigger); + } + + private void OnAttemptTrigger(Entity ent, ref AttemptTriggerEvent args) + { + if (!ent.Comp.BlockTriggers) + return; + + if (!TryGetScpMask(ent, out var scpMask)) + return; + + TryCreatePopup(ent, scpMask); + args.Cancelled = true; + } +} diff --git a/Content.Shared/_Scp/ScpMask/ScpMaskSystem.cs b/Content.Shared/_Scp/ScpMask/ScpMaskSystem.cs index 20b65001c24..eb74d2685db 100644 --- a/Content.Shared/_Scp/ScpMask/ScpMaskSystem.cs +++ b/Content.Shared/_Scp/ScpMask/ScpMaskSystem.cs @@ -43,6 +43,7 @@ public override void Initialize() SubscribeLocalEvent(OnDamage); + InitializeChecker(); InitializeEquipment(); InitializeRestrictions(); } diff --git a/Content.Shared/_Scp/Trigger/ScpTriggerOnAction/ScpTriggerOnActionListenerComponent.cs b/Content.Shared/_Scp/Trigger/ScpTriggerOnAction/ScpTriggerOnActionListenerComponent.cs new file mode 100644 index 00000000000..1b879243486 --- /dev/null +++ b/Content.Shared/_Scp/Trigger/ScpTriggerOnAction/ScpTriggerOnActionListenerComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._Scp.Trigger.ScpTriggerOnAction; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ScpTriggerOnActionListenerComponent : Component +{ + +} diff --git a/Content.Shared/_Scp/Trigger/ScpTriggerOnAction/ScpTriggerOnActionSystem.cs b/Content.Shared/_Scp/Trigger/ScpTriggerOnAction/ScpTriggerOnActionSystem.cs new file mode 100644 index 00000000000..5e510e2bae6 --- /dev/null +++ b/Content.Shared/_Scp/Trigger/ScpTriggerOnAction/ScpTriggerOnActionSystem.cs @@ -0,0 +1,21 @@ +using Content.Shared._Scp.Other.Events; +using Content.Shared.Trigger.Systems; + +namespace Content.Shared._Scp.Trigger.ScpTriggerOnAction; + +public sealed class ScpTriggerOnActionSystem : EntitySystem +{ + [Dependency] private readonly TriggerSystem _trigger = default!; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAction); + } + + private void OnAction(Entity ent, ref ScpTriggerOnActionEvent args) + { + if (_trigger.Trigger(ent, ent, args.KeyOut)) + args.Handled = true; + } +} diff --git a/Resources/Locale/ru-RU/_prototypes/_scp/actions/scp-actions.ftl b/Resources/Locale/ru-RU/_prototypes/_scp/actions/scp-actions.ftl new file mode 100644 index 00000000000..ed021336032 --- /dev/null +++ b/Resources/Locale/ru-RU/_prototypes/_scp/actions/scp-actions.ftl @@ -0,0 +1,6 @@ +ent-RememberPhraseAction = Имитация голоса + .desc = Позволяет имитировать голоса. +ent-ScpSmokeAction = Выпуск газа + .desc = Выпускает облако газа. +ent-ScpSleepAction = Регенеративный сон + .desc = Позволяет погрузиться в глубокий сон для быстрого исцеления. diff --git a/Resources/Locale/ru-RU/_prototypes/_scp/actions/scp939.ftl b/Resources/Locale/ru-RU/_prototypes/_scp/actions/scp939.ftl index 42e7d0b5dc5..4f7c5cd1ddb 100644 --- a/Resources/Locale/ru-RU/_prototypes/_scp/actions/scp939.ftl +++ b/Resources/Locale/ru-RU/_prototypes/_scp/actions/scp939.ftl @@ -1,6 +1,6 @@ -ent-Scp939Mimic = Имитация голоса +ent-Scp939MimicAction = { ent-RememberPhraseAction } .desc = Позволяет SCP-939 имитировать голоса для приманивания жертв. -ent-Scp939Smoke = Амнезиаковый газ +ent-Scp939SmokeAction = Амнезиаковый газ .desc = Выпускает облако амнезиакового газа для дезориентации жертв. -ent-Scp939Sleep = Регенеративный сон +ent-Scp939SleepAction = { ent-ScpSleepAction } .desc = Позволяет SCP-939 погрузиться в глубокий сон для быстрого исцеления. diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index 2c47cd24d6d..406a308386b 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -108,6 +108,7 @@ - type: MobMover - type: MovementSpeedModifier - type: LagCompensation + - type: ScpOnSoundVisibility # Fire edit - type: CanBeCarried # Sunrise-Edit - type: entity diff --git a/Resources/Prototypes/_Scp/Actions/scp939.yml b/Resources/Prototypes/_Scp/Actions/scp939.yml index ca1cdfbb32e..6711ed5bf19 100644 --- a/Resources/Prototypes/_Scp/Actions/scp939.yml +++ b/Resources/Prototypes/_Scp/Actions/scp939.yml @@ -1,37 +1,17 @@ - type: entity - id: Scp939Mimic + parent: RememberPhraseAction + id: Scp939MimicAction name: Voice Mimicry description: Allows SCP-939 to mimic voices to lure prey. - components: - - type: Action - useDelay: 5 - icon: - sprite: _Scp/Actions/scp-939.rsi - state: mimic - - type: InstantAction - event: !type:Scp939MimicActionEvent - type: entity - id: Scp939Smoke + parent: ScpSmokeAction + id: Scp939SmokeAction name: Amnestic Gas description: Releases a cloud of amnestic gas to disorient victims. - components: - - type: Action - useDelay: 300 - icon: - sprite: _Scp/Actions/scp-939.rsi - state: smaug - - type: InstantAction - event: !type:Scp939GasAction - type: entity - id: Scp939Sleep + parent: ScpSleepAction + id: Scp939SleepAction name: Regenerative Slumber description: Allows SCP-939 to enter a deep sleep for rapid healing. - components: - - type: Action - icon: - sprite: _Scp/Actions/scp-939.rsi - state: sleep - - type: InstantAction - event: !type:Scp939SleepAction diff --git a/Resources/Prototypes/_Scp/Actions/scp_actions.yml b/Resources/Prototypes/_Scp/Actions/scp_actions.yml new file mode 100644 index 00000000000..78121adf587 --- /dev/null +++ b/Resources/Prototypes/_Scp/Actions/scp_actions.yml @@ -0,0 +1,38 @@ +- type: entity + id: RememberPhraseAction + name: Voice Mimicry + description: Allows to mimic voices to lure prey. + components: + - type: Action + useDelay: 5 + icon: + sprite: _Scp/Actions/scp-actions.rsi + state: mimic + - type: InstantAction + event: !type:ScpRememberPhraseActionEvent + +- type: entity + id: ScpSmokeAction + name: Release Gas + description: Releases a cloud of gas to disorient victims. + components: + - type: Action + useDelay: 300 + icon: + sprite: _Scp/Actions/scp-actions.rsi + state: smaug + - type: InstantAction + event: !type:ScpTriggerOnActionEvent + keyOut: scpSmokeTrigger + +- type: entity + id: ScpSleepAction + name: Slumber + description: Allows to enter a deep sleep. + components: + - type: Action + icon: + sprite: _Scp/Actions/scp-actions.rsi + state: sleep + - type: InstantAction + event: !type:ScpSleepActionEvent diff --git a/Resources/Prototypes/_Scp/Entities/Mobs/Player/Scp/Main/scp939.yml b/Resources/Prototypes/_Scp/Entities/Mobs/Player/Scp/Main/scp939.yml index 779af663a6d..4c55dd616d9 100644 --- a/Resources/Prototypes/_Scp/Entities/Mobs/Player/Scp/Main/scp939.yml +++ b/Resources/Prototypes/_Scp/Entities/Mobs/Player/Scp/Main/scp939.yml @@ -17,11 +17,64 @@ drawdepth: Mobs sprite: _Scp/Mobs/Scp/scp-939.rsi layers: - - map: [ "enum.Scp939Layers.Base" ] + - map: [ "base" ] state: alive + - type: GenericVisualizer + visuals: + enum.MobStateVisuals.State: + base: + Dead: { state: dead } + Critical: { state: dead } + Alive: { state: alive } + enum.ScpHealOnSleepVisuals.Sleeping: + base: + True: { state: asleep } + - type: ActiveListener + range: 20 - type: Scp class: Keter - type: Scp939 + - type: ScpOnSoundVisibilityViewer + poorEyesOnFlash: true + onFlashMessage: scp939-flashed + protections: + components: + - Scp939Protection + - type: ScpHealOnSleep + actionProto: Scp939SleepAction + hibernationHealingRate: + types: + Blunt: -20 + Slash: -20 + Piercing: -20 + Heat: -20 + Shock: -20 + Bloodloss: -20 + Genetic: -20 + Toxin: -20 + Airloss: -20 + Asphyxiation: -20 + Poison: -20 + Radiation: -20 + Cellular: -20 + hibernationStates: + - Critical + - type: ScpRememberPhrase + actionProto: Scp939MimicAction + - type: ScpTriggerOnActionListener + - type: SmokeOnTrigger + keysIn: + - scpSmokeTrigger + solution: + reagents: + - ReagentId: АМН-С227 + Quantity: 40 + spreadAmount: 10 + smokePrototype: АМН-С227Smoke + - type: ScpMaskChecker + - type: ActionGrant + actions: + - Scp939SmokeAction - type: MeleeWeapon altDisarm: false soundHit: diff --git a/Resources/Textures/_Scp/Actions/scp-939.rsi/meta.json b/Resources/Textures/_Scp/Actions/scp-actions.rsi/meta.json similarity index 100% rename from Resources/Textures/_Scp/Actions/scp-939.rsi/meta.json rename to Resources/Textures/_Scp/Actions/scp-actions.rsi/meta.json diff --git a/Resources/Textures/_Scp/Actions/scp-939.rsi/mimic.png b/Resources/Textures/_Scp/Actions/scp-actions.rsi/mimic.png similarity index 100% rename from Resources/Textures/_Scp/Actions/scp-939.rsi/mimic.png rename to Resources/Textures/_Scp/Actions/scp-actions.rsi/mimic.png diff --git a/Resources/Textures/_Scp/Actions/scp-939.rsi/sleep.png b/Resources/Textures/_Scp/Actions/scp-actions.rsi/sleep.png similarity index 100% rename from Resources/Textures/_Scp/Actions/scp-939.rsi/sleep.png rename to Resources/Textures/_Scp/Actions/scp-actions.rsi/sleep.png diff --git a/Resources/Textures/_Scp/Actions/scp-939.rsi/smaug.png b/Resources/Textures/_Scp/Actions/scp-actions.rsi/smaug.png similarity index 100% rename from Resources/Textures/_Scp/Actions/scp-939.rsi/smaug.png rename to Resources/Textures/_Scp/Actions/scp-actions.rsi/smaug.png