Skip to content
Open
Show file tree
Hide file tree
Changes from all 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,30 @@
using Robust.Shared.Map;
using Robust.Shared.Audio;
using Content.Shared.Chat.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Content.Shared.Damage;
using Robust.Shared.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.Mobs.Systems;
namespace Content.Server.Imperial.SCP.NothingThere.Components;

[RegisterComponent]
public sealed partial class ImperialControlledNothingThereComponent : Component
{
[DataField("gibBodyAction")]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Передача строки излишне

public EntProtoId GibBodyAction = "ImperialNothingThereGibBodyAction";
[ViewVariables]
public EntityUid? GibBodyEntity;
[ViewVariables]
public EntityUid OriginalBody = EntityUid.Invalid;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Добавьте либо атрибут ViewVariables, либо DataField

[ViewVariables]
public SoundSpecifier ExitSound = new SoundPathSpecifier("/Audio/Imperial/SCP/nothingthere_gibbody.ogg");
[ViewVariables]
public int KillCount = 0;
Comment on lines +19 to +23

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Не храните runtime-состояние как prototype-конфиг.

По текущему использованию OriginalBody и KillCount меняются только в рантайме. Лучше пометить их как runtime-поля для VV, иначе KillCount можно случайно зашить в YAML, а OriginalBody вообще не видно при отладке.

Based on learnings: In the Imperial SS14 project, fields of components that are not configured via YAML prototypes (runtime state) should be marked with the [ViewVariables] attribute, while fields configured via prototypes should use [DataField].

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

In
`@Content.Server/Imperial/SCP/NothingThere/Components/ImperialControlledNothingThereComponent.cs`
around lines 19 - 23, OriginalBody and KillCount are runtime-only state but are
currently marked with [DataField]; remove the [DataField] attributes and instead
annotate these fields with [ViewVariables] (e.g., [ViewVariables] or
[ViewVariables(VVAccess.ReadWrite)] as appropriate) in the
ImperialControlledNothingThereComponent so they are visible/editable in VV and
not treated as prototype-configured values; keep their runtime defaults
(OriginalBody = EntityUid.Invalid, KillCount = 0) and leave ExitSound as the
DataField-configured prototype value.

[ViewVariables]
public SoundSpecifier GibSound = new SoundCollectionSpecifier("gib", AudioParams.Default.WithVariation(0.025f));
[ViewVariables]
public float GibletLaunchImpulse = 150;
[ViewVariables]
public float GibletLaunchImpulseVariance = 3;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using Robust.Shared.Map;
using Robust.Shared.Audio;
using Content.Shared.Chat.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Content.Shared.Damage;
using Robust.Shared.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.Mobs.Systems;
using Content.Shared.Mobs;
Comment on lines +8 to +9

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Неиспользуемые директивы using.

Content.Shared.Mobs.Systems и Content.Shared.Mobs импортированы, но типы из них не используются в этом файле компонента. Рекомендуется удалить неиспользуемые импорты.

♻️ Предлагаемое исправление
-using Content.Shared.Mobs.Systems;
-using Content.Shared.Mobs;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
using Content.Shared.Mobs.Systems;
using Content.Shared.Mobs;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Content.Server/Imperial/SCP/NothingThere/Components/ImperialNothingThereComponent.cs`
around lines 8 - 9, Файл содержит неиспользуемые директивы using: remove the
unused imports Content.Shared.Mobs.Systems and Content.Shared.Mobs from the top
of the ImperialNothingThereComponent file; locate the
ImperialNothingThereComponent class in the
Content.Server.Imperial.SCP.NothingThere.Components namespace and delete the two
using lines (using Content.Shared.Mobs.Systems; and using Content.Shared.Mobs;)
so only required namespaces remain.

using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Polymorph;
namespace Content.Server.Imperial.SCP.NothingThere.Components;

[RegisterComponent]
public sealed partial class ImperialNothingThereComponent : Component
{
public SoundSpecifier ChaseSound = new SoundPathSpecifier("/Audio/Imperial/SCP/nothingthere_idle.ogg");

[ViewVariables]
public EntityUid? PlayingStream;

[ViewVariables]
public bool IsPlaying = false;
[DataField("enterBodyAction")]
public EntProtoId EnterBodyAction = "ImperialNothingThereEnterBodyAction";
public EntityUid? EnterBodyEntity;

[DataField]
public TimeSpan EnterBodyWindUp = TimeSpan.FromSeconds(2);
[DataField("threshold", required: true)]
public FixedPoint2 Threshold = new();
[DataField("killsRequired")]
public int KillsRequired = 5;

[DataField("killCount")]
public int KillCount = 0;

[DataField("phase")]
public NothingTherePhase Phase = NothingTherePhase.Original;
public EntProtoId? EggEntityProto = "ImperialSCPNothingThereEgg";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Отсутствуют атрибуты [DataField] для полей с прототипами.

Поля EggEntityProto, TrueEntityProto, GoodbyeProto и HitProto содержат идентификаторы прототипов, но не имеют атрибута [DataField]. Это означает, что их нельзя переопределить через YAML конфигурацию. Если это намеренно — добавьте [ViewVariables] для отладки, иначе добавьте [DataField].

Based on learnings: "fields configured via prototypes should use [DataField]".

♻️ Предлагаемое исправление
-public EntProtoId? EggEntityProto = "ImperialSCPNothingThereEgg";
+[DataField("eggEntityProto")]
+public EntProtoId? EggEntityProto = "ImperialSCPNothingThereEgg";
-public EntProtoId? TrueEntityProto = "ImperialSCPNothingThereTrue";
+[DataField("trueEntityProto")]
+public EntProtoId? TrueEntityProto = "ImperialSCPNothingThereTrue";
-public EntProtoId GoodbyeProto = "ImperialNothingThereEmpowerHit";
+[DataField("goodbyeProto")]
+public EntProtoId GoodbyeProto = "ImperialNothingThereEmpowerHit";
-public EntProtoId HitProto = "ImperialNothingThereHit";
+[DataField("hitProto")]
+public EntProtoId HitProto = "ImperialNothingThereHit";

Also applies to: 55-55, 74-75

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

In
`@Content.Server/Imperial/SCP/NothingThere/Components/ImperialNothingThereComponent.cs`
at line 43, Поля компонента ImperialNothingThereComponent — EggEntityProto,
TrueEntityProto, GoodbyeProto и HitProto — содержат идентификаторы прототипов,
но не помечены атрибутом [DataField], из‑за чего их нельзя переопределить через
YAML; откройте класс ImperialNothingThereComponent и добавить атрибут
[DataField] к каждому из этих полей (если их нужно конфигурировать из
прототипов), либо — если пропи́сно намеренно неизменяемо — пометить их
[ViewVariables] для отладки; убедитесь, что имена полей в [DataField("...")]
соответствуют ожидаемым YAML-ключам и что типы (EntProtoId?) остаются без
изменений.

[DataField("transformEggAction")]
public EntProtoId TransformEggAction = "ImperialNothingThereEggAction";
[DataField("transformEggEntity")]
public EntityUid? TransformEggEntity;
[DataField("enterSound")]
public SoundSpecifier EnterSound = new SoundPathSpecifier("/Audio/Imperial/SCP/nothingthere_enter.ogg");

[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan EggTransformDuration = TimeSpan.FromSeconds(30);
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan EggTransformEnd = TimeSpan.Zero;
[ViewVariables]
public EntProtoId? TrueEntityProto = "ImperialSCPNothingThereTrue";
[DataField("hatchSound")]
public SoundSpecifier HatchSound = new SoundPathSpecifier("/Audio/Imperial/SCP/nothingthere_hatch.ogg");
[ViewVariables]
public bool Empowered = false;

@coderabbitai coderabbitai Bot Apr 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Отсутствуют атрибуты [ViewVariables] для runtime-состояния.

Поля Empowered и NeedGoodbye являются runtime-состоянием (не конфигурируются через YAML), но не имеют атрибута [ViewVariables]. Согласно практикам проекта, runtime state должен быть помечен [ViewVariables] для удобства отладки.

Based on learnings: "fields of components that are not configured via YAML prototypes (runtime state) should be marked with the [ViewVariables] attribute".

♻️ Предлагаемое исправление
-public bool Empowered = false;
+[ViewVariables]
+public bool Empowered = false;
-public bool NeedGoodbye = false;
+[ViewVariables]
+public bool NeedGoodbye = false;

Also applies to: 78-78

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

In
`@Content.Server/Imperial/SCP/NothingThere/Components/ImperialNothingThereComponent.cs`
at line 59, Поля runtime-состояния в компоненте ImperialNothingThereComponent
(`Empowered` и `NeedGoodbye`) не помечены атрибутом [ViewVariables]; добавьте
атрибут [ViewVariables] над определениями этих полей в классе
ImperialNothingThereComponent, чтобы они были видимы в отладке/инспекторах
(оставьте типы и имена полей без изменений).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Добавьте либо атрибут ViewVariables, либо DataField

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "=== Snippet (1..200) with line numbers ==="
nl -ba Content.Server/Imperial/SCP/NothingThere/Components/ImperialNothingThereComponent.cs | sed -n '1,200p'

echo
echo "=== Grep for target fields (with context) ==="
rg -n -C2 $'\\b(Empowered|NeedGoodbye)\\b' -- Content.Server/Imperial/SCP/NothingThere/Components/ImperialNothingThereComponent.cs || true

Length of output: 84


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

file='Content.Server/Imperial/SCP/NothingThere/Components/ImperialNothingThereComponent.cs'

echo "=== File exists? ==="
if [[ -f "$file" ]]; then
  echo "yes: $file"
else
  echo "no: $file"
  exit 0
fi

echo
echo "=== Content with line numbers (1..240) ==="
# Use awk to add line numbers (nl may not be available)
awk '{printf "%6d  %s\n", NR, $0}' "$file" | sed -n '1,240p'

echo
echo "=== Locate Empowered / NeedGoodbye declarations (with 3 lines context) ==="
if command -v rg >/dev/null 2>&1; then
  rg -n -C3 $'\\b(Empowered|NeedGoodbye)\\b' -- "$file" || true
else
  grep -nE -C3 '\b(Empowered|NeedGoodbye)\b' "$file" || true
fi

echo
echo "=== Search prototypes for empowered/needgoodbye keys to detect YAML-configurability ==="
if command -v rg >/dev/null 2>&1; then
  rg -n -i --glob 'Resources/Prototypes/**' -- $'\\b(empowered|needgoodbye)\\b' || true
else
  grep -RInEi '\b(empowered|needgoodbye)\b' Resources/Prototypes || true
fi

Length of output: 5538


@The-Fiafi спасибо! Подтвердил по коду и прототипам:

  • Line 59: Empowered — без атрибута.
  • Line 78: NeedGoodbye — без атрибута.
  • В Resources/Prototypes/** нет ключей empowered/needgoodbye для этого компонента (нашлись только «empowered burger», не относящиеся к SCP).

С учётом ранее зафиксированного правила проекта (runtime поля помечаем [ViewVariables]), предлагаю добавить [ViewVariables] к обоим полям.

Минимальное исправление: отметить runtime‑поля [ViewVariables].

Если хотите сделать их настраиваемыми из YAML, скажите — заменю на [DataField("empowered")] и [DataField("needGoodbye")] (или добавлю их вдобавок к [ViewVariables], как уже сделано для EmpowerDamage).


🧠 Learnings used
Learnt from: The-Fiafi
Repo: imperial-space/SS14-public PR: 531
File: Content.Server/Imperial/SCP/ChaseMusic/Components/ImperialSCPChaseMusicComponent.cs:14-16
Timestamp: 2026-01-24T12:55:23.032Z
Learning: In the Imperial SS14 project, fields of components that are not configured via YAML prototypes (runtime state) should be marked with the [ViewVariables] attribute, while fields configured via prototypes should use [DataField]. This guideline applies to all C# component files under Content.Server; review each field to determine whether it’s prototype-configured and assign the appropriate attribute accordingly.

Learnt from: The-Fiafi
Repo: imperial-space/SS14-public PR: 545
File: Content.Server/Imperial/XxRaay/Systems/AnimatronicTargetSystem.cs:21-21
Timestamp: 2026-02-01T14:57:29.328Z
Learning: Enforce C# private field naming: all private fields must start with an underscore and use camelCase (e.g., _transformSystem). This rule applies across the repository (imperial-space/SS14-public) to maintain consistency in all C# files.

Learnt from: The-Fiafi
Repo: imperial-space/SS14-public PR: 565
File: Content.Server/Imperial/XenoGenetcs/ServerXenoGeneticsSystem.cs:16-16
Timestamp: 2026-02-19T18:00:42.014Z
Learning: In C# files across the repository, mark classes as sealed when there is no explicit need for inheritance. This project-wide coding standard (confirmed by The-Fiafi) helps prevent unintended inheritance and encourages explicit design. Exceptions should be made only where inheritance is actually required (e.g., for extensibility, mockable abstraction, or framework constraints). Ensure consistency by auditing classes in Content.Server/Imperial/XenoGenetcs/ServerXenoGeneticsSystem.cs and related files, applying sealed where inheritance is not needed.

Failed to handle agent chat message. Please try again.

[DataField("empowerAction")]
public EntProtoId EmpowerAction = "ImperialNothingThereHelloAction";
[DataField("empowerEntity")]
public EntityUid? EmpowerEntity;
[DataField("empowerSound")]
public SoundSpecifier EmpowerSound = new SoundPathSpecifier("/Audio/Imperial/SCP/nothingthere_empower.ogg");
[DataField(required: true), ViewVariables]
public DamageSpecifier EmpowerDamage = new();
[ViewVariables]
public ProtoId<PolymorphPrototype> EggMorph = "NothingThereEggMorph";
[ViewVariables]
public ProtoId<PolymorphPrototype> TrueMorph = "NothingThereTrueMorph";
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[ViewVariables]
public TimeSpan GoodbyeDelay = TimeSpan.FromSeconds(0.5f);
[ViewVariables]
public EntProtoId GoodbyeProto = "ImperialNothingThereEmpowerHit";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Помечайте поля, которые вы не используете как датафилды, атрибутом ViewVariables

[ViewVariables]
public EntProtoId HitProto = "ImperialNothingThereHit";
[DataField]
public bool NeedItems = false;
[ViewVariables]
public bool NeedGoodbye = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Добавьте либо атрибут ViewVariables, либо DataField

[DataField("projectileAction")]
public EntProtoId ProjectileAction = "ImperialNothingThereProjectileAction";
[DataField("projectileEntity")]
public EntityUid? ProjectileEntity;
}

public enum NothingTherePhase : byte
{
Original = 0,
Egg = 1,
True = 2
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
[RegisterComponent]
public sealed partial class ImperialNothingThereGoodbyeComponent : Component
{
[ViewVariables]
public bool Used = false;
[ViewVariables]
public EntProtoId HitProto = "ImperialNothingThereHit";
[DataField("empowerSound")]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Передача строки излишне

public SoundSpecifier EmpowerSound = new SoundPathSpecifier("/Audio/Imperial/SCP/nothingthere_empower.ogg");
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan AttackDuration = TimeSpan.FromSeconds(1.5);
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan AttackEnd = TimeSpan.Zero;
[ViewVariables]
public EntityUid WeaponUser = EntityUid.Invalid;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Content.Server.Imperial.SCP.NothingThere.Components;
using Robust.Shared.Audio;
using Content.Shared.Mobs;

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

public sealed partial class ImperialNothingThereSystem
{

private void InitializeChaseMusic()
{
SubscribeLocalEvent<ImperialNothingThereComponent, MapInitEvent>(OnInit);
SubscribeLocalEvent<ImperialNothingThereComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<ImperialNothingThereComponent, MobStateChangedEvent>(OnMobStateChanged);
}
private void OnRemove(EntityUid uid, ImperialNothingThereComponent comp, ComponentRemove args)
{
StopChaseMusic(uid, comp);
}

public void StartChaseMusic(EntityUid uid, ImperialNothingThereComponent comp)
{
if (comp.IsPlaying)
return;
if (comp.Phase == NothingTherePhase.True)
return;
comp.PlayingStream = _audio.PlayPvs(
comp.ChaseSound,
uid,
AudioParams.Default.WithLoop(true)
)?.Entity;

comp.IsPlaying = true;
Comment on lines +27 to +33

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

IsPlaying нужно выставлять только после успешного старта стрима.

Если _audio.PlayPvs(...) вернет null, компонент зафиксирует IsPlaying = true, хотя фактического потока нет. После этого повторный запуск музыки уже не произойдет.

💡 Возможный фикс
-        comp.PlayingStream = _audio.PlayPvs(
+        var stream = _audio.PlayPvs(
             comp.ChaseSound,
             uid,
             AudioParams.Default.WithLoop(true)
         )?.Entity;
-
-        comp.IsPlaying = true;
+        if (stream == null)
+            return;
+
+        comp.PlayingStream = stream;
+        comp.IsPlaying = true;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
comp.PlayingStream = _audio.PlayPvs(
comp.ChaseSound,
uid,
AudioParams.Default.WithLoop(true)
)?.Entity;
comp.IsPlaying = true;
var stream = _audio.PlayPvs(
comp.ChaseSound,
uid,
AudioParams.Default.WithLoop(true)
)?.Entity;
if (stream == null)
return;
comp.PlayingStream = stream;
comp.IsPlaying = true;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Content.Server/Imperial/SCP/NothingThere/Systems/ImperialNothingThere.ChaseMusic.cs`
around lines 27 - 33, The component currently sets comp.IsPlaying = true
unconditionally even if _audio.PlayPvs(...) returns null; change the logic so
you first call _audio.PlayPvs(comp.ChaseSound, uid,
AudioParams.Default.WithLoop(true)) and assign its ?.Entity to
comp.PlayingStream, then only set comp.IsPlaying = true if comp.PlayingStream is
not null (otherwise leave/set comp.IsPlaying = false) so failed PlayPvs attempts
don't mark the component as playing and prevent future retries.

}

