Skip to content

fix(fight): restore stealth flags after a fight by recalculation - #4092

Open
bylins wants to merge 1 commit into
masterfrom
fix/stealth-flags-after-fight
Open

bylins wants to merge 1 commit into
masterfrom
fix/stealth-flags-after-fight

Conversation

@bylins

@bylins bylins commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

Закрывает #4091.

Как было

Флаги незаметности в бою снимает сам affect_total по GET_ENEMY (affect_data.cpp:1340), а возвращала их после боя защёлка:

// SetFighting
ch->battle_affects.clear();
if (AFF_FLAGGED(ch, EAffect::kInvisible)) {
    ch->battle_affects.set(kEafInvisible);
}

// stop_fighting
if (ch->battle_affects.get(kEafInvisible)) {
    AFF_FLAGS(ch).set(EAffect::kInvisible);
}

Защёлка запоминала только «на входе в бой инвиз был» и ставила флаг обратно безусловно, без оглядки на источник. Из трёх случаев верно работал один:

источник незаметности что было
вещь, надетая до боя возвращается верно
заклинание возвращается зря: Appear снёс аффект на первом уроне, и персонаж выходит из боя невидимым без источника — до первого попутного пересчёта
надето или наложено посреди боя не возвращается вовсе: защёлку взводит только вход в бой, живой аффект остаётся с опущенным флагом до следующего affect_total, то есть до перенадевания вещи

Периодического пересчёта, который раньше это подчищал, в хербите давно нет, и в stop_fighting его тоже не было — я проверил все 67 вызовов affect_total: это события (надеть/снять, еда, set, наложение аффекта, смерть на арене), ни одного в раунде боя.

Как стало

Флаги возвращает тот же пересчёт, который их подавлял:

ch->battle_affects.clear();
if (!ch->IsNpc() || IsCharmice(ch)) {
    affect_total(ch);
}

Он поднимает флаги из живых источников — вещь и живой аффект вернёт, снятое заклинанием не вернёт, потому что узла уже нет. GET_ENEMY на этот момент уже обнулён выше, так что подавлять нечего.

Пересчёт только для игрока и чармиса. Обычному мобу он не нужен: незаметность ему заново поднимает мобий пульс в mobile_activity по ENpcFlag::kInvis, причём уже после проверки if (ch->GetEnemy() || ch->get_wait() > 0) continue;, то есть вне боя. Чармису этого мало — пульс вернёт только прототипный флаг, а наложенную или надетую на него незаметность вернуть больше нечем.

Это не возврат периодического пересчёта: один вызов на выход из боя, не чаще, чем сейчас на надевание вещи.

Условие тут не для красоты. Исторически полный пересчёт звался из reset_affects — через неё проходит каждый умирающий, — и на АОЕ это означало столько пересчётов, сколько трупов за раунд. Оттуда его и убрали. Условие !ch->IsNpc() || IsCharmice(ch) держит нас в стороне от того же самого: массовая смерть мобов до affect_total не доходит вовсе, пересчёт получают только игрок и его чармисы. Убрать условие — вернуться в ту же историю через другую дверь.

За шагом боя есть чем следить: гистограмма heartbeat.step.duration с меткой step="Violence performing" (в Prometheus heartbeat_step_duration_*). База до правки — 0.639 мс в среднем за полчаса. Квантили по ней считать бесполезно, границы бакетов заданы в секундах; смотреть надо среднее и heartbeat_missed_pulses_total.

Защёлка kEafInvisible стала не нужна — бит убран, комментарий про максимум плоскости поправлен на kEafFirstPoison.

Проверка

Сборка -Dbuild_profile=release -Dbuild_tests=true без предупреждений, ./tests/tests — 796 passed, 3 skipped (те же, что и до правки).

🤖 Generated with Claude Code

Флаги незаметности в бою снимает сам affect_total по GET_ENEMY, а возвращала
их после боя защёлка kEafInvisible: SetFighting запоминал "на входе в бой
инвиз был", stop_fighting ставил флаг обратно. Безусловно, без оглядки на
источник, отчего из трёх случаев правильно работал один.

Инвиз с вещи, надетой до боя, возвращался верно. Инвиз от заклинания
возвращался тоже -- хотя Appear снёс аффект на первом уроне, и персонаж
выходил из боя невидимым без источника, до первого попутного пересчёта.
А надетый или наложенный посреди боя не возвращался вовсе: защёлку взводит
только вход в бой, так что живой аффект оставался с опущенным флагом до
следующего affect_total -- то есть до перенадевания вещи.

Теперь флаги возвращает тот же пересчёт, который их подавлял: он поднимает
их из живых источников и не возвращает снятое заклинанием, потому что узла
уже нет. GET_ENEMY на этот момент пуст, подавлять нечего.

Пересчёт зовём для игрока и для чармиса. Обычному мобу он не нужен: его
незаметность заново поднимает мобий пульс в mobile_activity по
ENpcFlag::kInvis -- и делает это уже после проверки на GetEnemy, то есть
вне боя. Чармису этого мало: пульс вернёт только прототипный флаг, а
наложенную или надетую на него незаметность вернуть больше нечем.

Защёлка kEafInvisible больше не нужна, бит убран.

Closes #4091

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant