Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions Content.Server/_Scp/Fear/FearSystem.Fears.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Runtime.InteropServices;
using Content.Server._Scp.Shaders.Highlighting;
using Content.Server._Sunrise.Mood;
using Content.Shared._Scp.Blinking;
using Content.Shared._Scp.Fear;
using Content.Shared._Scp.Fear.Components;
using Content.Shared._Scp.Fear.Components.Fears;
Expand Down Expand Up @@ -52,13 +52,21 @@ private void OnMobStateChanged(MobStateChangedEvent ev)
var toggleUsed = new ItemToggledEvent(false, activated, null);
RaiseLocalEvent(ev.Target, ref toggleUsed);

// Если activated = true, значит человек умер.
// Поэтому код ниже требует true, так как реализует логику для смерти.
if (!activated)
return;

var whoSaw = _watching.GetAllEntitiesVisibleTo<MoodComponent>(ev.Target);
using var realWatchers = ListPoolEntity<BlinkableComponent>.Rent();
if (!_watching.TryGetWatchers(ev.Target, realWatchers.Value, flags: LookupFlags.Dynamic))
return;

foreach (var uid in whoSaw)
foreach (var uid in realWatchers.Value)
{
// Убийца не будет печалиться смерти убитого
if (uid.Owner == ev.Origin)
continue;

AddNegativeMoodEffect(uid, MoodSomeoneDiedOnMyEyes);
}
}
Expand All @@ -80,7 +88,7 @@ private void UpdateHemophobia()
continue;

_hemophobiaBloodList.Clear();
var bloodAmount = _helpers.GetAroundSolutionVolume(uid, hemophobia.Reagent, in _hemophobiaBloodList);
var bloodAmount = _helpers.GetAroundSolutionVolume(uid, hemophobia.Reagent, _hemophobiaBloodList);
var requiredBloodAmount = hemophobia.BloodRequiredPerState[fear.State];

if (bloodAmount <= requiredBloodAmount)
Expand Down
7 changes: 2 additions & 5 deletions Content.Server/_Scp/Fear/FearSystem.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Linq;
using Content.Shared._Scp.Fear;
using Content.Shared._Scp.Fear;
using Content.Shared._Scp.Fear.Components;
using Content.Shared._Scp.Fear.Systems;
using Content.Shared._Sunrise.Mood;
Expand Down Expand Up @@ -80,11 +79,9 @@ public bool TryCalmDown(Entity<FearComponent> ent)
if (_activeFearEffects.HasComp(ent))
return false;

var visibleFearSources = _watching.GetAllVisibleTo<FearSourceComponent>(ent.Owner, ent.Comp.SeenBlockerLevel);

// Проверка на то, что мы в данный момент не смотрим на какую-то страшную сущность.
// Нельзя успокоиться, когда мы смотрим на источник страха.
if (visibleFearSources.Any())
if (_watching.TryGetAnyEntitiesVisibleTo<FearSourceComponent>(ent.Owner, ent.Comp.SeenBlockerLevel))
return false;

var newFearState = GetDecreasedLevel(ent.Comp.State);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ public sealed partial class ArtifactScp096MadnessComponent : Component
public float Radius = 12f;

