Skip to content

fix(Scripts/VaultOfArchavon): reliably stone bosses and kick players before Wintergrasp#26428

Closed
Chinoske wants to merge 3 commits into
azerothcore:masterfrom
Chinoske:fix/voa-wintergrasp-timer-threshold-26411
Closed

fix(Scripts/VaultOfArchavon): reliably stone bosses and kick players before Wintergrasp#26428
Chinoske wants to merge 3 commits into
azerothcore:masterfrom
Chinoske:fix/voa-wintergrasp-timer-threshold-26411

Conversation

@Chinoske

@Chinoske Chinoske commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Issue title claims the pre-Wintergrasp VoA mechanics (bosses turn to stone, players kicked out) aren't implemented — they actually are (SPELL_STONED_AURA = 63080, Wintergrasp.KickVoAPlayers config, defaults to true), but they don't reliably fire.
  • Root cause: the checks used 60-second-wide time windows (e.g. timer <= 10min && timer >= 9min) evaluated on a fixed 60-second poll. If the Wintergrasp timer moves through a window faster than the next poll (a GM-set jump via .bf timer, but this can in principle also happen from ordinary phase misalignment), the window is skipped entirely and none of the mechanics fire for that whole pre-war cycle — matching the issue's repro steps (.bf timer 15m10m5m10s with only a few seconds between each).
  • Fix: replaced the window checks with threshold-crossing checks gated by one-shot flags (warned15, stoned, warned2, kicked), polled every 5s. Each stage fires exactly once as soon as the timer is observed at or below its threshold, regardless of how it got there — no window to miss.
  • Also handles the edge case where the timer jumps so far that Wintergrasp's war has already started by the next poll: an active war is treated as "timer at 0", so any stage not yet fired gets fired retroactively in the same tick, instead of silently doing nothing because IsWarTime() is already true.
  • Flags reset for a fresh countdown once the timer is next observed back above 15 minutes (previous war cycle fully concluded).

Test plan

  • Verified Battlefield::Timer semantics (Battlefield.cpp): counts down continuously in both pre-war and war states; .bf timer (cs_bf.cpp) calls SetTimer() directly on the same Timer field my code reads
  • codestyle-cpp.py passes clean
  • Applied to a local test server and reproduced the original bug (stepped .bf timer calls worked, but a direct 15m→10s jump did nothing) with the first version of the fix, then fixed the war-already-started edge case and confirmed both the stepped case and the direct-jump case now correctly fire the 15-min/2-min warnings, boss stoning, and player kick-to-homebind

Closes #26411

🤖 Generated with Claude Code

…before Wintergrasp

The pre-war checks (15/10/2/1 minute warnings, stoning, kick-out) used
60-second-wide time WINDOWS evaluated on a fixed 60-second poll. Any time
the Wintergrasp timer changed faster than that poll interval (a GM-set
jump, but also just unlucky phase alignment under normal countdown), the
narrow window could be skipped entirely and none of the mechanics would
fire for that whole pre-war cycle.

Replaced with threshold-crossing checks gated by one-shot flags, polled
every 5s: each stage fires exactly once as soon as the timer is observed
at or below its threshold, regardless of how it got there. Also treats an
already-active war as "timer at 0" so a jump straight past every
threshold still fires the missed stages retroactively instead of bailing
out because IsWarTime() is already true. Flags reset once the timer is
back above 15 minutes (previous cycle fully concluded).

Closes azerothcore#26411
@github-actions github-actions Bot added Script Refers to C++ Scripts for the Core file-cpp Used to trigger the matrix build labels Jul 2, 2026
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The reset countdown logic in the Vault of Archavon instance script was rewritten to use one-shot boolean flags tracking warning and kick states, replacing minute-based timer checks with a 5-second gated countdown that emits warnings, applies a stoning aura, and kicks players/evades bosses as Wintergrasp approaches.

Changes

Wintergrasp countdown behavior

Layer / File(s) Summary
Countdown state flags and Update logic rewrite
src/server/scripts/Northrend/VaultOfArchavon/instance_vault_of_archavon.cpp
Adds warned15, warned2, kicked private flags initialized in Initialize(), and rewrites Update() to gate on a 5s interval, check a config flag, compute a countdown timer, reset flags when far from war, and trigger ordered one-shot emotes, stoning aura, and player kick/boss evade actions as the timer counts down.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested labels: Ready to be Reviewed

