Skip to content

[PORT] Neuroaversão#752

Open
Will-Oliver-Br wants to merge 11 commits into
Project-Dumont:dumontfrom
Will-Oliver-Br:Port-Neuroaversion
Open

[PORT] Neuroaversão#752
Will-Oliver-Br wants to merge 11 commits into
Project-Dumont:dumontfrom
Will-Oliver-Br:Port-Neuroaversion

Conversation

@Will-Oliver-Br

Copy link
Copy Markdown
Contributor

Sobre a PR

Porta as PRs do Funky que adiciona a trait enxaquecas crônicas, que aplica um efeito em um tempo aleatório. E a trait neuroaversão, que adiciona o mesmo efeito das enxaquecas crônicas, só que com algumas coisas a mais, mas isso apenas acontece quando se coloca qualquer implante.
Prs: funky-station/funky-station#2365, funky-station/funky-station#2495, funky-station/funky-station#2597 & funky-station/funky-station#2765.

Por quê? / Balanceamento

Mais traits.

Anexos

2026-06-05.10-18-59.mp4

Requerimentos

Changelog

🆑

  • add: Adiciona as traits enxaquecas crônicas e neuroaversão.

TrixxedHeart and others added 7 commits June 5, 2026 10:25
Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com>
Co-authored-by: funkystationbot <funky@funkystation.org>
Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com>
Co-authored-by: funkystationbot <funky@funkystation.org>
Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com>
Co-authored-by: funkystationbot <funky@funkystation.org>
* refactor neuroaversion and chronic migraines

refactor neuroaversion and chronic migraines

wait

improve nightmare code

fix fuck

* remove comment

* chore: Automatically update REUSE headers

* Delete triggerseizurecomponent

* chore: Automatically update REUSE headers

* "are you stupid" me:

* mmm

---------

Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com>
Co-authored-by: funkystationbot <funky@funkystation.org>
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adição completa de dois traços neurológicos jogáveis (Enxaquecas Crônicas e Neuroaversão) com componentes sincronizados, sistemas de orquestração, renderização visual com shader, áudio preditivo e mecânicas de dano/movimento incluindo gatilho probabilístico de convulsão via acúmulo de "seizure build".

Changes

Sistema de Enxaqueca e Convulsão com Neuroaversão

