Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// usings moved to file-level globals; removed unused using

namespace Content.Server.Imperial.SCP.SCP008.Components;

[RegisterComponent]
public sealed partial class SCP008InfectionAuraComponent : Component
{
[DataField("radius")]
public float Radius = 8f;

[DataField("zombifyDelay")]
public TimeSpan ZombifyDelay = TimeSpan.FromSeconds(12);

[DataField("updateInterval")]
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1);

[DataField("lookupFlags")]
public LookupFlags LookupFlags = LookupFlags.Dynamic;

[DataField("allowCritical")]
public bool AllowCritical = false;

[DataField("warningDelay")]
public TimeSpan WarningDelay = TimeSpan.FromSeconds(3);

[DataField("warningPopup")]
public LocId WarningPopup = "scp008-warning-popup";

[ViewVariables]
public TimeSpan NextUpdate;

[ViewVariables]
public TimeSpan LastUpdate;

[ViewVariables]
public Dictionary<EntityUid, TimeSpan> ExposureTime = new();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using Content.Server.Imperial.SCP.SCP008.Components;
using Content.Server.Zombies;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Popups;
using Content.Shared.Zombies;
using Robust.Shared.Timing;
using Robust.Shared.Localization;

namespace Content.Server.Imperial.SCP.SCP008.Systems;

public sealed class SCP008InfectionAuraSystem : EntitySystem
{
private readonly HashSet<EntityUid> _reusableInRange = new();
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly ZombieSystem _zombie = default!;
[Dependency] private readonly IGameTiming _timing = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<SCP008InfectionAuraComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SCP008InfectionAuraComponent, ComponentShutdown>(OnShutdown);
}

private void OnMapInit(Entity<SCP008InfectionAuraComponent> ent, ref MapInitEvent args)
{
ent.Comp.LastUpdate = _timing.CurTime;
ent.Comp.NextUpdate = _timing.CurTime + ent.Comp.UpdateInterval;
ent.Comp.ExposureTime.Clear();
}

private void OnShutdown(Entity<SCP008InfectionAuraComponent> ent, ref ComponentShutdown args)
{
ent.Comp.ExposureTime.Clear();
}