[DataField]
public float Percent = 70f;
public float Percent = 0.7f;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System.Linq;
using Content.Server._Scp.Scp096;
using Content.Server._Sunrise.Helpers;
using Content.Shared._Scp.Blinking;
using Content.Shared._Scp.Scp096.Main.Components;
using Content.Shared._Scp.ScpMask;
using Content.Shared._Scp.Watching;
using Content.Shared._Sunrise.Helpers;
using Content.Shared.Xenoarchaeology.Artifact;
using Content.Shared.Xenoarchaeology.Artifact.XAE;
Expand All @@ -15,7 +15,7 @@ public sealed class ArtifactScp096MadnessSystem : BaseXAESystem<ArtifactScp096Ma
{
[Dependency] private readonly ScpMaskSystem _scpMask = default!;
[Dependency] private readonly Scp096System _scp096 = default!;
[Dependency] private readonly EyeWatchingSystem _watching = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SunriseHelpersSystem _helpers = default!;
[Dependency] private readonly IRobustRandom _random = default!;

Expand All @@ -24,7 +24,7 @@ protected override void OnActivated(Entity<ArtifactScp096MadnessComponent> ent,
if (!_helpers.TryGetFirst<Scp096Component>(out var scp096))
return;

var targets = _watching.GetWatchers(ent.Owner)
var targets = _lookup.GetEntitiesInRange<BlinkableComponent>(Transform(scp096.Value).Coordinates, ent.Comp.Radius, LookupFlags.Dynamic)
.ToList()
.ShuffleRobust(_random)
.TakePercentage(ent.Comp.Percent);
Expand All @@ -37,5 +37,7 @@ protected override void OnActivated(Entity<ArtifactScp096MadnessComponent> ent,
// TODO: Пофиксить разрыв маски много раз
_scpMask.TryTear(scp096.Value);
}

// TODO: Звук
}
}
6 changes: 3 additions & 3 deletions Content.Server/_Scp/Scp173/Scp173System.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ private void OnStructureDamage(Entity<Scp173Component> uid, ref Scp173DamageStru
return;
}

if (Watching.IsWatched(uid.Owner))
if (Watching.IsWatchedByAny(uid))
{
var message = Loc.GetString("scp173-fast-movement-too-many-watchers");
_popup.PopupEntity(message, uid, uid, PopupType.LargeCaution);
Expand Down Expand Up @@ -185,7 +185,7 @@ private void OnClog(Entity<Scp173Component> ent, ref Scp173ClogAction args)
return;
}