Layer / File(s) Summary
Componentes de Estado de Rede Compartilhados
Content.Shared/Traits/Assorted/*.cs
SeizureComponent com máquina de estados (Prodrome/Seizure/Recovery/Fading), SeizureOverlayComponent para visuals, MigraineComponent para desfoque/pulso, ChronicMigrainesComponent para agendamento, e NeuroAversionComponent para timers/build/multiplicadores.
Sistemas Base Abstratos Compartilhados
Content.Shared/Traits/Assorted/Shared*.cs
SharedSeizureSystem gerencia fases de convulsão e interpolação visual; SharedNeuroAversionSystem implementa acúmulo de seizure build e cálculo probabilístico de disparo; MigraineSystem (shared) controla duração e fade com suporte a slowdown.
Orquestração de Enxaquecas Crônicas
Content.Server/Traits/Assorted/ChronicMigrainesSystem.cs
Sistema servidor agenda episódios aleatórios de enxaqueca, valida MigraineComponent ativo, exibe popups contextuais, delega para NeuroAversionSystem quando presente.
Orquestração de Neuroaversão
Content.Server/Traits/Assorted/NeuroAversionSystem.cs
Processa timers de enxaqueca/convulsão com multiplicadores por HP/mindshield, implementa cálculo e disparo probabilístico de seizure, detecta job-granted mindshield, aplica duração estendida com chronic migraines.
Implementação Concreta de Convulsão
Content.Server/Traits/Assorted/SeizureSystem.cs, Content.Shared/EntityEffects/Effects/ReduceSeizureBuild.cs
SeizureSystem (server) executa transições de fase com paralyze/stun/jitter/stutter e dano proporcional ao movimento; TriggerSeizureEffect ativa convulsão via reagente; ReduceSeizureBuild reduz build enquanto reagente estiver presente.
Renderização e Áudio Cliente
Content.Client/Traits/Assorted/MigraineOverlay.cs, Content.Client/Traits/Assorted/*System.cs
MigraineOverlay renderiza shader de desfoque/pulso sincronizado com MigraineComponent ou SeizureOverlayComponent; MigraineSystem toca áudio de enxaqueca; SeizureSystem toca áudio de pródromo/crise conforme estado visual.
Protótipos, Traits e Localizações
Resources/Prototypes/*, Resources/Locale/*/traits.ftl, Resources/Audio/*
Definição de traits (ChronicMigraines, NeuroAversion) e status effects (Migraine, MigraineSeizure); strings em en-US/pt-BR para eventos; integração de ReduceSeizureBuild ao reagente Psicodine; atribuições CC0 para áudio migraine.ogg.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed O título '[PORT] Neuroaversão' é parcialmente relacionado ao changeset, referindo-se a um aspecto real (a trait neuroaversão) mas não capturando completamente a mudança principal (adição de duas traits: enxaquecas crônicas E neuroaversão).
Description check ✅ Passed A descrição é claramente relacionada ao changeset, explicando o propósito de portar duas traits do repositório Funky, com referências às PRs originais e detalhes sobre funcionalidade.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Kyoth25f Kyoth25f left a comment

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.

altere os nomes das funções, nome das variáveis, comentários e documentação de acordo com a mudança de todo implante afetar.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 12

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Content.Client/Traits/Assorted/MigraineSystem.cs`:
- Around line 54-59: Content.Client/Traits/Assorted/MigraineSystem.cs (54-59):
change the guard that currently only sets _playedStartSound = false when
LocalSession?.AttachedEntity is null to instead detect when AttachedEntity !=
_lastLocalEntity and reset _playedStartSound (and update _lastLocalEntity) so
flags are cleared on body swap, not only on detach;
Content.Client/Traits/Assorted/MigraineSystem.cs (77-84): on the detach branch
also clear the tracked _lastLocalEntity (or set to EntityUid.Invalid) in
addition to existing per-component resets so there is no carry‑over after swap;
Content.Client/Traits/Assorted/SeizureSystem.cs (42-48): detect change of
LocalSession?.AttachedEntity compared to a stored _lastLocalEntity and reset
_playedProdromeSound and _playedSeizureSound when the local entity changes
(update _lastLocalEntity as well);
Content.Client/Traits/Assorted/SeizureSystem.cs (76-95): keep the existing
per-component cycle resets but augment with the local-entity-swap reset (no
direct change needed here if the root fix updates _lastLocalEntity and resets
flags globally, otherwise add an explicit check to reset these flags when
LocalSession?.AttachedEntity differs from _lastLocalEntity).

In `@Content.Server/Traits/Assorted/NeuroAversionSystem.cs`:
- Around line 124-127: The conditional that sets traitInteractionMult in
NeuroAversionSystem.cs uses ChronicMigraineInteractionMultiplier for the true
branch but incorrectly uses 1.3f for the false branch as well, neutralizing the
effect; change the false branch to the neutral multiplier (1.0f) so it reads:
var traitInteractionMult = hasChronicMigraines ?
ChronicMigraineInteractionMultiplier : 1.0f, updating the expression that uses
hasChronicMigraines, traitInteractionMult and
ChronicMigraineInteractionMultiplier.
- Around line 95-101: The migraine timer progression is inverted: in
NeuroAversionSystem.cs compute seconds as frameTime * migraineFactor *
severityMultiplier (i.e., multiply by migraineFactor) instead of frameTime * (1f
/ migraineFactor) * severityMultiplier so that more-damaged entities advance the
timer faster; update the seconds calculation that feeds comp.NextMigraineTime
accordingly (referencing migraineFactor, severityMultiplier, frameTime, and
comp.NextMigraineTime).

In `@Content.Server/Traits/Assorted/SeizureSystem.cs`:
- Around line 112-128: StartSeizure currently ignores the seizureDuration
parameter so callers can't override seizure length; set
seizureComp.SeizureDuration to the provided seizureDuration (or leave default
when null) and, if appropriate, update seizureComp.RemainingTime when you
directly start the Seizure state. Modify the StartSeizure method to copy the
seizureDuration into SeizureComponent.SeizureDuration (and adjust RemainingTime
when entering SeizureState), and apply the same change to any other StartSeizure
overloads (the other StartSeizure implementation mentioned in the review) so all
callers' custom durations are honored.
- Around line 238-245: StopSeizure currently only removes SeizureComponent and
stutter, leaving SeizureOverlayComponent and active effects (paralyze/jitter)
running; update StopSeizure to immediately terminate all seizure state: remove
or trigger fade-out on SeizureOverlayComponent, cancel any
prodrome/active-seizure timers, and clear or remove any paralysis/jitter
status/effect components applied by the seizure logic so there is no lingering
disable/jitter. Refer to StopSeizure, SeizureComponent, SeizureOverlayComponent
and _stuttering when making changes and ensure any helper/cleanup methods used
by seizure start (e.g., effect-apply or timer registration) are reversed here so
the public contract "stops immediately" is satisfied.

In `@Content.Shared/EntityEffects/Effects/ReduceSeizureBuild.cs`:
- Around line 45-52: Remove the unprofessional inline comment "this is probably
a fucked way to do this" in ReduceSeizureBuild.cs; replace it with a
professional note if needed (e.g., "TODO: review approach for accessing
bloodstream Solution via SharedSolutionContainerSystem and consider alternative
APIs or performance implications" or open a ticket) near the block that obtains
solutionSystem, Solution? bloodstream, and the TryGetComponent/TryGetSolution
calls so the intent and any concerns are documented clearly.
- Around line 39-40: Remova o comentário "Clamp that EXPLICITIVE" e ajuste a
lógica de clamp em ReduceSeizureBuild: em vez de forçar um mínimo arbitrário
após a escala, clame o valor base (ReductionAmount) para o intervalo válido (por
exemplo 0..1) antes de aplicar Scale e então compute actualReduction =
clampedReductionAmount * Scale; se for necessário garantir limites finais,
aplique um clamp final em actualReduction para [0,1] (não um mínimo arbitrário
como 0.001). Referencie os símbolos ReductionAmount, Scale e actualReduction ao
fazer a mudança.

In `@Content.Shared/Traits/Assorted/MigraineSystem.cs`:
- Around line 57-63: O trecho em MigraineSystem que calcula fadeSpeed/fadeAmount
pode dividir por zero quando migraine.FadeOutDuration == 0; corrija verificando
FadeOutDuration antes da divisão (por exemplo, se FadeOutDuration <= 0 então
tratar fadeAmount como 1.0f ou calcular fadeAmount = frameTime / FadeOutDuration
somente quando > 0) e em seguida clamp (0..1) o fadeAmount antes de usá-lo nas
chamadas MathHelper.Lerp para migraine.BlurryMagnitude e
migraine.PulseAmplitude; ajuste as variáveis fadeSpeed/fadeAmount (referenciadas
no bloco onde migraine.IsFading é true) para evitar Infinity/NaN e garantir
valores válidos passados ao Lerp.

In `@Content.Shared/Traits/Assorted/SharedNeuroAversionSystem.cs`:
- Around line 61-75: GetConditionMultiplier currently multiplies base health
multiplier by the mindshield severity multiplier (uses comp.StartedMindShielded
? comp.StartedMindShieldedMultiplier : comp.MidRoundMindShieldedMultiplier), but
the server applies that severity multiplier again later, causing duplication;
remove the severity multiplication here so
GetConditionMultiplier(NeuroAversionComponent comp, bool isCritical, float
missingHpFrac) only computes and returns the base health multiplier
(ConditionCritical/Bad/Okay/Good) and let the hazard code continue to apply the
mindshield severity multiplier once.

In `@Content.Shared/Traits/Assorted/SharedSeizureSystem.cs`:
- Around line 49-116: SharedSeizureSystem: the overlay is being advanced twice
per tick—remove duplicate mutation by ensuring only one path updates
pulse/fade/blur: in UpdateSeizureComponents (which calls UpdateOverlayVisuals)
stop mutating SeizureOverlayComponent there (e.g., remove
overlayComp.PulseAccumulator+=frameTime and the fade/ramp logic) and leave
UpdateOverlayComponents responsible for PulseAccumulator, UpdateOverlayFade and
Dirty, or vice versa—pick one canonical updater and make the other only perform
non-visual responsibilities (e.g., movement recovery). Server SeizureSystem:
apply the same separation in the override in
Content.Server/Traits/Assorted/SeizureSystem.cs (lines 66-105) so the server
does not also advance the overlay twice—replicate the same change (make the
override call the single chosen visual updater or remove duplicate pulse/fade
mutations) so behavior is consistent between shared and server implementations.
- Around line 211-223: When transitioning to Fading, avoid double-applying
slowdown by clearing or ignoring the sealed component multiplier: in
TransitionToFading set seizure.MovementSpeedMultiplier = 1.0 (or 0 if
multipliers are applied additively) immediately after setting
seizure.CurrentState = SeizureState.Fading so only the overlay's
MovementSpeedMultiplier is used; alternatively, modify OnRefreshMovementSpeed to
early-return when seizure.CurrentState == SeizureState.Fading so it does not
apply SeizureComponent.MovementSpeedMultiplier while the overlay is fading.
Update whichever approach you choose (functions: TransitionToFading,
OnRefreshMovementSpeed; field: SeizureComponent.MovementSpeedMultiplier; enum:
SeizureState.Fading) and add a unit comment explaining the behavior.
- Around line 198-223: TransitionToSeizure and TransitionToRecovery update
SeizureComponent fields but never mark the component dirty, so add a Dirty(uid,
seizure) call after setting seizure.CurrentState and seizure.RemainingTime in
both TransitionToSeizure and TransitionToRecovery to ensure state and
RemainingTime replicate to clients (use the same Dirty helper already used in
TransitionToFading); no other logic changes required.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0ab33d97-b688-4748-a532-d215a8741017

📥 Commits

Reviewing files that changed from the base of the PR and between 0a43d18 and 5d6fcd1.

⛔ Files ignored due to path filters (3)
  • Resources/Audio/_Funkystation/Effects/Migraine/migraine.ogg is excluded by !**/*.ogg
  • Resources/Audio/_Funkystation/Effects/Migraine/neuroseize.ogg is excluded by !**/*.ogg
  • Resources/Audio/_Funkystation/Effects/Migraine/prodrome.ogg is excluded by !**/*.ogg