public override void Update(float frameTime)
{
base.Update(frameTime);

var curTime = _timing.CurTime;
var query = EntityQueryEnumerator<SCP008InfectionAuraComponent>();

while (query.MoveNext(out var uid, out var comp))
{
if (comp.NextUpdate > curTime)
continue;

var elapsed = curTime - comp.LastUpdate;
comp.LastUpdate = curTime;
comp.NextUpdate = curTime + comp.UpdateInterval;

if (elapsed <= TimeSpan.Zero)
continue;

_reusableInRange.Clear();
var nearbyEntities = _lookup.GetEntitiesInRange(uid, comp.Radius, comp.LookupFlags);

foreach (var target in nearbyEntities)
{
if (target == uid)
continue;

if (!TryComp<MobStateComponent>(target, out var mobState))
continue;

var validState = mobState.CurrentState == MobState.Alive ||
(comp.AllowCritical && mobState.CurrentState == MobState.Critical);

if (!validState)
continue;

if (HasComp<ZombieComponent>(target) || HasComp<ZombieImmuneComponent>(target))
continue;

_reusableInRange.Add(target);

var totalExposure = elapsed;
var previousExposure = TimeSpan.Zero;
if (comp.ExposureTime.TryGetValue(target, out var existingExposure))
{
previousExposure = existingExposure;
totalExposure += previousExposure;
}

var warningStart = comp.ZombifyDelay - comp.WarningDelay;
if (warningStart < TimeSpan.Zero)
warningStart = TimeSpan.Zero;

if (previousExposure < warningStart && totalExposure >= warningStart && totalExposure < comp.ZombifyDelay)
_popup.PopupEntity(Loc.GetString(comp.WarningPopup), target, target, PopupType.MediumCaution);

if (totalExposure >= comp.ZombifyDelay)
{
_zombie.ZombifyEntity(target, mobState);
comp.ExposureTime.Remove(target);
continue;
}

comp.ExposureTime[target] = totalExposure;
}

var toRemove = new List<EntityUid>();
foreach (var (tracked, _) in comp.ExposureTime)
{
if (!_reusableInRange.Contains(tracked))
toRemove.Add(tracked);
}

foreach (var tracked in toRemove)
{
comp.ExposureTime.Remove(tracked);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using Robust.Shared.Audio;

namespace Content.Server.Imperial.SCP.SCP096.Components;

[RegisterComponent]
public sealed partial class SCP096RageOnLookComponent : Component
{
[DataField("observeRadius")]
public float ObserveRadius = 9f;

[DataField("minLookDot")]
public float MinLookDot = 0.42f;

[DataField("requireUnobstructed")]
public bool RequireUnobstructed = true;

[DataField("enragedWalkModifier")]
public float EnragedWalkModifier = 1.7f;

[DataField("enragedSprintModifier")]
public float EnragedSprintModifier = 1.7f;

[DataField("enragedAttackRate")]
public float EnragedAttackRate = 1.7f;

[DataField("calmAttackRate")]
public float CalmAttackRate = 1.0f;

[DataField("rageWindup")]
public TimeSpan RageWindup = TimeSpan.FromSeconds(10);

[DataField("rageDuration")]
public TimeSpan RageDuration = TimeSpan.FromSeconds(120);

[DataField("rageWindupPopup")]
public LocId RageWindupPopup = "scp096-rage-windup-popup";

[DataField("ragePopup")]
public LocId RagePopup = "scp096-rage-popup";

[DataField("rageCalmPopup")]
public LocId RageCalmPopup = "scp096-rage-calm-popup";

[DataField("rageSound")]
public SoundSpecifier RageSound = new SoundPathSpecifier("/Audio/Voice/Human/malescream_1.ogg");

[DataField("cryingSound")]
public SoundSpecifier CryingSound = new SoundPathSpecifier("/Audio/Voice/Human/cry_male_1.ogg");

[DataField("rageLoopSound")]
public SoundSpecifier RageLoopSound = new SoundPathSpecifier("/Audio/Ambience/Objects/anomaly_generator_ambi.ogg");
Comment on lines +50 to +51
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

RageLoopSound по умолчанию использует звук генератора аномалий — вероятно, заглушка.

/Audio/Ambience/Objects/anomaly_generator_ambi.ogg — это эмбиент генератора аномалий, не связанный с SCP-096. Если это временный звук-заглушка, стоит заменить его на соответствующий аудиоресурс SCP-096 или оставить комментарий о намеренном использовании.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Content.Server/Imperial/SCP/SCP096/Components/SCP096RageOnLookComponent.cs`
around lines 51 - 52, The default RageLoopSound in SCP096RageOnLookComponent is
set to an unrelated anomaly generator ambient file; update the SoundSpecifier
for the RageLoopSound field (public SoundSpecifier RageLoopSound) to point to
the correct SCP-096 loop audio asset, or if this is intentionally a placeholder,
replace the hardcoded SoundPathSpecifier with a neutral null/default value and
add an inline comment on the RageLoopSound field indicating it is a temporary
stub awaiting the proper SCP-096 audio resource so the intent is clear to future
reviewers.


[ViewVariables]
public bool IsEnraged;

[ViewVariables]
public bool IsRageWindup;

[ViewVariables]
public TimeSpan RageWindupEndTime;

[ViewVariables]
public TimeSpan RageEndTime;

[ViewVariables]
public bool UsingRageLoopSound;

[ViewVariables]
public float? OriginalAttackRate;

[ViewVariables]
public HashSet<EntityUid> RageTargets = new();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Content.Server.Imperial.SCP.SCP096.Components;

[RegisterComponent]
public sealed partial class SCP096RageSuppressorComponent : Component
{
}
Loading
Loading