public void StopChaseMusic(EntityUid uid, ImperialNothingThereComponent comp)
{
if (!comp.IsPlaying)
return;
if (comp.Phase == NothingTherePhase.True)
return;
if (comp.PlayingStream != null)
{
_audio.Stop(comp.PlayingStream.Value);
comp.PlayingStream = null;
}
comp.IsPlaying = false;
}
private void OnMobStateChanged(EntityUid uid, ImperialNothingThereComponent comp, MobStateChangedEvent args)
{
if (_mobStateSystem.IsDead(uid) || _mobStateSystem.IsCritical(uid))
{
StopChaseMusic(uid, comp);
}
else if (_mobStateSystem.IsAlive(uid))
{
StartChaseMusic(uid, comp);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@

using Content.Server.Imperial.SCP.NothingThere.Components;
using Content.Shared.Imperial.SCP.NothingThere.Events;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Hands.Components;
using Content.Shared.Popups;
using Content.Shared.DoAfter;
using Content.Shared.Mobs.Components;
using Robust.Shared.Timing;
namespace Content.Server.Imperial.SCP.NothingThere.Systems;

public sealed partial class ImperialNothingThereSystem
{
#region Initialize
private void InitializeArsenal()
{
SubscribeLocalEvent<ImperialNothingThereComponent, ImperialNothingThereGoodbyeEvent>(OnGoodbyeAction);
SubscribeLocalEvent<ImperialNothingThereComponent, ImperialNothingThereGoodbyeDoAfterEvent>(OnGoodbyeDoAfter);
SubscribeLocalEvent<ImperialNothingThereGoodbyeComponent, MeleeHitEvent>(OnGoodbyeAttack);
}
#endregion
#region Item provision
private void UpdateItemProvide()
{
var query = EntityQueryEnumerator<ImperialNothingThereComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (!comp.NeedItems)
continue;
var hands = EnsureComp<HandsComponent>(uid);
if (_hands.TryGetEmptyHand((uid, hands), out var emptyHand))
{
EntityUid? hit;
if (comp.NeedGoodbye)
{
hit = Spawn(comp.GoodbyeProto, Transform(uid).Coordinates);
comp.NeedGoodbye = false;
}
else
{
hit = Spawn(comp.HitProto, Transform(uid).Coordinates);
}
if (!_hands.TryForcePickup(uid, hit ?? EntityUid.Invalid, emptyHand, checkActionBlocker: false, handsComp: hands))
{
QueueDel(hit);
return;
}
else
comp.NeedItems = false;
}
}
var weaponquery = EntityQueryEnumerator<ImperialNothingThereGoodbyeComponent>();
while (weaponquery.MoveNext(out var wpnuid, out var wpncomp))
{
var curTime = _gameTiming.CurTime;
if (wpncomp.WeaponUser == EntityUid.Invalid || wpncomp.AttackEnd < curTime)
continue;
var user = wpncomp.WeaponUser;
if (!TryComp<HandsComponent>(user, out var hands))
continue;
wpncomp.AttackEnd = TimeSpan.Zero;
wpncomp.WeaponUser = EntityUid.Invalid;
var handd = _hands.GetActiveHand((user, hands));
_hands.DoDrop((user, hands), handd!, false, false);
if (Exists(wpnuid))
QueueDel(wpnuid);
if (TryComp<ImperialNothingThereComponent>(user, out var scp))
{
scp.NeedItems = true;
}
}
}
#endregion
#region Goodbye Handle
private void OnGoodbyeAction(Entity<ImperialNothingThereComponent> ent, ref ImperialNothingThereGoodbyeEvent args)
{
if (args.Handled)
return;

var doAfterArgs = new DoAfterArgs(EntityManager, ent, ent.Comp.GoodbyeDelay, new ImperialNothingThereGoodbyeDoAfterEvent(), ent)
{
BreakOnMove = true,
BreakOnDamage = false,
NeedHand = false
};
Comment on lines +80 to +85

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Привяжите do-after к исходному предмету и руке.

Сейчас NeedHand = false, а в OnGoodbyeDoAfter() удаляется просто текущий GetActiveItem(). Если за время windup игрок переключит руку или возьмет другой предмет, по завершении исчезнет уже он; при пустой активной руке handd! тоже небезопасен.

Also applies to: 82-89

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

In
`@Content.Server/Imperial/SCP/NothingThere/Systems/ImperialNothingThereSystem.Arsenal.cs`
around lines 60 - 65, Привяжите do-after к изначальному предмету и руке: при
создании DoAfterArgs (в месте с вызовом new DoAfterArgs(...)) установите
NeedHand = true и передайте туда текущий активный предмет/руку (например
сохраните var initItem = GetActiveItem() и передайте его в поле/параметр
DoAfterArgs, либо используйте доступное поле для Used/TargetEntity), затем в
обработчике OnGoodbyeDoAfter используйте переданный в args объект (а не повторно
вызывать GetActiveItem()) и добавьте безопасную проверку handd на null/valid
перед использова­нием/удалением предмета.

if (!TryComp<MobStateComponent>(args.Performer, out var mob))
return;
if (!TryComp<ImperialNothingThereComponent>(args.Performer, out var comp))
return;
if (_doAfterSystem.TryStartDoAfter(doAfterArgs))
{
args.Handled = true;
_popupSystem.PopupEntity(Loc.GetString("nothing-there-goodbye-windup"), ent, PopupType.MediumCaution);
}
args.Handled = true;
}
private void OnGoodbyeDoAfter(Entity<ImperialNothingThereComponent> ent, ref ImperialNothingThereGoodbyeDoAfterEvent args)
{
if (args.Cancelled || args.Handled)
return;

var hands = EnsureComp<HandsComponent>(args.User);
var handd = _hands.GetActiveHand((args.User, hands));
var wpn = _hands.GetActiveItem((args.User, hands));
var user = args.User;
_hands.DoDrop((args.User, hands), handd!, false, false);
QueueDel(wpn);
ent.Comp.NeedGoodbye = true;
ent.Comp.NeedItems = true;
}

private void OnGoodbyeAttack(EntityUid uid, ImperialNothingThereGoodbyeComponent comp, ref MeleeHitEvent args)
{
if (comp.Used || args.Handled)
return;
if (!TryComp<HandsComponent>(args.User, out var hands))
return;
args.Handled = true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Исправить форматирование

_audio.PlayPvs(comp.EmpowerSound, args.User);
comp.Used = true;
var user = args.User;
var handd = _hands.GetActiveHand((user, hands));
comp.WeaponUser = user;
var curTime = _gameTiming.CurTime;
comp.AttackEnd = curTime + comp.AttackDuration;
}
#endregion
}
Loading
Loading