📒 Files selected for processing (25)
  • Content.Client/Traits/Assorted/MigraineOverlay.cs
  • Content.Client/Traits/Assorted/MigraineSystem.cs
  • Content.Client/Traits/Assorted/SeizureSystem.cs
  • Content.Server/Traits/Assorted/ChronicMigrainesSystem.cs
  • Content.Server/Traits/Assorted/NeuroAversionSystem.cs
  • Content.Server/Traits/Assorted/SeizureSystem.cs
  • Content.Shared/EntityEffects/Effects/ReduceSeizureBuild.cs
  • Content.Shared/Traits/Assorted/ChronicMigrainesComponent.cs
  • Content.Shared/Traits/Assorted/MigraineComponent.cs
  • Content.Shared/Traits/Assorted/MigraineSystem.cs
  • Content.Shared/Traits/Assorted/NeuroAversionComponent.cs
  • Content.Shared/Traits/Assorted/SeizureComponent.cs
  • Content.Shared/Traits/Assorted/SeizureOverlayComponent.cs
  • Content.Shared/Traits/Assorted/SharedNeuroAversionSystem.cs
  • Content.Shared/Traits/Assorted/SharedSeizureSystem.cs
  • Resources/Audio/_Funkystation/Effects/Migraine/attributions.yml
  • Resources/Locale/en-US/_Funkystation/chemistry/effects.ftl
  • Resources/Locale/en-US/_Funkystation/traits/traits.ftl
  • Resources/Locale/en-US/traits/traits.ftl
  • Resources/Locale/pt-BR/_Funkystation/chemistry/effects.ftl
  • Resources/Locale/pt-BR/_Funkystation/traits/traits.ftl
  • Resources/Prototypes/Reagents/medicine.yml
  • Resources/Prototypes/Traits/quirks.yml
  • Resources/Prototypes/_Funkystation/Traits/quirks.yml
  • Resources/Prototypes/status_effects.yml

