Conversation
📝 WalkthroughWalkthroughДобавлена новая механика захвата SCP-объектов, позволяющая нескольким удерживающим (по одному на руку) брать целей за руки, накладывая замедление движения и блокируя действия. Система включает полный hold и мягкий hold с механиками вырывания, иммунитетом после вырывания, синхронизацией руки через виртуальные предметы, перенаправлением pull-интеракций и интеграцией с SCP-096. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Server
participant Holder as Holder Entity
participant Target as Target Entity
rect rgba(100, 200, 100, 0.5)
Note over Client,Target: Инициирование захвата (Hold Attempt)
Client->>Server: PullingSystem.TryStartPull(holder, target)
Server->>Server: TryRedirectPullToScpHold(holder, target)
Server->>Server: SharedScpHoldingSystem.CanToggleHold()
Server->>Server: SharedScpHoldingSystem.TryToggleHold(holder, target)
Server->>Holder: AddHolderContribution(holder, target)
Server->>Target: ScpHeldComponent создан/обновлен
Server->>Server: RefreshHeldState(target)
Server->>Client: Синхронизация компонентов
end
rect rgba(150, 100, 200, 0.5)
Note over Client,Target: Во время захвата (Holding)
Server->>Server: UpdateHeld() каждый тик
Server->>Target: Применить мягкое перетаскивание или zero velocity
Server->>Target: Блокировать движение/действия
Server->>Holder: Применить модификатор замедления
Client->>Client: ScpHoldingPredictionSystem обновляет прогноз
end
rect rgba(200, 100, 100, 0.5)
Note over Client,Target: Вырывание (Breakout)
Target->>Server: InputMoverSystem.OnMoveAttempt()
Server->>Server: TryBreakOut(target, viaMovement=true)
Server->>Server: Проверить SoftEscapeAvailableAt
alt Full Hold
Server->>Server: StartDoAfter(ScpHoldBreakoutDoAfterEvent)
else Soft Hold
Server->>Server: Выполнить вырывание сразу
end
Server->>Target: Применить иммунитет (ScpHoldImmuneComponent)
Server->>Holder: Применить урон/параліч (для SCP-096)
Server->>Server: ClearHoldState(target)
Server->>Client: Синхронизация завершения захвата
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Suggested labels
Poem
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (3 errors, 1 warning)
✅ Passed checks (6 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ast-grep (0.42.1)Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs[ ... [truncated 6455 characters] ... pped = new GotUnequippedHandEvent(uid, args.Entity, hand.Value);\n RaiseLocalEvent(args.Entity, gotUnequipped);\n\n var didUnequip = new DidUnequipHandEvent(uid, args.Entity, hand.Value);\n RaiseLocalEvent(uid, didUnequip);\n\n if (TryComp(args.Entity, out VirtualItemComponent? Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs[ ... [truncated 30013 characters] ... oveEvent args)\n {\n // Fire edit start - do not let a blocker cancel its own shutdown refresh\n if (component.LifeStage > ComponentLifeStage.Running)\n return;\n\n // If we're relaying then don't cancel.\n if (HasComp(uid))\n return;\n\n args.Cancel(); // no more scurrying around\n // Fire edit end\n }", Content.IntegrationTests/Tests/_Scp/ScpHoldingTest.cs[ ... [truncated 1998 characters] ... HoldBreakoutEvent>;\n\npublic sealed class ScpHoldAttemptCancelSystem : EntitySystem\n{\n public override void Initialize()\n {\n SubscribeLocalEvent<ScpHoldAttemptCancelTestComponent, ScpHoldAttemptEvent>(OnAttempt);\n }\n\n private static void OnAttempt(Entity ent, ref ScpHoldAttemptEvent args)\n {\n args.Cancel();\n }\n}",
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 21
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@Content.Client/_Scp/Holding/ScpHoldingPredictionSystem.cs`:
- Around line 22-26: Replace the fixed 500ms timeout suppression with
state-based suppression: remove BlockerRespawnSuppressionDuration and the use of
_suppressedUntil and any time comparisons, and instead keep suppression until
the authoritative ScpHolderComponent for the suppressed holder either changes
its Target or the component is removed; implement this by storing
_suppressedHolder and _suppressedTarget when you start suppression and, where
the code currently checks _suppressedUntil, instead query the current
ScpHolderComponent for that holder (or its existence) and clear suppression only
when ScpHolderComponent.Target is different from _suppressedTarget or the
component no longer exists. Ensure all locations that reference
BlockerRespawnSuppressionDuration or _suppressedUntil (including the code paths
around the logic that spawns/filters the virtual blocker) are updated to use the
new state-based check.
In `@Content.Shared/_Scp/Holding/ScpHeldHandBlockerComponent.cs`:
- Around line 8-20: Компонент ScpHeldHandBlockerComponent помечен как
[NetworkedComponent] но не генерирует состояние и не маркирует поля для сетевой
синхронизации, из‑за чего предсказание/откат в ScpHoldingPredictionSystem
некорректны; добавьте атрибут [AutoGenerateComponentState] к классу
ScpHeldHandBlockerComponent и пометьте поля Target и Holder атрибутом
[AutoNetworkedField], чтобы они синхронизировались и откатывались так же, как в
аналогичных компонентах ScpHolderComponent, ScpHeldComponent и ScpHoldComponent.
In `@Content.Shared/_Scp/Holding/ScpHoldableComponent.cs`:
- Around line 9-10: The ScpHoldableComponent in Shared lacks the
NetworkedComponent attribute needed for client-side replication; add the
[NetworkedComponent] attribute to the ScpHoldableComponent declaration (the
public sealed partial class ScpHoldableComponent : Component) in Content.Shared
so the component is networked and eligible checks replicate correctly, and
ensure any required using/import for the networking attribute's namespace is
added if missing.
In `@Content.Shared/_Scp/Holding/ScpHoldRestrictedComponent.cs`:
- Around line 5-9: Добавьте атрибуты автогенерации и автосинхронизации для
сетевого компонента: пометьте класс ScpHoldRestrictedComponent атрибутом
[AutoGenerateComponentState] и пометьте поле Stage атрибутом
[AutoNetworkedField]; это гарантирует, что поле Stage будет участвовать в
сериализации/десериализации и откате состояния при предсказании для
NetworkedComponent.
In `@Content.Shared/_Scp/Holding/SharedScpHoldingSystem.Actions.cs`:
- Around line 22-40: CanToggleHold currently only validates starting a hold and
thus disagrees with TryToggleHold's actual toggle logic; update CanToggleHold to
mirror TryToggleHold's preconditions by returning true for the release case when
the holder's active target equals the provided target, false when the holder is
already holding a different target (so UI/prediction matches the PopupHolder
rejection), and otherwise perform the start-hold checks (or delegate to
CanStartHold) when the holder has no active target; ensure the method signature
and behavior follow the OnEvent -> TryDo -> CanDo -> Do pattern and reference
CanToggleHold and TryToggleHold so any callers/predictions stay consistent with
the public API.
- Around line 245-247: Вызов PopupTarget(held.Owner, "scp-hold-breakout-start")
выполняется без prediction-gating; либо оберните этот вызов в проверку
IsFirstTimePredicted (как это сделано в ShowBreakoutAttemptFeedback), либо
замените вызов на prediction-safe хелпер (например
PopupPredicted/PopupEntity-перенаправление), чтобы обеспечить one-shot UX effect
только при первой предсказанной итерации; отредактируйте функцию PopupTarget или
место вызова, чтобы использовать IsFirstTimePredicted или вызвать
соответствующий prediction-safe wrapper (см. ShowBreakoutAttemptFeedback,
PopupTarget, PopupPredicted, IsFirstTimePredicted).
In `@Content.Shared/_Scp/Holding/SharedScpHoldingSystem.Drag.cs`:
- Around line 104-112: GetSoftDragDirection currently falls back to
Transform(holderUid).LocalRotation.ToWorldVec(), which returns a parent-relative
direction and can mismatch the map-space vectors (offset, holderVelocity);
change the fallback to use the entity's world rotation instead by replacing the
LocalRotation usage with WorldRotation so the returned direction is in
world/map-space and consistent with the normalized offset and velocity checks
(symbols: GetSoftDragDirection, SoftDragSnapTolerance,
SoftDragVelocityDirectionThreshold,
Transform(holderUid).WorldRotation.ToWorldVec()).
In `@Content.Shared/_Scp/Holding/SharedScpHoldingSystem.Events.cs`:
- Around line 221-233: OnHolderBlockerDropped currently releases the holder
contribution but lets the normal drop flow continue, which can race with the
blocker being removed; after calling ReleaseHolderContribution(args.User,
ent.Comp.Target, clearIfEmpty: true) immediately stop the default drop handling
on the GettingDroppedAttemptEvent (e.g. call args.Cancel() or set args.Cancel =
true / args.Handled = true depending on the event API) so the virtual item's
drop logic does not run against the already-processed blocker; keep this change
inside OnHolderBlockerDropped right after the ReleaseHolderContribution call.
- Around line 135-155: The handlers OnHeldAttemptMobCollide and
OnHeldAttemptMobTargetCollide are currently cancelling all mob-collisions for
any ScpHeldComponent; change them to only cancel when the held is in a FullHold
state OR when the other entity is the actual holder for this held pair.
Concretely: in both OnHeldAttemptMobCollide and OnHeldAttemptMobTargetCollide,
early-return unless ent.Component.FullHold is true OR
_holderQuery.TryComp(args.OtherEntity, out var holder) && holder.Target ==
ent.Owner; keep the finer-grained logic in OnHeldPreventCollide untouched so
pair-wise holder↔held collisions still work.
In `@Content.Shared/_Scp/Holding/SharedScpHoldingSystem.Hands.cs`:
- Around line 27-69: SyncPlaceholderHands currently calls DeleteHeldHandBlockers
unconditionally and then recreates all virtual blockers; change it to reuse
existing ScpHeldHandBlockerComponent entities like SyncHolderHandBlocker does
by: first collect existing blocker entities for held.Owner (inspect components
via ScpHeldHandBlockerComponent and _placeholderIcons), compute which blockers
already match a holder in held.Comp.Holders (matching blocker.Holder ==
holderUid and blocker.Target == held.Owner) and keep them, delete only obsolete
blockers and their virtual items, then for any missing holders spawn virtual
items with _virtualItem.TrySpawnVirtualItemInHand,
EnsureComp<UnremoveableComponent> and set
ScpHeldHandBlockerComponent.Target/Holder as now done; do not unconditionally
call DeleteHeldHandBlockers and avoid dropping/respawning valid held items via
_hands.TryGetHeldItem/_hands.DoDrop unless necessary.
In `@Content.Shared/_Scp/Holding/SharedScpHoldingSystem.Restrictions.cs`:
- Around line 10-16: В методе OnHoldRestrictedActionAttempt (обработчик
ActionAttemptEvent) замените вызов _popup.PopupClient(...) на
_popup.PopupPredicted(...), чтобы использовать предсказанный вариант попапа для
предсказанных событий и избежать двойного отображения; оставьте те же аргументы
(сообщение Loc.GetString("scp-hold-action-restricted"), args.User, args.User)
при замене, как в аналогичных системах (SharedArtifactCrusherSystem,
ScpMaskSystem).
In `@Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs`:
- Around line 44-47: В коде комментарии-маркеры "Fire edit start" и "Fire edit
end" (см. строку с "// Fire edit start - do not let a blocker cancel its own
shutdown refresh" и соответствующий конец блока) используют неправильный формат;
заменить эти форк-маркеры на Sunrise-формат: используйте "Sunrise-Edit" for
single-line or "Sunrise edit start" / "Sunrise edit end" for multi-line edit
blocks so они соответствуют стандарту репозитория (обновить оба места, где
встречается "Fire edit start" и "Fire edit end").
In `@Content.Shared/Movement/Pulling/Systems/PullingSystem.cs`:
- Around line 532-535: TryRedirectPullToScpHold currently runs before CanPull
and bypasses key checks and events (NeedsHands, CanInteract/distance, bound
status) and does not raise StartPullAttemptEvent / BeingPulledAttemptEvent /
PullAttemptEvent, so subscriber systems (CuffableSystem, BuckleSystem,
VentCrawSystem, CarryingSystem, AdminFrozenSystem, etc.) can't veto the action;
fix by unifying the guard logic: either have TryRedirectPullToScpHold call the
same validation and raise the same events as CanPull (including NeedsHands,
CanInteract checks and binding checks) before returning a redirect, or extract
the shared pre-pull guard into a helper (e.g., ValidateAndRaisePullAttemptEvents
or similar) and call it from both TryRedirectPullToScpHold and the original pull
path so both paths perform identical checks and raise StartPullAttemptEvent /
BeingPulledAttemptEvent / PullAttemptEvent.
In `@Resources/Locale/en-US/_strings/_scp/holding/holding.ftl`:
- Around line 13-14: Update the locale entry alerts-scp-held-desc so the player
instruction is marked as out-of-character: prefix the sentence "Move or click
this alert to try to break free." with "OOC:" (e.g., "OOC: Move or click this
alert to try to break free.") while keeping the rest of the description intact;
edit the alerts-scp-held-desc string accordingly to ensure the control hint
complies with OOC tagging guidelines.
In `@Resources/Prototypes/_Scp/Roles/Jobs/Administration/security_commander.yml`:
- Around line 36-39: В текущей конфигурации у Security Commander блок ScpHold
использует holdableWhitelist, который содержит только ClassDAppearance и потому
не покрывает весь персонал; исправьте это, расширив или заменив
holdableWhitelist в записи для Security Commander так, чтобы включить все типы
персонала (например добавить/заменить на сущности вроде ResearchAppearance,
StaffAppearance, GuardAppearance, SecurityAppearance или общий маркер
PersonnelAppearance/персонал вместо ClassDAppearance), либо удалить/исправить
whitelist на более общий критерий, чтобы соответствовать заявленному охвату
механика; изменяйте именно секцию ScpHold и поле holdableWhitelist в записи
Security Commander.
In
`@Resources/Prototypes/_Scp/Roles/Jobs/LowAccessPersonnel/class_d_botanist.yml`:
- Around line 26-34: В файле конфигурации ClassDBotanist удалите назначение
механики удержания: уберите или переместите блок type: ScpHold (и связанные поля
holdableWhitelist / holdableBlacklist) из class_d_botanist.yml, т.е. удалите
упоминание ScpHold у ClassDBotanist и перенесите эту фичу в правильный
scope/роль, где удержание должно быть доступно; убедитесь, что поля
holdableWhitelist и holdableBlacklist больше не присутствуют в описании
ClassDBotanist (или добавьте вместо них ссылку на корректный класс/роль,
отвечающую за ScpHold).
In
`@Resources/Prototypes/_Scp/Roles/Jobs/SpecialPurposeSquad/heavy_containment_zone_officer.yml`:
- Around line 31-40: Конфигурация ScpHold с одинаковыми секциями
holdableWhitelist и holdableBlacklist (components: Scp096, ClassDAppearance и
blacklist: ActiveScp096Rage, ActiveScp096HeatingUp, ActiveScp096WithoutFace)
повторяется в нескольких job-файлах; вынесите её в общий шаблон или базовый
job-прототип (например: базовый прототип/компонент с именем ScpHoldBase) и
замените дублирующие блоки в файлах на ссылку/наследование этого шаблона,
сохранив возможность переопределения по нужде.
In
`@Resources/Prototypes/_Scp/Roles/Jobs/SpecialPurposeSquad/junior_heavy_containment_zone_officer.yml`:
- Around line 30-39: В блоке конфигурации ScpHold удалите запись
ClassDAppearance из holdableWhitelist — оставьте только Scp096 в whitelist и
сохраните текущий holdableBlacklist (ActiveScp096Rage, ActiveScp096HeatingUp,
ActiveScp096WithoutFace) чтобы фокусировать механику только на SCP-096;
проверьте секцию ScpHold и поля holdableWhitelist/holdableBlacklist и уберите
только ClassDAppearance, не трогая остальные ключи.
In `@Resources/Prototypes/Actions/types.yml`:
- Around line 264-265: The comment on the upstream entry for the action type
ScpHoldRestricted uses the wrong upstream marker; replace the inline comment "#
Fire edit" with the required repository upstream marker "# Sunrise-Edit" (exact
casing) so the ScpHoldRestricted line uses the Sunrise upstream edit marker as
per guidelines.
In `@Resources/Prototypes/Entities/Mobs/Species/base.yml`:
- Around line 281-282: The upstream YAML changes use a Fire-format inline
comment instead of the required Sunrise markers; replace the Fire comment on the
modified entries (types ScpHold and ScpHoldable) with the Sunrise upstream edit
markers—wrap the edited lines with the Sunrise edit markers (e.g., use the
"Sunrise-Edit" or "Sunrise edit start" / "Sunrise edit end" pattern) so the
change follows the repository's Sunrise upstream-edit convention for the Species
base.yml entries where ScpHold and ScpHoldable are altered.
- Around line 281-282: В BaseMobSpeciesOrganic удалите запись типа ScpHold
(оставьте ScpHoldable если нужно), чтобы базовый органический прототип больше не
получал механику удержания; вместо этого назначьте компонент ScpHold только
нужным ролям через job "special" (проверьте где определяется job "special" и
добавьте/переместите тип ScpHold туда).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4181336a-3b73-42ff-802a-15d03d2894e6
📒 Files selected for processing (51)
Content.Client/_Scp/Holding/ScpHoldingPredictionSystem.csContent.IntegrationTests/Tests/_Scp/ScpHoldingTest.csContent.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.csContent.Shared/Interaction/SharedInteractionSystem.Blocking.csContent.Shared/Movement/Pulling/Systems/PullingSystem.csContent.Shared/_Scp/Holding/PullingSystem.ScpHolding.csContent.Shared/_Scp/Holding/ScpHeldComponent.csContent.Shared/_Scp/Holding/ScpHeldHandBlockerComponent.csContent.Shared/_Scp/Holding/ScpHoldComponent.csContent.Shared/_Scp/Holding/ScpHoldHandBlockerComponent.csContent.Shared/_Scp/Holding/ScpHoldImmuneComponent.csContent.Shared/_Scp/Holding/ScpHoldRestrictedComponent.csContent.Shared/_Scp/Holding/ScpHoldStage.csContent.Shared/_Scp/Holding/ScpHoldableComponent.csContent.Shared/_Scp/Holding/ScpHolderComponent.csContent.Shared/_Scp/Holding/ScpHoldingEvents.csContent.Shared/_Scp/Holding/SharedScpHoldingSystem.Actions.csContent.Shared/_Scp/Holding/SharedScpHoldingSystem.Drag.csContent.Shared/_Scp/Holding/SharedScpHoldingSystem.Events.csContent.Shared/_Scp/Holding/SharedScpHoldingSystem.Feedback.csContent.Shared/_Scp/Holding/SharedScpHoldingSystem.Hands.csContent.Shared/_Scp/Holding/SharedScpHoldingSystem.Restrictions.csContent.Shared/_Scp/Holding/SharedScpHoldingSystem.State.csContent.Shared/_Scp/Holding/SharedScpHoldingSystem.csContent.Shared/_Scp/Scp096/Main/Components/Scp096Component.csContent.Shared/_Scp/Scp096/Main/Systems/SharedScp096System.Holding.csContent.Shared/_Scp/Scp096/Main/Systems/SharedScp096System.Rage.csContent.Shared/_Scp/Scp096/Main/Systems/SharedScp096System.WithoutFace.csContent.Shared/_Scp/Scp096/Main/Systems/SharedScp096System.csResources/Locale/en-US/_strings/_scp/holding/holding.ftlResources/Locale/ru-RU/_strings/_scp/holding/holding.ftlResources/Prototypes/Actions/types.ymlResources/Prototypes/Entities/Mobs/Species/base.ymlResources/Prototypes/_Scp/Actions/scp096.ymlResources/Prototypes/_Scp/Alerts/holding.ymlResources/Prototypes/_Scp/Entities/Mobs/Player/Scp/Main/scp096.ymlResources/Prototypes/_Scp/Entities/StatusEffects/holding.ymlResources/Prototypes/_Scp/Roles/Jobs/Administration/security_commander.ymlResources/Prototypes/_Scp/Roles/Jobs/CommandantSquad/external_administrative_zone_commandant.ymlResources/Prototypes/_Scp/Roles/Jobs/CommandantSquad/external_administrative_zone_officer.ymlResources/Prototypes/_Scp/Roles/Jobs/CommandantSquad/field_medical_specialist.ymlResources/Prototypes/_Scp/Roles/Jobs/CommandantSquad/junior_external_administrative_zone_officer.ymlResources/Prototypes/_Scp/Roles/Jobs/CommandantSquad/senior_external_administrative_zone_officer.ymlResources/Prototypes/_Scp/Roles/Jobs/LowAccessPersonnel/class_d.ymlResources/Prototypes/_Scp/Roles/Jobs/LowAccessPersonnel/class_d_botanist.ymlResources/Prototypes/_Scp/Roles/Jobs/LowAccessPersonnel/class_d_cook.ymlResources/Prototypes/_Scp/Roles/Jobs/LowAccessPersonnel/class_d_janitor.ymlResources/Prototypes/_Scp/Roles/Jobs/SpecialPurposeSquad/heavy_containment_zone_commandant.ymlResources/Prototypes/_Scp/Roles/Jobs/SpecialPurposeSquad/heavy_containment_zone_officer.ymlResources/Prototypes/_Scp/Roles/Jobs/SpecialPurposeSquad/junior_heavy_containment_zone_officer.ymlResources/Prototypes/_Scp/Roles/Jobs/SpecialPurposeSquad/senior_heavy_containment_zone_officer.yml
| private static readonly TimeSpan BlockerRespawnSuppressionDuration = TimeSpan.FromSeconds(0.5); | ||
|
|
||
| private EntityUid? _suppressedHolder; | ||
| private EntityUid? _suppressedTarget; | ||
| private TimeSpan _suppressedUntil; |
There was a problem hiding this comment.
Не привязывайте suppression blocker'а к фиксированным 500 мс.
При RTT выше 500 мс _suppressedUntil истечёт раньше, чем приедет authoritative состояние holder'а, и виртуальный blocker снова начнёт респавниться/мигать на клиенте. Здесь безопаснее держать suppression до смены ScpHolderComponent.Target или удаления ScpHolderComponent, а не по wall-clock таймауту.
Also applies to: 48-50, 141-160
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Content.Client/_Scp/Holding/ScpHoldingPredictionSystem.cs` around lines 22 -
26, Replace the fixed 500ms timeout suppression with state-based suppression:
remove BlockerRespawnSuppressionDuration and the use of _suppressedUntil and any
time comparisons, and instead keep suppression until the authoritative
ScpHolderComponent for the suppressed holder either changes its Target or the
component is removed; implement this by storing _suppressedHolder and
_suppressedTarget when you start suppression and, where the code currently
checks _suppressedUntil, instead query the current ScpHolderComponent for that
holder (or its existence) and clear suppression only when
ScpHolderComponent.Target is different from _suppressedTarget or the component
no longer exists. Ensure all locations that reference
BlockerRespawnSuppressionDuration or _suppressedUntil (including the code paths
around the logic that spawns/filters the virtual blocker) are updated to use the
new state-based check.
| [RegisterComponent, NetworkedComponent] | ||
| [Access(typeof(SharedScpHoldingSystem))] | ||
| public sealed partial class ScpHeldHandBlockerComponent : Component | ||
| { | ||
| /// <summary> | ||
| /// Held target whose hand is occupied by this placeholder. | ||
| /// </summary> | ||
| public EntityUid Target; | ||
|
|
||
| /// <summary> | ||
| /// Holder whose sprite is shown in this placeholder. | ||
| /// </summary> | ||
| public EntityUid Holder; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Проверяем использование компонента в предсказуемом коде
rg -n "ScpHeldHandBlockerComponent" --type=cs -C3Repository: space-sunrise/project-fire
Length of output: 3881
🏁 Script executed:
#!/bin/bash
# Поиск ScpHoldingPredictionSystem или prediction-related code
rg -n "ScpHoldingPrediction\|PredictionSystem.*Holding" --type=cs -A2Repository: space-sunrise/project-fire
Length of output: 52
🏁 Script executed:
#!/bin/bash
# Поиск всех упоминаний слова "prediction" в контексте SCP Holding
rg -n -i "prediction.*holding\|holding.*prediction" --type=cs -A2Repository: space-sunrise/project-fire
Length of output: 52
🏁 Script executed:
#!/bin/bash
# Проверяем, является ли SharedScpHoldingSystem системой предсказания
rg -n "class SharedScpHoldingSystem" --type=cs -A5Repository: space-sunrise/project-fire
Length of output: 4972
🏁 Script executed:
#!/bin/bash
# Проверяем, используется ли компонент в любом контексте, который может быть связан с предсказанием
rg -n "ScpHeldHandBlockerComponent" --type=cs | head -20Repository: space-sunrise/project-fire
Length of output: 780
🏁 Script executed:
#!/bin/bash
# Ищем все файлы, содержащие "Prediction" и "Scp"
fd -e cs | xargs grep -l "Prediction" | grep -i scpRepository: space-sunrise/project-fire
Length of output: 233
🏁 Script executed:
#!/bin/bash
# Проверяем содержимое ScpHoldingPredictionSystem
cat Content.Client/_Scp/Holding/ScpHoldingPredictionSystem.csRepository: space-sunrise/project-fire
Length of output: 5950
🏁 Script executed:
#!/bin/bash
# Проверяем, содержит ли ScpHoldingPredictionSystem упоминания ScpHeldHandBlockerComponent
rg -n "ScpHeldHandBlockerComponent" Content.Client/_Scp/Holding/ScpHoldingPredictionSystem.csRepository: space-sunrise/project-fire
Length of output: 52
🏁 Script executed:
#!/bin/bash
# Проверяем, как другие компоненты в файле определены с точки зрения state attributes
rg -n "@(RegisterComponent|NetworkedComponent|AutoGenerateComponentState)" Content.Shared/_Scp/Holding/ --type=cs -B2 | head -50Repository: space-sunrise/project-fire
Length of output: 52
🏁 Script executed:
#!/bin/bash
# Ищем все компоненты в Shared/_Scp/Holding/
rg -n "public sealed partial class.*Component.*:" Content.Shared/_Scp/Holding/ --type=cs -B3 | head -80Repository: space-sunrise/project-fire
Length of output: 3430
🏁 Script executed:
#!/bin/bash
# Читаем точный файл из review
cat -n Content.Shared/_Scp/Holding/ScpHeldHandBlockerComponent.csRepository: space-sunrise/project-fire
Length of output: 799
🏁 Script executed:
#!/bin/bash
# Проверяем, есть ли поля с [AutoNetworkedField] в других компонентах
rg -n "\[AutoNetworkedField\]" Content.Shared/_Scp/Holding/ --type=cs -B1 -A1 | head -40Repository: space-sunrise/project-fire
Length of output: 2529
🏁 Script executed:
#!/bin/bash
# Двойная проверка - показываем всё содержимое файла со всеми строками
wc -l Content.Shared/_Scp/Holding/ScpHeldHandBlockerComponent.cs && echo "---" && head -25 Content.Shared/_Scp/Holding/ScpHeldHandBlockerComponent.csRepository: space-sunrise/project-fire
Length of output: 718
Добавьте атрибуты сетевой синхронизации и предсказания к компоненту.
Компонент помечен [NetworkedComponent], но не имеет [AutoGenerateComponentState]. Поля Target и Holder не помечены [AutoNetworkedField]. Компонент используется в ScpHoldingPredictionSystem (client-side prediction) и должен правильно синхронизировать и откатывать состояние при рассогласовании. Все аналогичные компоненты в той же системе (ScpHolderComponent, ScpHeldComponent, ScpHoldComponent) имеют эти атрибуты.
🔧 Предлагаемое исправление
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
[Access(typeof(SharedScpHoldingSystem))]
public sealed partial class ScpHeldHandBlockerComponent : Component
{
/// <summary>
/// Held target whose hand is occupied by this placeholder.
/// </summary>
+ [AutoNetworkedField]
public EntityUid Target;
/// <summary>
/// Holder whose sprite is shown in this placeholder.
/// </summary>
+ [AutoNetworkedField]
public EntityUid Holder;
}📝 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.
| [RegisterComponent, NetworkedComponent] | |
| [Access(typeof(SharedScpHoldingSystem))] | |
| public sealed partial class ScpHeldHandBlockerComponent : Component | |
| { | |
| /// <summary> | |
| /// Held target whose hand is occupied by this placeholder. | |
| /// </summary> | |
| public EntityUid Target; | |
| /// <summary> | |
| /// Holder whose sprite is shown in this placeholder. | |
| /// </summary> | |
| public EntityUid Holder; | |
| [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] | |
| [Access(typeof(SharedScpHoldingSystem))] | |
| public sealed partial class ScpHeldHandBlockerComponent : Component | |
| { | |
| /// <summary> | |
| /// Held target whose hand is occupied by this placeholder. | |
| /// </summary> | |
| [AutoNetworkedField] | |
| public EntityUid Target; | |
| /// <summary> | |
| /// Holder whose sprite is shown in this placeholder. | |
| /// </summary> | |
| [AutoNetworkedField] | |
| public EntityUid Holder; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Content.Shared/_Scp/Holding/ScpHeldHandBlockerComponent.cs` around lines 8 -
20, Компонент ScpHeldHandBlockerComponent помечен как [NetworkedComponent] но не
генерирует состояние и не маркирует поля для сетевой синхронизации, из‑за чего
предсказание/откат в ScpHoldingPredictionSystem некорректны; добавьте атрибут
[AutoGenerateComponentState] к классу ScpHeldHandBlockerComponent и пометьте
поля Target и Holder атрибутом [AutoNetworkedField], чтобы они
синхронизировались и откатывались так же, как в аналогичных компонентах
ScpHolderComponent, ScpHeldComponent и ScpHoldComponent.
| [RegisterComponent] | ||
| public sealed partial class ScpHoldableComponent : Component |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Рекомендуется добавить атрибут [NetworkedComponent] для компонента в Shared.
Компонент находится в Content.Shared и используется в предиктивном коде для проверки eligibility удержания. Для корректной репликации на клиент рекомендуется добавить атрибут [NetworkedComponent].
♻️ Предлагаемое исправление
+using Robust.Shared.GameObjects;
+
namespace Content.Shared._Scp.Holding;
/// <summary>
/// Marks an entity as a valid target for the SCP holding mechanic and stores per-target hold tuning.
/// </summary>
[RegisterComponent]
+[NetworkedComponent]
public sealed partial class ScpHoldableComponent : Component🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Content.Shared/_Scp/Holding/ScpHoldableComponent.cs` around lines 9 - 10, The
ScpHoldableComponent in Shared lacks the NetworkedComponent attribute needed for
client-side replication; add the [NetworkedComponent] attribute to the
ScpHoldableComponent declaration (the public sealed partial class
ScpHoldableComponent : Component) in Content.Shared so the component is
networked and eligible checks replicate correctly, and ensure any required
using/import for the networking attribute's namespace is added if missing.
| [RegisterComponent, NetworkedComponent] | ||
| public sealed partial class ScpHoldRestrictedComponent : Component | ||
| { | ||
| [DataField] | ||
| public ScpHoldStage Stage = ScpHoldStage.Full; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Проверяем, изменяется ли Stage во время выполнения
rg -n "\.Stage\s*=" --type=cs -g '*Holding*' -C 2Repository: space-sunrise/project-fire
Length of output: 52
🏁 Script executed:
# Search for Stage modifications across the codebase, not just Holding files
rg -n "\.Stage\s*=" --type=cs -A 2 -B 2 | grep -i "scphold\|stage"Repository: space-sunrise/project-fire
Length of output: 1176
🏁 Script executed:
# Search for methods that might set Stage
rg -n "Stage\s*=" --type=cs -A 1 -B 1 | head -50Repository: space-sunrise/project-fire
Length of output: 4079
🏁 Script executed:
# Check the full ScpHoldRestrictedComponent file to understand its scope
cat Content.Shared/_Scp/Holding/ScpHoldRestrictedComponent.csRepository: space-sunrise/project-fire
Length of output: 320
🏁 Script executed:
# Search for usages of ScpHoldRestrictedComponent in the codebase
rg -n "ScpHoldRestrictedComponent" --type=cs -A 2 -B 2Repository: space-sunrise/project-fire
Length of output: 1825
🏁 Script executed:
# Look for ScpHoldStage enum to understand Stage values
rg -n "enum ScpHoldStage\|Stage\s*=" --type=cs -A 5 | head -100Repository: space-sunrise/project-fire
Length of output: 52
🏁 Script executed:
# Check for any modification to ScpHoldRestrictedComponent or related Stage property
rg -n "ScpHoldRestricted" --type=cs -A 5 -B 5Repository: space-sunrise/project-fire
Length of output: 3735
🏁 Script executed:
# Look for ScpHoldStage enum definition to understand its values
rg -n "ScpHoldStage" --type=csRepository: space-sunrise/project-fire
Length of output: 901
🏁 Script executed:
# Check if there's any property or method that might set Stage
rg -n "Stage.*=" --type=cs | grep -i "scphold\|restrict" | head -20Repository: space-sunrise/project-fire
Length of output: 569
Добавьте атрибуты для сетевой синхронизации компонента.
Компонент помечен [NetworkedComponent] и находится в Content.Shared, поэтому должен соответствовать требованиям для предсказания и синхронизации состояния. Необходимо:
- Добавить
[AutoGenerateComponentState]к классу компонента - Добавить
[AutoNetworkedField]к полюStage
Это обеспечит корректную сериализацию, десериализацию и откат состояния при предсказании.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Content.Shared/_Scp/Holding/ScpHoldRestrictedComponent.cs` around lines 5 -
9, Добавьте атрибуты автогенерации и автосинхронизации для сетевого компонента:
пометьте класс ScpHoldRestrictedComponent атрибутом [AutoGenerateComponentState]
и пометьте поле Stage атрибутом [AutoNetworkedField]; это гарантирует, что поле
Stage будет участвовать в сериализации/десериализации и откате состояния при
предсказании для NetworkedComponent.
| public bool TryToggleHold(Entity<ScpHoldComponent> holder, EntityUid target, bool attemptChecked = false) | ||
| { | ||
| if (_holderQuery.TryComp(holder.Owner, out var activeHolder) && activeHolder.Target != null) | ||
| { | ||
| if (activeHolder.Target.Value == target) | ||
| { | ||
| ReleaseHolderContribution(holder.Owner, target, clearIfEmpty: true); | ||
| return true; | ||
| } | ||
|
|
||
| PopupHolder(holder.Owner, "scp-hold-already-holding-other"); | ||
| return false; | ||
| } | ||
|
|
||
| if (!CanStartHold(holder)) | ||
| return false; | ||
|
|
||
| if (!CanToggleHold(holder, target, checkAttempt: !attemptChecked)) | ||
| return false; |
There was a problem hiding this comment.
CanToggleHold() сейчас проверяет не toggle, а только start-hold.
TryToggleHold() уже обрабатывает ещё два состояния: release той же цели и отказ, если держатель занят другой целью. CanToggleHold() их игнорирует, поэтому даёт ложные true/false для precheck/UI/prediction и расходится с фактическим поведением публичного API.
🛠️ Вариант правки
public bool CanToggleHold(
Entity<ScpHoldComponent> holder,
EntityUid target,
bool quiet = false,
bool ignoreHandAvailability = false,
bool checkAttempt = false)
{
if (holder.Owner == target)
return false;
+
+ if (_holderQuery.TryComp(holder.Owner, out var activeHolder) && activeHolder.Target is { } activeTarget)
+ {
+ if (activeTarget == target)
+ return true;
+
+ if (!quiet)
+ PopupHolder(holder.Owner, "scp-hold-already-holding-other");
+ return false;
+ }
if (!CanStartHold(holder, quiet))
return false;Also applies to: 54-160
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Content.Shared/_Scp/Holding/SharedScpHoldingSystem.Actions.cs` around lines
22 - 40, CanToggleHold currently only validates starting a hold and thus
disagrees with TryToggleHold's actual toggle logic; update CanToggleHold to
mirror TryToggleHold's preconditions by returning true for the release case when
the holder's active target equals the provided target, false when the holder is
already holding a different target (so UI/prediction matches the PopupHolder
rejection), and otherwise perform the start-hold checks (or delegate to
CanStartHold) when the holder has no active target; ensure the method signature
and behavior follow the OnEvent -> TryDo -> CanDo -> Do pattern and reference
CanToggleHold and TryToggleHold so any callers/predictions stay consistent with
the public API.
| - type: ScpHold | ||
| holdableWhitelist: | ||
| components: | ||
| - Scp096 | ||
| holdableBlacklist: | ||
| components: | ||
| - ActiveScp096Rage | ||
| - ActiveScp096HeatingUp | ||
| - ActiveScp096WithoutFace |
There was a problem hiding this comment.
ClassDBotanist не должен получать ScpHold по заявленному scope фичи.
Сейчас механика удержания уходит в low-access роль, что расширяет доступ вне целевых подразделений и ломает баланс/авторизацию фичи.
💡 Предлагаемое исправление
- - type: ScpHold
- holdableWhitelist:
- components:
- - Scp096
- holdableBlacklist:
- components:
- - ActiveScp096Rage
- - ActiveScp096HeatingUp
- - ActiveScp096WithoutFace📝 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.
| - type: ScpHold | |
| holdableWhitelist: | |
| components: | |
| - Scp096 | |
| holdableBlacklist: | |
| components: | |
| - ActiveScp096Rage | |
| - ActiveScp096HeatingUp | |
| - ActiveScp096WithoutFace | |
| # ScpHold component removed from ClassDBotanist role | |
| # This role should not have SCP hold capability per feature scope requirements |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Resources/Prototypes/_Scp/Roles/Jobs/LowAccessPersonnel/class_d_botanist.yml`
around lines 26 - 34, В файле конфигурации ClassDBotanist удалите назначение
механики удержания: уберите или переместите блок type: ScpHold (и связанные поля
holdableWhitelist / holdableBlacklist) из class_d_botanist.yml, т.е. удалите
упоминание ScpHold у ClassDBotanist и перенесите эту фичу в правильный
scope/роль, где удержание должно быть доступно; убедитесь, что поля
holdableWhitelist и holdableBlacklist больше не присутствуют в описании
ClassDBotanist (или добавьте вместо них ссылку на корректный класс/роль,
отвечающую за ScpHold).
| - type: ScpHold | ||
| holdableWhitelist: | ||
| components: | ||
| - Scp096 | ||
| - ClassDAppearance | ||
| holdableBlacklist: | ||
| components: | ||
| - ActiveScp096Rage | ||
| - ActiveScp096HeatingUp | ||
| - ActiveScp096WithoutFace |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Рекомендация по уменьшению дублирования (опционально).
Конфигурация ScpHold с одинаковым whitelist/blacklist повторяется в нескольких job-файлах. Для улучшения поддерживаемости можно рассмотреть:
- Создание базового job-прототипа с общей конфигурацией
ScpHold - Или использование отдельного компонента-шаблона
Это необязательное улучшение — текущий подход работает и обеспечивает гибкость настройки per-job.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@Resources/Prototypes/_Scp/Roles/Jobs/SpecialPurposeSquad/heavy_containment_zone_officer.yml`
around lines 31 - 40, Конфигурация ScpHold с одинаковыми секциями
holdableWhitelist и holdableBlacklist (components: Scp096, ClassDAppearance и
blacklist: ActiveScp096Rage, ActiveScp096HeatingUp, ActiveScp096WithoutFace)
повторяется в нескольких job-файлах; вынесите её в общий шаблон или базовый
job-прототип (например: базовый прототип/компонент с именем ScpHoldBase) и
замените дублирующие блоки в файлах на ссылку/наследование этого шаблона,
сохранив возможность переопределения по нужде.
| - type: ScpHold | ||
| holdableWhitelist: | ||
| components: | ||
| - Scp096 | ||
| - ClassDAppearance | ||
| holdableBlacklist: | ||
| components: | ||
| - ActiveScp096Rage | ||
| - ActiveScp096HeatingUp | ||
| - ActiveScp096WithoutFace |
There was a problem hiding this comment.
Уберите ClassDAppearance из whitelist для этого SCP-096 сценария.
Добавление ClassDAppearance расширяет применение механики на нерелевантные цели. Для заявленного сценария OCN здесь достаточно фокуса на Scp096 + state blacklist.
💡 Предлагаемое исправление
- type: ScpHold
holdableWhitelist:
components:
- Scp096
- - ClassDAppearance
holdableBlacklist:
components:
- ActiveScp096Rage
- ActiveScp096HeatingUp
- ActiveScp096WithoutFace🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@Resources/Prototypes/_Scp/Roles/Jobs/SpecialPurposeSquad/junior_heavy_containment_zone_officer.yml`
around lines 30 - 39, В блоке конфигурации ScpHold удалите запись
ClassDAppearance из holdableWhitelist — оставьте только Scp096 в whitelist и
сохраните текущий holdableBlacklist (ActiveScp096Rage, ActiveScp096HeatingUp,
ActiveScp096WithoutFace) чтобы фокусировать механику только на SCP-096;
проверьте секцию ScpHold и поля holdableWhitelist/holdableBlacklist и уберите
только ClassDAppearance, не трогая остальные ключи.
| - type: ScpHoldRestricted # Fire edit - block combat mode while held | ||
| stage: Soft |
There was a problem hiding this comment.
Замените # Fire edit на # Sunrise-Edit.
Это upstream-файл, поэтому для этой изменённой строки нужно использовать Sunrise-маркер.
As per coding guidelines: "This repository's repository name is sunrise-station, so use the Sunrise prefix ... and Sunrise-Edit ... markers for unavoidable upstream edits."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Resources/Prototypes/Actions/types.yml` around lines 264 - 265, The comment
on the upstream entry for the action type ScpHoldRestricted uses the wrong
upstream marker; replace the inline comment "# Fire edit" with the required
repository upstream marker "# Sunrise-Edit" (exact casing) so the
ScpHoldRestricted line uses the Sunrise upstream edit marker as per guidelines.
| - type: ScpHold # TODO: Убрать перед мержем | ||
| - type: ScpHoldable |
There was a problem hiding this comment.
Используйте Sunrise-маркер для upstream-правки.
Для изменённых строк в upstream YAML здесь должен использоваться # Sunrise-Edit, а не Fire-формат.
As per coding guidelines: "This repository's repository name is sunrise-station, so use the Sunrise prefix, the _Sunrise project folder, and Sunrise-Edit / Sunrise edit start/end markers for unavoidable upstream edits."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Resources/Prototypes/Entities/Mobs/Species/base.yml` around lines 281 - 282,
The upstream YAML changes use a Fire-format inline comment instead of the
required Sunrise markers; replace the Fire comment on the modified entries
(types ScpHold and ScpHoldable) with the Sunrise upstream edit markers—wrap the
edited lines with the Sunrise edit markers (e.g., use the "Sunrise-Edit" or
"Sunrise edit start" / "Sunrise edit end" pattern) so the change follows the
repository's Sunrise upstream-edit convention for the Species base.yml entries
where ScpHold and ScpHoldable are altered.
Уберите ScpHold из BaseMobSpeciesOrganic.
На Line 281 вы добавляете ScpHold в базовый органический прототип, из-за чего механика выдаётся всем органикам, а не только целевым ролям через job special. Это ломает ролевые ограничения удержания.
💡 Предлагаемое исправление
- - type: ScpHold # TODO: Убрать перед мержем
- type: ScpHoldable🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Resources/Prototypes/Entities/Mobs/Species/base.yml` around lines 281 - 282,
В BaseMobSpeciesOrganic удалите запись типа ScpHold (оставьте ScpHoldable если
нужно), чтобы базовый органический прототип больше не получал механику
удержания; вместо этого назначьте компонент ScpHold только нужным ролям через
job "special" (проверьте где определяется job "special" и добавьте/переместите
тип ScpHold туда).
Краткое описание | Short description
Добавил систему удержания. Она позволяет двум людям схватить человека и не дать ему двигаться некоторое время.
Ее получит Комендантский отряд для всего персонала и отряд ОСН для SCP-096 с особыми условиями для процедуры лечения.
Ссылка на багрепорт/Предложение | Related Issue/Bug Report
Closes #824
Медиа (Видео/Скриншоты) | Media (Video/Screenshots)
Changelog
🆑 ThereDrD
Summary by CodeRabbit
Новые возможности