diff --git a/Content.Server/_Scp/Blinking/ReducedBlinking/ReducedBlinkingSystem.cs b/Content.Server/_Scp/Blinking/ReducedBlinking/ReducedBlinkingSystem.cs index eab1a8d1507..dcec73a14ef 100644 --- a/Content.Server/_Scp/Blinking/ReducedBlinking/ReducedBlinkingSystem.cs +++ b/Content.Server/_Scp/Blinking/ReducedBlinking/ReducedBlinkingSystem.cs @@ -6,6 +6,7 @@ namespace Content.Server._Scp.Blinking.ReducedBlinking; public sealed class ReducedBlinkingSystem : SharedReducedBlinkingSystem { + [Dependency] private readonly SharedBlinkingSystem _blinking = default!; [Dependency] private readonly PopupSystem _popup = default!; public override void Initialize() @@ -59,8 +60,8 @@ private void OnUserStartup(Entity ent, ref C if (!TryComp(ent, out var blinkable)) return; - blinkable.BlinkingInterval += ent.Comp.BlinkingBonusDuration; - DirtyField(ent.Owner, blinkable, nameof(BlinkableComponent.BlinkingInterval)); + blinkable.BlinkingIntervalBonus += ent.Comp.BlinkingIntervalBonus; + DirtyField(ent.Owner, blinkable, nameof(BlinkableComponent.BlinkingIntervalBonus)); } private void OnUserShutdown(Entity ent, ref ComponentShutdown _) @@ -68,8 +69,12 @@ private void OnUserShutdown(Entity ent, ref if (!TryComp(ent, out var blinkable)) return; - blinkable.BlinkingInterval -= ent.Comp.BlinkingBonusDuration; - DirtyField(ent.Owner, blinkable, nameof(BlinkableComponent.BlinkingInterval)); + blinkable.BlinkingIntervalBonus -= ent.Comp.BlinkingIntervalBonus; + if (blinkable.BlinkingIntervalBonus < TimeSpan.Zero) + blinkable.BlinkingIntervalBonus = TimeSpan.Zero; + + DirtyField(ent.Owner, blinkable, nameof(BlinkableComponent.BlinkingIntervalBonus)); + _blinking.ResetBlink(ent.Owner, predicted: false); _popup.PopupEntity(Loc.GetString("eye-droplets-end"), ent, ent); } diff --git a/Content.Shared/_Scp/Blinking/BlinkableComponent.cs b/Content.Shared/_Scp/Blinking/BlinkableComponent.cs index 10d3eb72e27..c1f910bcf75 100644 --- a/Content.Shared/_Scp/Blinking/BlinkableComponent.cs +++ b/Content.Shared/_Scp/Blinking/BlinkableComponent.cs @@ -33,6 +33,18 @@ public sealed partial class BlinkableComponent : Component [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField] public TimeSpan BlinkingIntervalVariance = TimeSpan.FromSeconds(4f); + /// + /// Привыкание к средствам, которые помогают не моргать. + /// + [ViewVariables, AutoNetworkedField] + public float ReducedBlinkingTolerance = 0f; + + /// + /// Накопительный бонус к интервалу между морганиями. + /// + [ViewVariables, AutoNetworkedField] + public TimeSpan BlinkingIntervalBonus; + /// /// Время следующего моргания. /// diff --git a/Content.Shared/_Scp/Blinking/ReducedBlinking/ActiveReducedBlinkingUserComponent.cs b/Content.Shared/_Scp/Blinking/ReducedBlinking/ActiveReducedBlinkingUserComponent.cs index 2f1a4671228..d3a31204388 100644 --- a/Content.Shared/_Scp/Blinking/ReducedBlinking/ActiveReducedBlinkingUserComponent.cs +++ b/Content.Shared/_Scp/Blinking/ReducedBlinking/ActiveReducedBlinkingUserComponent.cs @@ -6,7 +6,7 @@ namespace Content.Shared._Scp.Blinking.ReducedBlinking; public sealed partial class ActiveReducedBlinkingUserComponent : Component { [DataField(required:true), AutoNetworkedField] - public TimeSpan BlinkingBonusDuration; + public TimeSpan BlinkingIntervalBonus; [ViewVariables] public TimeSpan FirstBonusEndTime; diff --git a/Content.Shared/_Scp/Blinking/ReducedBlinking/ReducedBlinkingSystem.cs b/Content.Shared/_Scp/Blinking/ReducedBlinking/ReducedBlinkingSystem.cs index 8e6672217a5..aabeba70452 100644 --- a/Content.Shared/_Scp/Blinking/ReducedBlinking/ReducedBlinkingSystem.cs +++ b/Content.Shared/_Scp/Blinking/ReducedBlinking/ReducedBlinkingSystem.cs @@ -1,4 +1,5 @@ -using Content.Shared.DoAfter; +using Content.Shared._Sunrise.Random; +using Content.Shared.DoAfter; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Popups; @@ -12,6 +13,7 @@ namespace Content.Shared._Scp.Blinking.ReducedBlinking; // TODO: Переделать на химикат и дать возможно варить, используя реагент 173 и нечто из синтезатора реагентов. // TODO: Добавить звук закапывания капель. +// TODO: Анхардкод: Перенос значений в компоненты public abstract class SharedReducedBlinkingSystem : EntitySystem { [Dependency] private readonly SharedBlinkingSystem _blinking = default!; @@ -19,9 +21,15 @@ public abstract class SharedReducedBlinkingSystem : EntitySystem [Dependency] private readonly UseDelaySystem _useDelay = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly RandomPredictedSystem _random = default!; [Dependency] private readonly INetManager _net = default!; [Dependency] protected readonly IGameTiming Timing = default!; + private const float ToleranceReduceUseLimit = 90.0f; + private const float MinReducedBlinkingEffectiveness = 0.15f; + private const float MinToleranceIncrease = 15f; + private const float MaxToleranceIncrease = 25f; + public override void Initialize() { base.Initialize(); @@ -67,21 +75,39 @@ private void OnSuccess(Entity ent, ref EyeDropletsUsed return; } - blinkable.AdditionalBlinkingTime = ent.Comp.FirstBlinkingBonusTime; - DirtyField(target, blinkable, nameof(BlinkableComponent.AdditionalBlinkingTime)); - _blinking.ResetBlink(target); - _useDelay.TryResetDelay(ent); + if (blinkable.ReducedBlinkingTolerance >= ToleranceReduceUseLimit) + { + _popup.PopupPredicted(Loc.GetString("eye-droplets-tolerance-too-high"), ent, ent, PopupType.LargeCaution); + return; + } + + var effectiveness = GetReducedBlinkingEffectiveness(blinkable.ReducedBlinkingTolerance); + + var firstBonusTime = ent.Comp.FirstBlinkingBonusTime * effectiveness; + var otherBonusTime = ent.Comp.OtherBlinkingBonusTime * effectiveness; + var bonusDuration = ent.Comp.OtherBlinkingBonusDuration * effectiveness; var comp = new ActiveReducedBlinkingUserComponent { - BlinkingBonusDuration = ent.Comp.OtherBlinkingBonusDuration, - FirstBonusEndTime = Timing.CurTime + ent.Comp.FirstBlinkingBonusTime, - AllBonusEndTime = Timing.CurTime + ent.Comp.OtherBlinkingBonusTime, + BlinkingIntervalBonus = bonusDuration, + FirstBonusEndTime = Timing.CurTime + firstBonusTime, + AllBonusEndTime = Timing.CurTime + otherBonusTime, }; AddComp(target, comp, true); Dirty(target, comp); + blinkable.AdditionalBlinkingTime += firstBonusTime; + blinkable.ReducedBlinkingTolerance = MathF.Min( + 100f, + blinkable.ReducedBlinkingTolerance + _random.NextFloatForEntity(target, MinToleranceIncrease, MaxToleranceIncrease)); + + DirtyField(target, blinkable, nameof(BlinkableComponent.AdditionalBlinkingTime)); + DirtyField(target, blinkable, nameof(BlinkableComponent.ReducedBlinkingTolerance)); + + _blinking.ResetBlink(target); + _useDelay.TryResetDelay(ent); + if (ent.Comp.UseSound != null) _audio.PlayPredicted(ent.Comp.UseSound, ent, target); @@ -94,6 +120,12 @@ private void OnSuccess(Entity ent, ref EyeDropletsUsed if (ent.Comp.UsageCount <= 0 && _net.IsServer) QueueDel(ent); } + + private static float GetReducedBlinkingEffectiveness(float tolerance) + { + var normalized = Math.Clamp(tolerance / 100f, 0f, 1f); + return 1f - normalized * (1f - MinReducedBlinkingEffectiveness); + } } [Serializable, NetSerializable] diff --git a/Content.Shared/_Scp/Blinking/SharedBlinkingSystem.cs b/Content.Shared/_Scp/Blinking/SharedBlinkingSystem.cs index 56f146488ff..ff9c1a24dc3 100644 --- a/Content.Shared/_Scp/Blinking/SharedBlinkingSystem.cs +++ b/Content.Shared/_Scp/Blinking/SharedBlinkingSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Shared._Scp.Blinking.ReducedBlinking; using Content.Shared._Scp.Helpers; using Content.Shared._Scp.Scp173; using Content.Shared._Scp.Watching; @@ -12,6 +13,7 @@ namespace Content.Shared._Scp.Blinking; +// TODO: Анхардкод: Перенос значений в компоненты // TODO: Избавиться от членения на EyeClosing и Blinking. // Они слишком сильно переплетаются, чтобы их так разделять. // Вместо этого разделить систему на апдейт + обработку ивентов | API + хелперы + ивенты @@ -23,7 +25,10 @@ public abstract partial class SharedBlinkingSystem : EntitySystem [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly INetManager _net = default!; + private const float ReducedBlinkingToleranceDecayPerSecond = 0.05f; + protected EntityQuery BlinkableQuery; + protected EntityQuery ActiveReducedBlinkingQuery; public override void Initialize() { @@ -37,6 +42,7 @@ public override void Initialize() InitializeEyeClosing(); BlinkableQuery = GetEntityQuery(); + ActiveReducedBlinkingQuery = GetEntityQuery(); } #region Event handlers @@ -110,6 +116,19 @@ public override void Update(float frameTime) var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var blinkableComponent)) { + if (blinkableComponent.ReducedBlinkingTolerance > 0f) + { + if (!ActiveReducedBlinkingQuery.HasComp(uid)) + { + blinkableComponent.ReducedBlinkingTolerance = MathF.Max( + 0f, + blinkableComponent.ReducedBlinkingTolerance - ReducedBlinkingToleranceDecayPerSecond * frameTime + ); + + DirtyField(uid, blinkableComponent, nameof(BlinkableComponent.ReducedBlinkingTolerance)); + } + } + var blinkableEntity = (uid, blinkableComponent); if (TryOpenEyes(blinkableEntity)) @@ -156,7 +175,11 @@ public void SetNextBlink(Entity ent, TimeSpan interval, Tim if (interval < TimeSpan.Zero) interval = TimeSpan.Zero; - ent.Comp.NextBlink = _timing.CurTime + interval + variance.Value + ent.Comp.AdditionalBlinkingTime; + var nextBlinkDelay = interval + variance.Value + ent.Comp.BlinkingIntervalBonus + ent.Comp.AdditionalBlinkingTime; + if (nextBlinkDelay < TimeSpan.Zero) + nextBlinkDelay = TimeSpan.Zero; + + ent.Comp.NextBlink = _timing.CurTime + nextBlinkDelay; ent.Comp.AdditionalBlinkingTime = TimeSpan.Zero; if (!predicted) diff --git a/Resources/Locale/ru-RU/_strings/_scp/blinking/blinking.ftl b/Resources/Locale/ru-RU/_strings/_scp/blinking/blinking.ftl index 91de6795f4a..0ab23e7e816 100644 --- a/Resources/Locale/ru-RU/_strings/_scp/blinking/blinking.ftl +++ b/Resources/Locale/ru-RU/_strings/_scp/blinking/blinking.ftl @@ -1,6 +1,7 @@ eye-droplets-failed = Глаза { $name } должны быть открыты eye-droplets-used = { $name } закапывает капли себе в глаза eye-droplets-first-bonus-end = Глаза начинают пересыхать, придется начать моргать +eye-droplets-tolerance-too-high = Кажется, что мои глаза привыкли к каплям! eye-droplets-end = Глаза пересохли, кажется капли выветрились! close-eye-phrase-1 = Моргаю! close-eye-phrase-2 = Моргаю!!