Comment on lines +54 to +59
var localEntity = _playerManager.LocalSession?.AttachedEntity;
if (localEntity == null)
{
_playedStartSound = false;
return;
}

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 | ⚡ Quick win

As flags de áudio one-shot não estão acopladas à troca de entidade local, causando supressão de som após swap de corpo.

  • Content.Client/Traits/Assorted/MigraineSystem.cs#L54-L59: resetar _playedStartSound quando AttachedEntity mudar (não apenas quando virar null).
  • Content.Client/Traits/Assorted/MigraineSystem.cs#L77-L84: no detach, limpar também o estado da última entidade rastreada para evitar carry-over.
  • Content.Client/Traits/Assorted/SeizureSystem.cs#L42-L48: resetar _playedProdromeSound/_playedSeizureSound quando AttachedEntity mudar.
  • Content.Client/Traits/Assorted/SeizureSystem.cs#L76-L95: manter reset de ciclo por componente, mas complementar com reset por troca de entidade local.
📍 Affects 2 files
  • Content.Client/Traits/Assorted/MigraineSystem.cs#L54-L59 (this comment)
  • Content.Client/Traits/Assorted/MigraineSystem.cs#L77-L84
  • Content.Client/Traits/Assorted/SeizureSystem.cs#L42-L48
  • Content.Client/Traits/Assorted/SeizureSystem.cs#L76-L95
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Client/Traits/Assorted/MigraineSystem.cs` around lines 54 - 59,
Content.Client/Traits/Assorted/MigraineSystem.cs (54-59): change the guard that
currently only sets _playedStartSound = false when LocalSession?.AttachedEntity
is null to instead detect when AttachedEntity != _lastLocalEntity and reset
_playedStartSound (and update _lastLocalEntity) so flags are cleared on body
swap, not only on detach; Content.Client/Traits/Assorted/MigraineSystem.cs
(77-84): on the detach branch also clear the tracked _lastLocalEntity (or set to
EntityUid.Invalid) in addition to existing per-component resets so there is no
carry‑over after swap; Content.Client/Traits/Assorted/SeizureSystem.cs (42-48):
detect change of LocalSession?.AttachedEntity compared to a stored
_lastLocalEntity and reset _playedProdromeSound and _playedSeizureSound when the
local entity changes (update _lastLocalEntity as well);
Content.Client/Traits/Assorted/SeizureSystem.cs (76-95): keep the existing
per-component cycle resets but augment with the local-entity-swap reset (no
direct change needed here if the root fix updates _lastLocalEntity and resets
flags globally, otherwise add an explicit check to reset these flags when
LocalSession?.AttachedEntity differs from _lastLocalEntity).

Comment on lines +95 to +101
var migraineFactor = 1f - (0.5f * (1f - missingHpFrac));
var severityMultiplier = comp.StartedMindShielded
? comp.StartedMindShieldedMultiplier
: comp.MidRoundMindShieldedMultiplier;

var seconds = frameTime * (1f / migraineFactor) * severityMultiplier;
comp.NextMigraineTime -= TimeSpan.FromSeconds(seconds);

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 | 🟠 Major | ⚡ Quick win

A progressão do timer de enxaqueca está invertida em relação ao dano.

Na Line 100, dividir por migraineFactor faz entidades mais saudáveis avançarem o timer mais rápido que entidades mais feridas, contrariando a regra descrita no próprio método.

🔧 Correção sugerida
-        var seconds = frameTime * (1f / migraineFactor) * severityMultiplier;
+        var seconds = frameTime * migraineFactor * severityMultiplier;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Server/Traits/Assorted/NeuroAversionSystem.cs` around lines 95 - 101,
The migraine timer progression is inverted: in NeuroAversionSystem.cs compute
seconds as frameTime * migraineFactor * severityMultiplier (i.e., multiply by
migraineFactor) instead of frameTime * (1f / migraineFactor) *
severityMultiplier so that more-damaged entities advance the timer faster;
update the seconds calculation that feeds comp.NextMigraineTime accordingly
(referencing migraineFactor, severityMultiplier, frameTime, and
comp.NextMigraineTime).