Suggested reviewers: Kitzunu, sogladev

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is informative but missing most required template sections, including Changes Proposed, SOURCE, Tests Performed, and Known Issues. Fill in the repository template sections and checkboxes, including change categories, validation source, testing steps, and known issues/TODOs.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the Vault of Archavon Wintergrasp fix.
Linked Issues check ✅ Passed The fix matches #26411 by stoning bosses before Wintergrasp and kicking players out when war starts, while avoiding missed timer windows.
Out of Scope Changes check ✅ Passed The changes stay within Vault of Archavon Wintergrasp timing logic and do not introduce unrelated modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@src/server/scripts/Northrend/VaultOfArchavon/instance_vault_of_archavon.cpp`:
- Around line 147-150: The TeleportTo call in the instance_vault_of_archavon.cpp
player loop exceeds the 120-column limit. Reformat the logic around
Map::PlayerList, the const_iterator loop, and player->TeleportTo so the
arguments are wrapped onto multiple lines while preserving the same behavior and
keeping the line length within the project’s codestyle limit.
- Around line 96-103: The reset logic in `Update()` currently depends on
`bf->GetTimer()` being greater than 15 minutes, which breaks when
`NoBattleTimer` is configured below that threshold. Update the
`instance_vault_of_archavon` state handling to reset `warned15`, `stoned`,
`warned2`, and `kicked` on the war-active to war-inactive transition instead of
an absolute timer check, and use the existing `kicked` state plus a new private
`wasWarTime` member to detect that edge reliably.
🪄 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.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7cc1716b-3d0b-4f37-a8e5-09d4847ba04d

📥 Commits

Reviewing files that changed from the base of the PR and between a69d477 and 61bad87.

📒 Files selected for processing (1)
  • src/server/scripts/Northrend/VaultOfArchavon/instance_vault_of_archavon.cpp

Comment on lines +96 to +103
if (!warTime && timer > 15 * MINUTE * IN_MILLISECONDS)
{
checkTimer -= 60000; // one minute
if (!sWorld->getBoolConfig(CONFIG_WINTERGRASP_KICK_VOA_PLAYERS))
return;
if (Battlefield* bf = sBattlefieldMgr->GetBattlefieldByBattleId(BATTLEFIELD_BATTLEID_WG))
{
if (!bf->IsWarTime())
{
if (bf->GetTimer() <= (16 * MINUTE * IN_MILLISECONDS) && bf->GetTimer() >= (15 * MINUTE * IN_MILLISECONDS))
{
Map::PlayerList const& PlayerList = instance->GetPlayers();
if (!PlayerList.IsEmpty())
for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
if (Player* player = i->GetSource())
player->TextEmote("This instance will reset in 15 minutes.", nullptr, true);
}
else if (bf->GetTimer() <= (10 * MINUTE * IN_MILLISECONDS) && bf->GetTimer() >= (9 * MINUTE * IN_MILLISECONDS))
{
for (uint8 i = 0; i < MAX_ENCOUNTER; ++i)
if (Creature* cr = instance->GetCreature(bossGUIDs[i]))
if (!cr->IsInCombat())
{
cr->RemoveAllAuras();
if (Aura* aur = cr->AddAura(SPELL_STONED_AURA, cr))
{
aur->SetMaxDuration(60 * MINUTE * IN_MILLISECONDS);
aur->SetDuration(60 * MINUTE * IN_MILLISECONDS);
}
}

stoned = true;
}
else if (bf->GetTimer() <= (2 * MINUTE * IN_MILLISECONDS) && bf->GetTimer() > (MINUTE * IN_MILLISECONDS))
{
Map::PlayerList const& PlayerList = instance->GetPlayers();
if (!PlayerList.IsEmpty())
for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
if (Player* player = i->GetSource())
player->TextEmote("This instance is about to reset. Prepare to be removed.", nullptr, true);
}
else if (bf->GetTimer() <= MINUTE * IN_MILLISECONDS)
warned15 = false;
stoned = false;
warned2 = false;
kicked = false;
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reset relies on GetTimer() > 15 min, which can get stuck if NoBattleTimer is configured below 15 minutes.

After a war ends, timer becomes bf->GetTimer() again, which starts counting down from Wintergrasp.NoBattleTimer (default 150 min, safely above 15). If an operator configures NoBattleTimer to less than 15 minutes, this reset condition is never true again after the first cycle, so warned15/stoned/warned2/kicked stay true forever and the whole mechanic (warnings, stoning, kicking) silently stops firing for every subsequent war. Resetting on the war-active→inactive transition instead of on an absolute timer threshold would be robust to any config value.

🐛 Proposed fix: reset on war-end transition instead of absolute timer value
-        void Initialize() override
+        void Initialize() override
         {
             SetHeaders(DataHeader);
             memset(&m_auiEncounter, 0, sizeof(m_auiEncounter));
 
             ArchavonDeath = 0;
             EmalonDeath = 0;
             KoralonDeath = 0;
             checkTimer = 0;
             stoned = false;
             warned15 = false;
             warned2 = false;
             kicked = false;
+            wasWarTime = false;
         }
             bool const warTime = bf->IsWarTime();
             uint32 const timer = warTime ? 0 : bf->GetTimer();
 
-            // Far from the next war again: previous cycle is fully over, reset for a fresh countdown.
-            if (!warTime && timer > 15 * MINUTE * IN_MILLISECONDS)
+            // War just ended: begin a fresh countdown cycle regardless of the configured NoBattleTimer value.
+            if (wasWarTime && !warTime)
             {
                 warned15 = false;
                 stoned = false;
                 warned2 = false;
                 kicked = false;
-                return;
             }
+            wasWarTime = warTime;
+
+            if (!warTime && timer > 15 * MINUTE * IN_MILLISECONDS)
+                return;

(with a matching bool wasWarTime; private member added alongside kicked)

🤖 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 `@src/server/scripts/Northrend/VaultOfArchavon/instance_vault_of_archavon.cpp`
around lines 96 - 103, The reset logic in `Update()` currently depends on
`bf->GetTimer()` being greater than 15 minutes, which breaks when
`NoBattleTimer` is configured below that threshold. Update the
`instance_vault_of_archavon` state handling to reset `warned15`, `stoned`,
`warned2`, and `kicked` on the war-active to war-inactive transition instead of
an absolute timer check, and use the existing `kicked` state plus a new private
`wasWarTime` member to detect that edge reliably.

Comment on lines +147 to +150
Map::PlayerList const& PlayerList = instance->GetPlayers();
for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
if (Player* player = i->GetSource())
player->TeleportTo(player->m_homebindMapId, player->m_homebindX, player->m_homebindY, player->m_homebindZ, player->GetOrientation());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Line 150 exceeds the 120-column limit.

With indentation this line runs to roughly 157 columns, well past the project's max-120-column hard rule (CI-enforced via the codestyle lint script per repository conventions).

🎨 Proposed fix: wrap the call
                 Map::PlayerList const& PlayerList = instance->GetPlayers();
                 for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
                     if (Player* player = i->GetSource())
-                        player->TeleportTo(player->m_homebindMapId, player->m_homebindX, player->m_homebindY, player->m_homebindZ, player->GetOrientation());
+                        player->TeleportTo(player->m_homebindMapId, player->m_homebindX, player->m_homebindY,
+                            player->m_homebindZ, player->GetOrientation());

As per coding guidelines, "Maximum line length is 120 columns".

📝 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
Map::PlayerList const& PlayerList = instance->GetPlayers();
for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
if (Player* player = i->GetSource())
player->TeleportTo(player->m_homebindMapId, player->m_homebindX, player->m_homebindY, player->m_homebindZ, player->GetOrientation());
Map::PlayerList const& PlayerList = instance->GetPlayers();
for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
if (Player* player = i->GetSource())
player->TeleportTo(player->m_homebindMapId, player->m_homebindX, player->m_homebindY,
player->m_homebindZ, player->GetOrientation());
🤖 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 `@src/server/scripts/Northrend/VaultOfArchavon/instance_vault_of_archavon.cpp`
around lines 147 - 150, The TeleportTo call in the
instance_vault_of_archavon.cpp player loop exceeds the 120-column limit.
Reformat the logic around Map::PlayerList, the const_iterator loop, and
player->TeleportTo so the arguments are wrapped onto multiple lines while
preserving the same behavior and keeping the line length within the project’s
codestyle limit.

Source: Coding guidelines

Update()'s flag reset cleared the stoned bool but never removed the
actual aura from bosses, relying on OnPlayerEnter to do that cleanup.
Since the timer-based reset fires within 5s (well before a player
typically walks back in), OnPlayerEnter would see stoned already false
and skip the cleanup, leaving bosses stuck with the aura for up to its
60-minute duration. Now cleared in the same place the flag resets.
@Chinoske

Chinoske commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Pushed a follow-up: the timer-based flag reset cleared stoned but never removed the actual aura from bosses (only OnPlayerEnter did that, and it fires too late since the timer reset happens within 5s of .bf stop/war ending). Now the aura is cleaned up in the same place the flag resets. Confirmed on a local test server: .bf start → bosses get Stoned → .bf stop → aura clears within ~5-10s without needing to re-enter the instance.

- Reset the countdown flags on the war-active-to-inactive transition
  (tracked via a new wasWarTime bool) instead of relying on the timer
  being above an absolute 15-minute threshold, which would never be true
  again if Wintergrasp.NoBattleTimer is configured below 15 minutes.
- Wrap the TeleportTo call, which exceeded the 120-column limit.
@Chinoske

Chinoske commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Addressed both CodeRabbit findings:

  • Reset now triggers on the war-active→inactive transition (new wasWarTime bool) instead of relying on GetTimer() > 15min, which would never be true again if Wintergrasp.NoBattleTimer is configured below 15 minutes — good catch, that was a real latent bug.
  • Wrapped the TeleportTo call that exceeded 120 columns.

@Chinoske Chinoske closed this Jul 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

file-cpp Used to trigger the matrix build Script Refers to C++ Scripts for the Core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Vault of Archavon] Mechanics close to Wintergrasp start not implemented

1 participant