From b0796ebda4a9d913de08fe21ddba10d9c36bcc9a Mon Sep 17 00:00:00 2001 From: llupa <41073314+llupa@users.noreply.github.com> Date: Tue, 8 Sep 2026 20:38:16 +0200 Subject: [PATCH] fix(DB/SAI): Correct Frostmourne Cavern text and RP progression(#27522) --- .../rev_1788637875967431000.sql | 55 +++++++ e2e/README.md | 9 ++ .../frostmourne/frostmourne_e2e_test.go | 135 ++++++++++++++++++ 3 files changed, 199 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1788637875967431000.sql create mode 100644 e2e/suites/quests/frostmourne/frostmourne_e2e_test.go diff --git a/data/sql/updates/pending_db_world/rev_1788637875967431000.sql b/data/sql/updates/pending_db_world/rev_1788637875967431000.sql new file mode 100644 index 0000000000..682b389f2b --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1788637875967431000.sql @@ -0,0 +1,55 @@ +-- +-- Quest 12478 "Frostmourne Cavern": Arthas (27455) spoke his opening line before he was +-- visible, his replies overtook their cues, he repeated his last line, and Muradin (27480) +-- vanished on the spot instead of leaving. Retimed against the quest cinematic. +-- +DELETE FROM `smart_scripts` WHERE (`source_type` = 9) AND (`entryorguid` = 2745500); +INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES + (2745500,9,0,0,0,0,100,0,7000,7000,0,0,0,0,47,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Script9 - Set Visible'), + (2745500,9,1,0,0,0,100,0,0,0,0,0,0,0,118,0,0,0,0,0,0,15,190332,20,0,0,0,0,0,0,'Script9 - Set GO State'), + (2745500,9,2,0,0,0,100,0,0,0,0,0,0,0,12,27480,8,0,0,0,0,8,0,0,0,0,4816.75,-580.34,162.99,5.37,'Script9 - Summon Creature'), + (2745500,9,3,0,0,0,100,0,2000,2000,0,0,0,0,1,4,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Script9 - Talk'), + (2745500,9,4,0,0,0,100,0,40000,40000,0,0,0,0,1,5,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Script9 - Talk'), + (2745500,9,5,0,0,0,100,0,13000,13000,0,0,0,0,1,6,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Script9 - Talk'), + (2745500,9,6,0,0,0,100,0,8000,8000,0,0,0,0,1,7,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Script9 - Talk'), + (2745500,9,8,0,0,0,100,0,0,0,0,0,0,0,69,0,0,0,0,0,0,8,0,0,0,0,4820.36,-582.3,163.8,4,'Script9 - Move Point'), + (2745500,9,9,0,0,0,100,0,5000,5000,0,0,0,0,11,49824,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Script9 - Cast Spell'), + (2745500,9,10,0,0,0,100,0,11000,11000,0,0,0,0,118,1,0,0,0,0,0,15,190332,20,0,0,0,0,0,0,'Script9 - Set GO State'), + (2745500,9,11,0,0,0,100,0,0,0,0,0,0,0,71,0,1,36942,0,0,0,1,0,0,0,0,0,0,0,0,'Script9 - Set Equip'), + (2745500,9,12,0,0,0,100,0,500,500,0,0,0,0,5,25,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Script9 - Play Emote'), + (2745500,9,13,0,0,0,100,0,0,0,0,0,0,0,59,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Script9 - Set Run'), + (2745500,9,14,0,0,0,100,0,4000,4000,0,0,0,0,69,0,0,0,0,0,0,8,0,0,0,0,4809.08,-574.8,160.91,2.9,'Script9 - Move Point'), + (2745500,9,15,0,0,0,100,0,4000,4000,0,0,0,0,5,15,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Script9 - Play Emote'), + (2745500,9,16,0,0,0,100,0,4000,4000,0,0,0,0,69,0,0,0,0,0,0,8,0,0,0,0,4767,-567,163,3,'Script9 - Move Point'), + (2745500,9,17,0,0,0,100,0,7000,7000,0,0,0,0,41,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Script9 - Despawn'); + +DELETE FROM `smart_scripts` WHERE (`source_type` = 9) AND (`entryorguid` = 2748000); +INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES + (2748000,9,0,0,0,0,100,0,8000,8000,0,0,0,0,69,0,0,0,0,0,0,8,0,0,0,0,4818.63,-582.84,163.54,5.2,'Script9 - Move Point'), + (2748000,9,1,0,0,0,100,0,5000,5000,0,0,0,0,1,2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Script9 - Talk'), + (2748000,9,2,0,0,0,100,0,0,0,0,0,0,0,11,68442,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Script9 - Cast Spell'), + (2748000,9,3,0,0,0,100,0,16000,16000,0,0,0,0,28,68442,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Script9 - Remove Aura'), + (2748000,9,4,0,0,0,100,0,0,0,0,0,0,0,66,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0.7,'Script9 - Set Orientation'), + (2748000,9,5,0,0,0,100,0,5000,5000,0,0,0,0,5,5,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Script9 - Play Emote'), + (2748000,9,6,0,0,0,100,0,2000,2000,0,0,0,0,69,0,0,0,0,0,0,8,0,0,0,0,4816.75,-580.34,162.99,5.37,'Script9 - Move Point'), + (2748000,9,7,0,0,0,100,0,2000,2000,0,0,0,0,66,0,0,0,0,0,0,8,0,0,0,0,0,0,0,6.13,'Script9 - Set Orientation'), + (2748000,9,8,0,0,0,100,0,9000,9000,0,0,0,0,1,3,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Script9 - Talk'), + (2748000,9,9,0,0,0,100,0,32000,32000,0,0,0,0,40,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Muradin - On Script - Set Sheath Unarmed'), + (2748000,9,10,0,0,0,100,0,0,0,0,0,0,0,90,7,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Muradin - On Script - Set Stand State Dead'), + (2748000,9,11,0,0,0,100,0,13000,13000,0,0,0,0,91,7,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Muradin - On Script - Remove Stand State Dead'), + (2748000,9,12,0,0,0,100,0,500,500,0,0,0,0,17,64,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Muradin - On Script - Set Emote State 64'), + (2748000,9,13,0,0,0,100,0,5000,5000,0,0,0,0,1,4,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Muradin - On Script - Say Line 4'), + (2748000,9,14,0,0,0,100,0,3000,3000,0,0,0,0,1,5,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Muradin - On Script - Say Line 5'), + (2748000,9,15,0,0,0,100,0,4000,4000,0,0,0,0,17,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Muradin - On Script - Set Emote State 0'), + (2748000,9,16,0,0,0,100,0,1000,1000,0,0,0,0,11,49829,3,0,0,0,0,1,0,0,0,0,0,0,0,0,'Muradin - On Script - Cast \'Frostmourne Cavern Quest Credit\''), + (2748000,9,17,0,0,0,100,0,0,0,0,0,0,0,59,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Muradin - On Script - Set Run'), + (2748000,9,18,0,0,0,100,0,0,0,0,0,0,0,69,0,0,0,0,0,0,8,0,0,0,0,4809.08,-574.8,160.91,2.9,'Muradin - On Script - Move Point'), + (2748000,9,19,0,0,0,100,0,1000,1000,0,0,0,0,69,0,0,0,0,0,0,8,0,0,0,0,4767,-567,163,3,'Muradin - On Script - Move Point'), + (2748000,9,20,0,0,0,100,0,7000,7000,0,0,0,0,41,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,'Muradin - On Script - Despawn'); + +DELETE FROM `smart_scripts` WHERE (`source_type` = 9) AND (`entryorguid` = 2748001); +DELETE FROM `waypoints` WHERE (`entry` = 27480); + +-- Group 8 is a duplicate of group 7, unreferenced once the repeated line is gone. +DELETE FROM `creature_text_locale` WHERE (`CreatureID` = 27455) AND (`GroupID` = 8); +DELETE FROM `creature_text` WHERE (`CreatureID` = 27455) AND (`GroupID` = 8); diff --git a/e2e/README.md b/e2e/README.md index 1b46e93471..c50018f5e9 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -21,6 +21,14 @@ Authoring rules for new tests live in the harness: 1. Running AzerothCore **3.3.5a** authserver + worldserver. 2. MySQL with `acore_auth`, `acore_characters`, and `acore_world` (world DB is required for spawn cleanup and many fixtures). 3. Go **1.26+** and network reachability to auth (default `127.0.0.1:3724`). +4. `Warden.Enabled = 0` on the worldserver, until the harness bug below is fixed upstream. + Symptom: every login fails with `attempted to log in using invalid client OS ()`. + `WorldSocket::HandleAuthSession` reads `account.os` from the database and, when Warden is + active, rejects anything that is not `Win` or `OSX`. That column is written by the + *authserver* from the AUTH_LOGON_CHALLENGE FourCC, and AzerothGhost sends it leading-NUL + first (`client/auth.go`: `os := [4]byte{0, 'n', 'i', 'W'}`), so AC's `_os = os.data()` + builds an empty string and stores it. Docker stacks can set `AC_WARDEN_ENABLED=0` on the + worldserver service. Accounts are created by the harness (GM level 3, password `test`). Do not reuse real player accounts. @@ -150,6 +158,7 @@ If the scenario should stay as a regression, **move** it into `suites/` next to | social/trade | item+gold accept; cancel; walk-OOR TARGET_TO_FAR | P1 | covered | #25723 | | quests/lifecycle | STAY_ALIVE fail on death; status after save/relog | P1 | covered (`TestAC_26549_*`) | #26549 | | quests/escort | find spawned unit; follow-NPC despawns on logout | P2 | covered (`TestAC_24450_*`) | #24450 | +| quests/frostmourne | scrying-orb vision runs; Muradin leaves the cavern and despawns; quest 12478 COMPLETE | P2 | covered (`TestAC_25760_*`); dialogue order and duplicate line `blocked-harness` (no monster-say capture) | #25760 | | items/equip | visible-item slot after EquipEntry; additem; survives relog | P2 | covered | — | | protocol/session | pos; item/quest load; money save/relog | P1 | covered; GM vis persist `blocked-harness` (extra_flags after relog) | #25793 | | protocol/teleport | cross-map; named; GoCreatureID | P1 | covered | — | diff --git a/e2e/suites/quests/frostmourne/frostmourne_e2e_test.go b/e2e/suites/quests/frostmourne/frostmourne_e2e_test.go new file mode 100644 index 0000000000..f4b2bc2b8d --- /dev/null +++ b/e2e/suites/quests/frostmourne/frostmourne_e2e_test.go @@ -0,0 +1,135 @@ +//go:build e2e + +package frostmourne_test + +import ( + "fmt" + "testing" + "time" + + _ "github.com/go-sql-driver/mysql" + + "github.com/azerothcore/AzerothGhost/e2e/e2eharness" + "github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta" +) + +// Issue: https://github.com/azerothcore/azerothcore-wotlk/issues/25760 +// +// Quest 12478 "Frostmourne Cavern": Zelig's Scrying Orb (item 37933, spell 49817, +// spell_q12478_frostmourne_cavern) summons Prince Arthas (27455). His SmartAI +// actionlist 2745500 reveals him, summons Muradin (27480) and plays the vision; +// Muradin's list 2748000 ends by taking quest credit and leaving the cavern. +// +// The dialogue itself is monster say, which this harness cannot observe, so the +// line ordering and the removal of the duplicated line are not asserted here — see +// the gap noted in e2e/README.md. What this covers is the half with a +// protocol-visible oracle, and it is the half a single manual playthrough is worst +// at checking: +// +// - the vision starts at all, and Arthas summons Muradin; +// - Muradin leaves the cavern under his own movement instead of vanishing where +// he was knocked down, which is what the reworked ending added; +// - quest 12478 reaches COMPLETE, guarding the credit cast (spell 49829) that +// moved six and a half seconds later and now sits shortly before his despawn; +// - neither actor is leaked afterwards. Both are TEMPSUMMON_MANUAL_DESPAWN, so +// their only despawn is the last step of a long timed chain. +func TestAC_25760_FrostmourneCavernVisionCompletes(t *testing.T) { + meta.Begin(t, meta.TestMeta{ + Tags: []string{"med", "quests", "ai", "issue", "serial"}, + Runtime: "med", + Issue: 25760, + Category: "quests/frostmourne", + }) + + const ( + questFrostmourneCavern = uint32(12478) + spellScryingOrb = uint32(49817) + npcArthas = uint32(27455) + npcMuradin = uint32(27480) + mapNorthrend = uint32(571) + ) + const ( + // Arthas's summon position, hardcoded in spell_q12478_frostmourne_cavern, so the + // bot stands exactly where he materialises and distances below start there. + orbX, orbY, orbZ = float32(4821.3), float32(-580.14), float32(163.541) + // Muradin is knocked down beside the dais; his exit ends ~56 yd away, + // mostly south with a westward drift. + // "Still loaded but no longer beside us" is what distinguishes leaving + // from despawning on the spot. + nearRadius = float32(30) + farRadius = float32(250) + ) + + bot := e2eharness.NewSolo(t, e2eharness.ScenarioOpts{Prefix: "Frost", Level: 80}) + bot.AddQuest(t, questFrostmourneCavern) + bot.Teleport(t, orbX, orbY, orbZ, mapNorthrend) + + // Leftovers from an earlier run would satisfy the presence checks below. + bot.DespawnNearbyEntry(t, npcArthas, farRadius) + bot.DespawnNearbyEntry(t, npcMuradin, farRadius) + + // The orb's effect is SPELL_EFFECT_SEND_EVENT; casting it via GM avoids + // needing a real item-use packet. + bot.GM(t, fmt.Sprintf(".cast %d", spellScryingOrb)) + + arthas := bot.WaitUnit(t, npcArthas, 30*time.Second) + if arthas == 0 { + e2eharness.Preconditionf(t, "orb spell %d summoned no Arthas %d", spellScryingOrb, npcArthas) + } + muradin := bot.WaitUnit(t, npcMuradin, 30*time.Second) + if muradin == 0 { + e2eharness.Assertf(t, "Arthas %d never summoned Muradin %d", npcArthas, npcMuradin) + } + t.Logf("vision started arthas=0x%X muradin=0x%X", arthas, muradin) + + // Muradin is knocked down at ~86s, back on his feet at ~99s, speaks at ~104s + // and ~107s, then takes credit and runs out at ~112s, despawning at ~120s. + // He is only loaded-but-distant for about four seconds of that, so poll well + // inside the window: the loop body is two in-memory object-cache lookups and + // is not rate limited. + var ( + leftTheCavern bool + muradinGone bool + ) + deadline := time.Now().Add(160 * time.Second) + for time.Now().Before(deadline) { + near := bot.FindUnit(npcMuradin, nearRadius) + far := bot.FindUnit(npcMuradin, farRadius) + if far != 0 && near == 0 { + leftTheCavern = true + } + if far == 0 && leftTheCavern { + muradinGone = true + break + } + time.Sleep(400 * time.Millisecond) + } + + if !leftTheCavern { + e2eharness.Assertf(t, + "Muradin %d never moved away before despawning - the ending should walk him out of the cavern", + npcMuradin) + } + if !muradinGone { + e2eharness.Assertf(t, + "Muradin %d still loaded after the vision - TEMPSUMMON_MANUAL_DESPAWN summon was leaked", + npcMuradin) + } + + if bot.FindUnit(npcArthas, farRadius) != 0 { + e2eharness.Assertf(t, "Arthas %d still loaded after the vision", npcArthas) + } + + st, ok := bot.QuestStatusAfterSave(t, questFrostmourneCavern) + if !ok { + e2eharness.Assertf(t, "quest %d not in the character's log after the vision", questFrostmourneCavern) + } + if st != e2eharness.QuestStatusComplete { + e2eharness.Assertf(t, + "quest %d status %d, want %d COMPLETE - the credit cast did not land", + questFrostmourneCavern, st, e2eharness.QuestStatusComplete) + } + + bot.AssertWorldAlive(t) + t.Logf("PASS vision completed, Muradin left and despawned, quest %d COMPLETE", questFrostmourneCavern) +}