Comment on lines +124 to +127
var traitInteractionMult = hasChronicMigraines
? ChronicMigraineInteractionMultiplier
: 1.3f;

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 | 🟠 Major | ⚡ Quick win

A interação com ChronicMigraines está neutralizada.

Nas Lines 124-127, traitInteractionMult recebe 1.3f nos dois ramos; na prática, ter ou não ChronicMigraines não altera o cálculo.

🔧 Correção sugerida
-        var traitInteractionMult = hasChronicMigraines
-            ? ChronicMigraineInteractionMultiplier
-            : 1.3f;
+        var traitInteractionMult = hasChronicMigraines
+            ? ChronicMigraineInteractionMultiplier
+            : 1f;
📝 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
var traitInteractionMult = hasChronicMigraines
? ChronicMigraineInteractionMultiplier
: 1.3f;
var traitInteractionMult = hasChronicMigraines
? ChronicMigraineInteractionMultiplier
: 1f;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Server/Traits/Assorted/NeuroAversionSystem.cs` around lines 124 -
127, The conditional that sets traitInteractionMult in NeuroAversionSystem.cs
uses ChronicMigraineInteractionMultiplier for the true branch but incorrectly
uses 1.3f for the false branch as well, neutralizing the effect; change the
false branch to the neutral multiplier (1.0f) so it reads: var
traitInteractionMult = hasChronicMigraines ?
ChronicMigraineInteractionMultiplier : 1.0f, updating the expression that uses
hasChronicMigraines, traitInteractionMult and
ChronicMigraineInteractionMultiplier.

Comment on lines +112 to +128
public void StartSeizure(EntityUid uid, float? seizureDuration = null, float prodromeDuration = 10f,
string? prodromePopupKey = null, string? seizurePopupKey = null, string? recoveryPopupKey = null)
{
if (HasComp<SeizureComponent>(uid))
return; // Already having a seizure

var seizureComp = AddComp<SeizureComponent>(uid);
seizureComp.CurrentState = SeizureState.Prodrome;
seizureComp.ProdromeDuration = prodromeDuration;
seizureComp.RemainingTime = prodromeDuration;

seizureComp.ProdromePopupKey = prodromePopupKey;
seizureComp.SeizurePopupKey = seizurePopupKey;
seizureComp.RecoveryPopupKey = recoveryPopupKey;

StartProdromePhase(uid, seizureComp);
}

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 | 🟠 Major | ⚡ Quick win

seizureDuration é aceito e descartado.

TriggerSeizureEffect calcula uma duração customizada e a passa para StartSeizure, mas esse valor nunca é copiado para SeizureComponent.SeizureDuration. Na prática, reagentes/callers não conseguem alterar a duração da crise.

Also applies to: 269-272

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Server/Traits/Assorted/SeizureSystem.cs` around lines 112 - 128,
StartSeizure currently ignores the seizureDuration parameter so callers can't
override seizure length; set seizureComp.SeizureDuration to the provided
seizureDuration (or leave default when null) and, if appropriate, update
seizureComp.RemainingTime when you directly start the Seizure state. Modify the
StartSeizure method to copy the seizureDuration into
SeizureComponent.SeizureDuration (and adjust RemainingTime when entering
SeizureState), and apply the same change to any other StartSeizure overloads
(the other StartSeizure implementation mentioned in the review) so all callers'
custom durations are honored.

Comment on lines +238 to +245
public void StopSeizure(EntityUid uid)
{
if (!HasComp<SeizureComponent>(uid))
return;

_stuttering.DoRemoveStutter(uid, 0);
RemComp<SeizureComponent>(uid);
}

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 | 🟠 Major | ⚡ Quick win

StopSeizure() não encerra a crise de fato.