if (Watching.IsWatched(ent.Owner))
if (Watching.IsWatchedByAny(ent))
{
var message = Loc.GetString("scp173-fast-movement-too-many-watchers");
_popup.PopupEntity(message, ent, ent, PopupType.LargeCaution);
Expand Down Expand Up @@ -253,7 +253,7 @@ private void OnFastMovement(Entity<Scp173Component> ent, ref Scp173FastMovementA
return;
}

if (Watching.IsWatched(ent.Owner, out var watchersCount) && watchersCount > ent.Comp.MaxWatchers)
if (Watching.TryGetWatchers(ent, out var watchersCount) && watchersCount > ent.Comp.MaxWatchers)
{
var message = Loc.GetString("scp173-fast-movement-too-many-watchers");
_popup.PopupEntity(message, ent, ent, PopupType.LargeCaution);
Expand Down
10 changes: 5 additions & 5 deletions Content.Shared/_Scp/Blinking/SharedBlinkingSystem.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.Linq;
using Content.Shared._Scp.Scp096.Main.Components;
using Content.Shared._Scp.Helpers;
using Content.Shared._Scp.Scp173;
using Content.Shared._Scp.Watching;
using Content.Shared._Sunrise.Random;
Expand Down Expand Up @@ -255,11 +255,11 @@ protected void UpdateAlert(Entity<BlinkableComponent> ent)
protected bool IsScpNearby(EntityUid player)
{
// Получаем всех Scp с механиками зрения, которые видят игрока
var allScp173InView = _watching.GetAllVisibleTo<Scp173Component>(player);
var allScp096InView = _watching.GetAllVisibleTo<Scp096Component>(player);
using var scp173List = ListPoolEntity<Scp173Component>.Rent();
if (!_watching.TryGetAllEntitiesVisibleTo(player, scp173List.Value))
return false;

return allScp173InView.Any(e => _watching.CanBeWatched(player, e))
|| allScp096InView.Any(e => _watching.CanBeWatched(player, e));
return scp173List.Value.Any(e => _watching.CanBeWatched(player, e));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ public abstract partial class SharedFearSystem
/// </summary>
private void HighLightAllVisibleFears(Entity<FearComponent> ent)
{
var visibleFearSources =
_watching.GetAllEntitiesVisibleTo<FearSourceComponent>(ent.Owner, ent.Comp.SeenBlockerLevel);
using var fearSources = ListPoolEntity<FearSourceComponent>.Rent();
if (!_watching.TryGetWatchingTargets(ent.Owner, fearSources.Value, ent.Comp.SeenBlockerLevel))
return;

foreach (var source in visibleFearSources)
foreach (var source in fearSources.Value)
{
if (source.Comp.UponSeenState != ent.Comp.State)
continue;
Expand Down
9 changes: 7 additions & 2 deletions Content.Shared/_Scp/Fear/Systems/SharedFearSystem.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Content.Shared._Scp.Fear.Components;
using Content.Shared._Scp.Blinking;
using Content.Shared._Scp.Fear.Components;
using Content.Shared._Scp.Helpers;
using Content.Shared._Scp.Proximity;
using Content.Shared._Scp.Shaders;
Expand Down Expand Up @@ -27,6 +28,7 @@ namespace Content.Shared._Scp.Fear.Systems;
public abstract partial class SharedFearSystem : EntitySystem
{
[Dependency] private readonly SharedHighlightSystem _highlight = default!;
[Dependency] private readonly SharedBlinkingSystem _blinking = default!;
[Dependency] private readonly EyeWatchingSystem _watching = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedShaderStrengthSystem _shaderStrength = default!;
Expand Down Expand Up @@ -139,7 +141,10 @@ private void OnProximityInRange(Entity<FearComponent> ent, ref ProximityInRangeT
return;

// Проверка на зрение, чтобы можно было закрыть глазки и было не страшно
if (!_watching.SimpleIsWatchedBy(args.Receiver, [ent]))
if (_blinking.AreEyesClosedManually(ent.Owner))
return;

if (!_watching.IsWatchedBy(args.Receiver, ent, checkProximity: false))
return;

// Если текущий уровень страха выше, чем тот, что мы хотим поставить,
Expand Down
193 changes: 193 additions & 0 deletions Content.Shared/_Scp/Helpers/CollectionPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
using System.Runtime.CompilerServices;

namespace Content.Shared._Scp.Helpers;

/// <summary>
/// Provides a static object pool for collections to minimize garbage collection allocations.
/// </summary>
/// <typeparam name="TCollection">The type of the collection being pooled. Must implement <see cref="ICollection{T}"/>.</typeparam>
/// <typeparam name="T">The type of the elements contained in the collection.</typeparam>
public static class CollectionPool<TCollection, T>
where TCollection : class, ICollection<T>
{
private static readonly Stack<TCollection> Pool = new();
private static Func<TCollection>? _factory;

/// <summary>
/// Configures the factory function used to instantiate new collections when the pool is empty.
/// </summary>
/// <param name="factory">The delegate used to create new instances of <typeparamref name="TCollection"/>.</param>
/// <exception cref="InvalidOperationException">Thrown when the provided <paramref name="factory"/> is null.</exception>
public static void Configure(Func<TCollection> factory)
{
_factory = factory ?? throw new InvalidOperationException("Factory cannot be null");
}

/// <summary>
/// Creates a new collection using the configured factory.
/// </summary>
/// <returns>A new instance of <typeparamref name="TCollection"/>.</returns>
/// <exception cref="InvalidOperationException">Thrown if the pool has not been configured via <see cref="Configure"/>.</exception>
private static TCollection Create()
{
if (_factory is null)
{
throw new InvalidOperationException(
$"CollectionPool<{typeof(TCollection).Name}, {typeof(T).Name}> " +
$"is not configured. Call Configure(factory) before use.");
}

return _factory();
}

/// <summary>
/// Rents a collection from the pool. If the pool is empty, a new collection is created.
/// </summary>
/// <returns>A disposable <see cref="PooledCollection"/> wrapper. Use within a <see langword="using"/> statement to automatically return the collection to the pool.</returns>
public static PooledCollection Rent()
{
return Pool.TryPop(out var collection)
? new PooledCollection(collection)
: new PooledCollection(Create());
}

/// <summary>
/// Returns a collection to the pool. Clears the collection before storing it.
/// Collections will be dropped and garbage collected if the pool is full (>= 512 items) or if the collection capacity exceeds 1024.
/// </summary>
/// <param name="collection">The collection to return to the pool.</param>
internal static void Return(TCollection collection)
{
if (Pool.Count >= 512)
{
Logger.Warning("Pool bloated, new collections will not be stored");
return;
}

if (collection is List<T> list && list.Capacity > 2048)
return;

if (collection is HashSet<T> hashSet && hashSet.Capacity > 2048)
return;

collection.Clear();
Pool.Push(collection);
}

/// <summary>
/// An allocation-free disposable wrapper around a rented collection.
/// </summary>
public struct PooledCollection : IDisposable
{
private TCollection? _value;
private bool _disposed;

/// <summary>
/// Gets the underlying rented collection.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if accessed after the collection has been returned to the pool.</exception>
public TCollection Value
{
get
{
if (_disposed)
throw new InvalidOperationException("The collection has already been returned to the pool.");

return _value!;
}
}

internal PooledCollection(TCollection value)
{
_value = value;
_disposed = false;
}

/// <summary>
/// Returns the underlying collection to the pool and marks this wrapper as disposed.
/// </summary>
public void Dispose()
{
if (_disposed)
return;

_disposed = true;

if (_value is not null)
{
Return(_value);
_value = default;
}
}
}
}

/// <summary>
/// A pre-configured pool specifically for <see cref="List{T}"/> instances.
/// </summary>
/// <typeparam name="T">The type of elements in the list.</typeparam>
public static class ListPool<T>
{
static ListPool()
{
CollectionPool<List<T>, T>.Configure(() => new List<T>());
}

/// <summary>
/// Rents a list from the pool.
/// </summary>
/// <returns>A disposable wrapper containing the rented list.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static CollectionPool<List<T>, T>.PooledCollection Rent()
=> CollectionPool<List<T>, T>.Rent();
}

/// <summary>
/// A helper pool for renting lists of entities with specific components.
/// </summary>
/// <typeparam name="T">The component type associated with the entity.</typeparam>
public static class ListPoolEntity<T> where T : IComponent
{
/// <summary>
/// Rents a list of <see cref="Entity{T}"/> from the pool.
/// </summary>
/// <returns>A disposable wrapper containing the rented entity list.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static CollectionPool<List<Entity<T>>, Entity<T>>.PooledCollection Rent()
=> ListPool<Entity<T>>.Rent();
}

/// <summary>
/// A pre-configured pool specifically for <see cref="HashSet{T}"/> instances.
/// </summary>
/// <typeparam name="T">The type of elements in the hash set.</typeparam>
public static class HashSetPool<T>
{
static HashSetPool()
{
CollectionPool<HashSet<T>, T>.Configure(() => new HashSet<T>());
}

/// <summary>
/// Rents a hash set from the pool.
/// </summary>
/// <returns>A disposable wrapper containing the rented hash set.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static CollectionPool<HashSet<T>, T>.PooledCollection Rent()
=> CollectionPool<HashSet<T>, T>.Rent();
}

/// <summary>
/// A helper pool for renting hash sets of entities with specific components.
/// </summary>
/// <typeparam name="T">The component type associated with the entity.</typeparam>
public static class HashSetPoolEntity<T> where T : IComponent
{
/// <summary>
/// Rents a hash set of <see cref="Entity{T}"/> from the pool.
/// </summary>
/// <returns>A disposable wrapper containing the rented entity hash set.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static CollectionPool<HashSet<Entity<T>>, Entity<T>>.PooledCollection Rent()
=> HashSetPool<Entity<T>>.Rent();
}
Loading
Loading