diff --git a/data/sql/updates/pending_db_world/rev_1788221547201003100.sql b/data/sql/updates/pending_db_world/rev_1788221547201003100.sql new file mode 100644 index 0000000000..3210e6f897 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1788221547201003100.sql @@ -0,0 +1,17 @@ +-- +UPDATE `quest_template_addon` SET `ExclusiveGroup` = 0 WHERE `ID` IN (6541, 6542); + +DELETE FROM `conditions` +WHERE `SourceTypeOrReferenceId` = 19 AND `SourceGroup` = 0 AND `SourceEntry` = 6541 AND `SourceId` = 0 + AND `ElseGroup` = 0 AND `ConditionTypeOrReference` = 47 AND `ConditionTarget` = 0 + AND `ConditionValue1` = 6542 AND `ConditionValue2` = 65 AND `ConditionValue3` = 0; +DELETE FROM `conditions` +WHERE `SourceTypeOrReferenceId` = 19 AND `SourceGroup` = 0 AND `SourceEntry` = 6542 AND `SourceId` = 0 + AND `ElseGroup` = 0 AND `ConditionTypeOrReference` = 47 AND `ConditionTarget` = 0 + AND `ConditionValue1` = 6541 AND `ConditionValue2` = 65 AND `ConditionValue3` = 0; +/* DELETE safety: exact full condition primary keys removed above. */ +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, +`ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, +`NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(19, 0, 6541, 0, 0, 47, 0, 6542, 65, 0, 0, 0, 0, '', 'Quest 6541 available while quest 6542 is not taken or rewarded'), +(19, 0, 6542, 0, 0, 47, 0, 6541, 65, 0, 0, 0, 0, '', 'Quest 6542 available while quest 6541 is not taken or rewarded'); diff --git a/e2e/README.md b/e2e/README.md index 334890dcf9..c5ca9c7fcb 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -153,7 +153,7 @@ go test -tags=e2e ./local/... -count=1 -v -timeout 30m -parallel 1 | social/group | form / leave / leader / loot method / disband | P2 | covered | — | | social/loot | need/greed / master loot; below-half kill; a roll that ends on the timer names the roll in its terminating packet, and the member who never voted gets one PASS | P1 | covered (`TestAC_26862_*`, `TestAC_27299_*`); chest mid-roll `blocked-harness` (GO 194821 UseGameObject); pass-on-loot delete `blocked-harness` (item-survive after ALL_PASSED) | #26894 #26862 #27299 #22000 | | 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/lifecycle | STAY_ALIVE fail on death; status after save/relog; questgiver accept gating | P1 | covered (`TestAC_26549_*`, `TestAC_27417_*`) | #26549 #27417 | | 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 | | quests/objectives | a mob that drops a quest item advertises it, so the client shows the objective on hover (`creature_questitem` -> `SMSG_CREATURE_QUERY_RESPONSE.questItems`) | P2 | covered (`TestAC_27553_*`), decoding the response through a raw packet hook since the harness has no dispatch case for it | #27553 | diff --git a/e2e/suites/quests/lifecycle/report_to_kadrak_e2e_test.go b/e2e/suites/quests/lifecycle/report_to_kadrak_e2e_test.go new file mode 100644 index 0000000000..56eccd3a7f --- /dev/null +++ b/e2e/suites/quests/lifecycle/report_to_kadrak_e2e_test.go @@ -0,0 +1,137 @@ +//go:build e2e + +package lifecycle_test + +import ( + "testing" + "time" + + _ "github.com/go-sql-driver/mysql" + + "github.com/azerothcore/AzerothGhost/e2e/e2eharness" + "github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta" +) + +const ( + questReportToKadrakBarrens uint32 = 6541 + questReportToKadrakStonetalon uint32 = 6542 + creatureThork uint32 = 3429 + creatureDarnTalongrip uint32 = 11821 + creatureKadrak uint32 = 8582 +) + +// Issue: https://github.com/azerothcore/azerothcore-wotlk/issues/27417 +func TestAC_27417_ReportToKadrakGating(t *testing.T) { + meta.Begin(t, meta.TestMeta{ + Tags: []string{"short", "quests", "issue", "serial"}, + Runtime: "short", + Issue: 27417, + Category: "quests/lifecycle", + }) + + tests := []struct { + name string + prefix string + firstQuest uint32 + firstStarter uint32 + secondQuest uint32 + secondStarter uint32 + }{ + { + "BarrensThenStonetalon", "KadrakA", + questReportToKadrakBarrens, creatureThork, + questReportToKadrakStonetalon, creatureDarnTalongrip, + }, + { + "StonetalonThenBarrens", "KadrakB", + questReportToKadrakStonetalon, creatureDarnTalongrip, + questReportToKadrakBarrens, creatureThork, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + bot := e2eharness.NewSolo(t, e2eharness.ScenarioOpts{ + Prefix: tc.prefix, + Race: e2eharness.RaceOrc, + Class: e2eharness.ClassWarrior, + Level: 17, + }) + bot.TeleportPad(t, e2eharness.PackagePad(t)) + + firstStarterGUID := bot.Spawn(t, tc.firstStarter, 10*time.Second) + secondStarterGUID := bot.Spawn(t, tc.secondStarter, 10*time.Second) + kadrakGUID := bot.Spawn(t, creatureKadrak, 10*time.Second) + bot.GM(t, ".gm off") + bot.FlushWorld(t) + + acceptReportQuest(t, bot, firstStarterGUID, tc.firstQuest, true) + acceptReportQuest(t, bot, secondStarterGUID, tc.secondQuest, false) + rewardReportQuest(t, bot, kadrakGUID, tc.firstQuest) + acceptReportQuest(t, bot, secondStarterGUID, tc.secondQuest, true) + rewardReportQuest(t, bot, kadrakGUID, tc.secondQuest) + t.Logf("PASS quests %d then %d completed sequentially", tc.firstQuest, tc.secondQuest) + }) + } +} + +func acceptReportQuest( + t *testing.T, + bot *e2eharness.ScenarioBot, + starterGUID uint64, + questID uint32, + wantAccepted bool, +) { + t.Helper() + if err := bot.World.QuestgiverHello(starterGUID); err != nil { + e2eharness.HarnessFailf(t, "questgiver hello quest=%d: %v", questID, err) + } + if err := bot.World.QuestgiverAcceptQuest(starterGUID, questID); err != nil { + e2eharness.HarnessFailf(t, "accept quest=%d: %v", questID, err) + } + bot.FlushWorld(t) + status, accepted := bot.QuestStatusAfterSave(t, questID) + + if !wantAccepted { + if accepted { + e2eharness.ConfirmedBugf(t, 27417, "quest %d accepted with sibling active (status=%s)", + questID, e2eharness.QuestStatusName(status)) + } + return + } + + if !accepted { + e2eharness.ConfirmedBugf(t, 27417, "quest %d was not accepted", questID) + } + if status != e2eharness.QuestStatusIncomplete && status != e2eharness.QuestStatusComplete { + e2eharness.Assertf(t, "quest %d accepted with unexpected status=%s", questID, + e2eharness.QuestStatusName(status)) + } +} + +func rewardReportQuest(t *testing.T, bot *e2eharness.ScenarioBot, kadrakGUID uint64, questID uint32) { + t.Helper() + if err := bot.World.QuestgiverHello(kadrakGUID); err != nil { + e2eharness.HarnessFailf(t, "Kadrak hello quest=%d: %v", questID, err) + } + if err := bot.World.QuestgiverCompleteQuest(kadrakGUID, questID); err != nil { + e2eharness.HarnessFailf(t, "complete quest=%d: %v", questID, err) + } + bot.FlushWorld(t) + if err := bot.World.QuestgiverChooseReward(kadrakGUID, questID, 0); err != nil { + e2eharness.HarnessFailf(t, "choose reward quest=%d: %v", questID, err) + } + bot.FlushWorld(t) + bot.Save(t) + + var rewarded int + if err := bot.CharDB.QueryRow( + "SELECT COUNT(*) FROM character_queststatus_rewarded WHERE guid = ? AND quest = ?", + bot.GUID, questID, + ).Scan(&rewarded); err != nil { + e2eharness.HarnessFailf(t, "query rewarded quest=%d: %v", questID, err) + } + if rewarded != 1 { + e2eharness.ConfirmedBugf(t, 27417, "quest %d was not rewarded", questID) + } +}