O método remove só SeizureComponent e stutter. Se for chamado durante prodrome/crise, o SeizureOverlayComponent fica ativo sem entrar em fade, e efeitos já aplicados como paralyze/jitter continuam até expirarem. Isso contradiz a API pública de "stops immediately".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Server/Traits/Assorted/SeizureSystem.cs` around lines 238 - 245,
StopSeizure currently only removes SeizureComponent and stutter, leaving
SeizureOverlayComponent and active effects (paralyze/jitter) running; update
StopSeizure to immediately terminate all seizure state: remove or trigger
fade-out on SeizureOverlayComponent, cancel any prodrome/active-seizure timers,
and clear or remove any paralysis/jitter status/effect components applied by the
seizure logic so there is no lingering disable/jitter. Refer to StopSeizure,
SeizureComponent, SeizureOverlayComponent and _stuttering when making changes
and ensure any helper/cleanup methods used by seizure start (e.g., effect-apply
or timer registration) are reversed here so the public contract "stops
immediately" is satisfied.

Comment on lines +57 to +63
if (migraine.IsFading)
{
var fadeSpeed = 1.0f / migraine.FadeOutDuration;
var fadeAmount = fadeSpeed * frameTime;

migraine.BlurryMagnitude = MathHelper.Lerp(migraine.BlurryMagnitude, 0f, fadeAmount);
migraine.PulseAmplitude = MathHelper.Lerp(migraine.PulseAmplitude, 0f, fadeAmount);

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 | 🟠 Major | ⚡ Quick win

Evite divisão por zero no fade da enxaqueca.

Na Line 59, FadeOutDuration pode vir como 0 (config/protótipo), gerando Infinity em fadeAmount e comportamento de interpolação inválido.

🔧 Correção sugerida
-                var fadeSpeed = 1.0f / migraine.FadeOutDuration;
-                var fadeAmount = fadeSpeed * frameTime;
+                var fadeDuration = MathF.Max(0.001f, migraine.FadeOutDuration);
+                var fadeAmount = MathF.Min(1f, frameTime / fadeDuration);
📝 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
if (migraine.IsFading)
{
var fadeSpeed = 1.0f / migraine.FadeOutDuration;
var fadeAmount = fadeSpeed * frameTime;
migraine.BlurryMagnitude = MathHelper.Lerp(migraine.BlurryMagnitude, 0f, fadeAmount);
migraine.PulseAmplitude = MathHelper.Lerp(migraine.PulseAmplitude, 0f, fadeAmount);
if (migraine.IsFading)
{
var fadeDuration = MathF.Max(0.001f, migraine.FadeOutDuration);
var fadeAmount = MathF.Min(1f, frameTime / fadeDuration);
migraine.BlurryMagnitude = MathHelper.Lerp(migraine.BlurryMagnitude, 0f, fadeAmount);
migraine.PulseAmplitude = MathHelper.Lerp(migraine.PulseAmplitude, 0f, fadeAmount);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Shared/Traits/Assorted/MigraineSystem.cs` around lines 57 - 63, O
trecho em MigraineSystem que calcula fadeSpeed/fadeAmount pode dividir por zero
quando migraine.FadeOutDuration == 0; corrija verificando FadeOutDuration antes
da divisão (por exemplo, se FadeOutDuration <= 0 então tratar fadeAmount como
1.0f ou calcular fadeAmount = frameTime / FadeOutDuration somente quando > 0) e
em seguida clamp (0..1) o fadeAmount antes de usá-lo nas chamadas
MathHelper.Lerp para migraine.BlurryMagnitude e migraine.PulseAmplitude; ajuste
as variáveis fadeSpeed/fadeAmount (referenciadas no bloco onde migraine.IsFading
é true) para evitar Infinity/NaN e garantir valores válidos passados ao Lerp.

Comment on lines +61 to +75
protected static float GetConditionMultiplier(NeuroAversionComponent comp, bool isCritical, float missingHpFrac)
{
// Determine base multiplier from health state
float baseMultiplier = isCritical ? comp.ConditionCriticalMultiplier
: missingHpFrac >= BadHealthThreshold ? comp.ConditionBadMultiplier
: missingHpFrac >= OkayHealthThreshold ? comp.ConditionOkayMultiplier
: comp.ConditionGoodMultiplier;

// Apply severity multiplier based on when mindshield was applied
var severityMultiplier = comp.StartedMindShielded
? comp.StartedMindShieldedMultiplier
: comp.MidRoundMindShieldedMultiplier;

return baseMultiplier * severityMultiplier;
}

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 | 🟠 Major | ⚡ Quick win

Não duplique o multiplicador de mindshield no cálculo de convulsão.

As Lines 70-74 já aplicam severidade de mindshield dentro de GetConditionMultiplier, e o servidor multiplica novamente no hazard. Isso infla/achata a probabilidade final de forma não intencional.

🔧 Correção sugerida (na causa raiz)
-        // Apply severity multiplier based on when mindshield was applied
-        var severityMultiplier = comp.StartedMindShielded
-            ? comp.StartedMindShieldedMultiplier
-            : comp.MidRoundMindShieldedMultiplier;
-
-        return baseMultiplier * severityMultiplier;
+        return baseMultiplier;
📝 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
protected static float GetConditionMultiplier(NeuroAversionComponent comp, bool isCritical, float missingHpFrac)
{
// Determine base multiplier from health state
float baseMultiplier = isCritical ? comp.ConditionCriticalMultiplier
: missingHpFrac >= BadHealthThreshold ? comp.ConditionBadMultiplier
: missingHpFrac >= OkayHealthThreshold ? comp.ConditionOkayMultiplier
: comp.ConditionGoodMultiplier;
// Apply severity multiplier based on when mindshield was applied
var severityMultiplier = comp.StartedMindShielded
? comp.StartedMindShieldedMultiplier
: comp.MidRoundMindShieldedMultiplier;
return baseMultiplier * severityMultiplier;
}
protected static float GetConditionMultiplier(NeuroAversionComponent comp, bool isCritical, float missingHpFrac)
{
// Determine base multiplier from health state
float baseMultiplier = isCritical ? comp.ConditionCriticalMultiplier
: missingHpFrac >= BadHealthThreshold ? comp.ConditionBadMultiplier
: missingHpFrac >= OkayHealthThreshold ? comp.ConditionOkayMultiplier
: comp.ConditionGoodMultiplier;
return baseMultiplier;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Shared/Traits/Assorted/SharedNeuroAversionSystem.cs` around lines 61
- 75, GetConditionMultiplier currently multiplies base health multiplier by the
mindshield severity multiplier (uses comp.StartedMindShielded ?
comp.StartedMindShieldedMultiplier : comp.MidRoundMindShieldedMultiplier), but
the server applies that severity multiplier again later, causing duplication;
remove the severity multiplication here so
GetConditionMultiplier(NeuroAversionComponent comp, bool isCritical, float
missingHpFrac) only computes and returns the base health multiplier
(ConditionCritical/Bad/Okay/Good) and let the hazard code continue to apply the
mindshield severity multiplier once.

Comment on lines +49 to +116
protected virtual void UpdateSeizureComponents(float frameTime)
{
var query = EntityQueryEnumerator<SeizureComponent>();
while (query.MoveNext(out var uid, out var seizure))
{
seizure.RemainingTime -= frameTime;

UpdateMovementSpeed(uid, seizure, frameTime);
UpdateOverlayVisuals(uid, seizure, frameTime);

if (seizure.RemainingTime <= 0f)
{
HandlePhaseTransition(uid, seizure);
}
}
}

protected virtual void UpdateOverlayComponents(float frameTime)
{
var overlayQuery = EntityQueryEnumerator<SeizureOverlayComponent>();
while (overlayQuery.MoveNext(out var overlayUid, out var overlay))
{
overlay.PulseAccumulator += frameTime;

if (overlay.HandleMovementRecovery)
{
UpdateMovementRecovery(overlayUid, overlay, frameTime);
}

UpdateOverlayFade(overlayUid, overlay, frameTime);
Dirty(overlayUid, overlay);
}
}

protected virtual void UpdateOverlayVisuals(EntityUid uid, SeizureComponent seizure, float frameTime)
{
if (!TryComp<SeizureOverlayComponent>(uid, out var overlayComp))
return;

overlayComp.PulseAccumulator += frameTime;

if (overlayComp.IsFading)
{
var fadeSpeed = 1.0f / overlayComp.FadeOutDuration;
var fadeAmount = fadeSpeed * frameTime;

overlayComp.BlurryMagnitude = MathHelper.Lerp(overlayComp.BlurryMagnitude, 0f, fadeAmount);
overlayComp.PulseAmplitude = MathHelper.Lerp(overlayComp.PulseAmplitude, 0f, fadeAmount);
overlayComp.CurrentBlur = MathHelper.Lerp(overlayComp.CurrentBlur, 0f, fadeAmount);

if (overlayComp.BlurryMagnitude <= 0.01f && overlayComp.PulseAmplitude <= 0.01f && overlayComp.CurrentBlur <= 0.01f)
{
RemComp<SeizureOverlayComponent>(uid);
return;
}
}
else
{
var targetBlur = overlayComp.BlurryMagnitude;
var rampSpeed = targetBlur > overlayComp.CurrentBlur
? overlayComp.RampUpSpeed
: overlayComp.RampDownSpeed;

overlayComp.CurrentBlur = MathHelper.Lerp(overlayComp.CurrentBlur, targetBlur, frameTime * rampSpeed);
}

Dirty(uid, overlayComp);
}

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 | 🟠 Major | ⚡ Quick win

O SeizureOverlayComponent está sendo avançado duas vezes por frame.

UpdateSeizureComponents() já chama UpdateOverlayVisuals(...), e depois UpdateOverlayComponents() percorre o mesmo overlay no mesmo tick para incrementar PulseAccumulator, interpolar blur e aplicar fade outra vez. Isso acelera pulso/fade enquanto a crise está ativa.

  • Content.Shared/Traits/Assorted/SharedSeizureSystem.cs#L49-L116: deixe apenas um dos caminhos mutar PulseAccumulator, blur e fade; o outro deve ficar só com a responsabilidade que ainda faltar.
  • Content.Server/Traits/Assorted/SeizureSystem.cs#L66-L105: replique a mesma separação no override, senão o servidor continuará dobrando a atualização do overlay mesmo após corrigir a base.
📍 Affects 2 files
  • Content.Shared/Traits/Assorted/SharedSeizureSystem.cs#L49-L116 (this comment)
  • Content.Server/Traits/Assorted/SeizureSystem.cs#L66-L105
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Shared/Traits/Assorted/SharedSeizureSystem.cs` around lines 49 - 116,
SharedSeizureSystem: the overlay is being advanced twice per tick—remove
duplicate mutation by ensuring only one path updates pulse/fade/blur: in
UpdateSeizureComponents (which calls UpdateOverlayVisuals) stop mutating
SeizureOverlayComponent there (e.g., remove
overlayComp.PulseAccumulator+=frameTime and the fade/ramp logic) and leave
UpdateOverlayComponents responsible for PulseAccumulator, UpdateOverlayFade and
Dirty, or vice versa—pick one canonical updater and make the other only perform
non-visual responsibilities (e.g., movement recovery). Server SeizureSystem:
apply the same separation in the override in
Content.Server/Traits/Assorted/SeizureSystem.cs (lines 66-105) so the server
does not also advance the overlay twice—replicate the same change (make the
override call the single chosen visual updater or remove duplicate pulse/fade
mutations) so behavior is consistent between shared and server implementations.

Comment on lines +198 to +223
protected virtual void TransitionToSeizure(EntityUid uid, SeizureComponent seizure)
{
seizure.CurrentState = SeizureState.Seizure;
var actualDuration = Random.NextFloat(seizure.SeizureDuration.X, seizure.SeizureDuration.Y);
seizure.RemainingTime = actualDuration;
}

protected virtual void TransitionToRecovery(EntityUid uid, SeizureComponent seizure)
{
seizure.CurrentState = SeizureState.Recovery;
seizure.RemainingTime = seizure.RecoveryDuration;
}

protected virtual void TransitionToFading(EntityUid uid, SeizureComponent seizure)
{
if (TryComp<SeizureOverlayComponent>(uid, out var overlay))
{
overlay.IsFading = true;
overlay.FadeOutDuration = DefaultFadeOutDuration;
overlay.MovementSpeedMultiplier = seizure.MovementSpeedMultiplier;
overlay.HandleMovementRecovery = true;

seizure.CurrentState = SeizureState.Fading;
seizure.RemainingTime = overlay.FadeOutDuration;

Dirty(uid, overlay);

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 | 🟠 Major

Marcar SeizureComponent como dirty nas transições para sincronizar CurrentState/RemainingTime
Em SharedSeizureSystem.cs (linhas 198-223), TransitionToSeizure/TransitionToRecovery alteram seizure.CurrentState e seizure.RemainingTime (incluindo Random.NextFloat(...)), mas não chamam Dirty(uid, seizure)—diferente de TransitionToFading, que faz Dirty(uid, overlay). Isso pode impedir (ou atrasar) a replicação correta para clientes. [major fixes required] [data integrity]

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Shared/Traits/Assorted/SharedSeizureSystem.cs` around lines 198 -
223, TransitionToSeizure and TransitionToRecovery update SeizureComponent fields
but never mark the component dirty, so add a Dirty(uid, seizure) call after
setting seizure.CurrentState and seizure.RemainingTime in both
TransitionToSeizure and TransitionToRecovery to ensure state and RemainingTime
replicate to clients (use the same Dirty helper already used in
TransitionToFading); no other logic changes required.

Comment on lines +211 to +223
protected virtual void TransitionToFading(EntityUid uid, SeizureComponent seizure)
{
if (TryComp<SeizureOverlayComponent>(uid, out var overlay))
{
overlay.IsFading = true;
overlay.FadeOutDuration = DefaultFadeOutDuration;
overlay.MovementSpeedMultiplier = seizure.MovementSpeedMultiplier;
overlay.HandleMovementRecovery = true;

seizure.CurrentState = SeizureState.Fading;
seizure.RemainingTime = overlay.FadeOutDuration;

Dirty(uid, overlay);

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 | 🟠 Major | ⚡ Quick win

Fading mantém dois multiplicadores de movimento ativos ao mesmo tempo.

Ao entrar em Fading, o overlay começa a recuperar MovementSpeedMultiplier, mas OnRefreshMovementSpeed continua aplicando o último multiplicador do SeizureComponent sem checar o estado atual. Até o componente ser removido, o slowdown é aplicado pelos dois handlers. Zere o multiplicador do SeizureComponent na transição ou ignore SeizureState.Fading no refresh.

Also applies to: 277-287

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Shared/Traits/Assorted/SharedSeizureSystem.cs` around lines 211 -
223, When transitioning to Fading, avoid double-applying slowdown by clearing or
ignoring the sealed component multiplier: in TransitionToFading set
seizure.MovementSpeedMultiplier = 1.0 (or 0 if multipliers are applied
additively) immediately after setting seizure.CurrentState = SeizureState.Fading
so only the overlay's MovementSpeedMultiplier is used; alternatively, modify
OnRefreshMovementSpeed to early-return when seizure.CurrentState ==
SeizureState.Fading so it does not apply
SeizureComponent.MovementSpeedMultiplier while the overlay is fading. Update
whichever approach you choose (functions: TransitionToFading,
OnRefreshMovementSpeed; field: SeizureComponent.MovementSpeedMultiplier; enum:
SeizureState.Fading) and add a unit comment explaining the behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants