feat(Core/Quest): Add BreadcrumbForQuestId field to quest_template_addon (#24079)

Co-authored-by: blinkysc <blinkysc@users.noreply.github.com>
Co-authored-by: sogladev <sogladev@gmail.com>
Co-authored-by: Gultask <100873791+Gultask@users.noreply.github.com>
Co-authored-by: Ryan Turner <16946913+TheSCREWEDSoftware@users.noreply.github.com>
This commit is contained in:
blinkysc
2026-01-29 06:24:03 -06:00
committed by GitHub
parent 6043acac0a
commit b79ef2bedf
7 changed files with 396 additions and 16 deletions

View File

@@ -1446,6 +1446,7 @@ public:
bool SatisfyQuestConditions(Quest const* qInfo, bool msg);
bool SatisfyQuestTimed(Quest const* qInfo, bool msg) const;
bool SatisfyQuestExclusiveGroup(Quest const* qInfo, bool msg) const;
bool SatisfyQuestBreadcrumb(Quest const* qInfo, bool msg) const;
bool SatisfyQuestNextChain(Quest const* qInfo, bool msg) const;
bool SatisfyQuestPrevChain(Quest const* qInfo, bool msg) const;
bool SatisfyQuestDay(Quest const* qInfo, bool msg) const;

View File

@@ -239,7 +239,7 @@ bool Player::CanSeeStartQuest(Quest const* quest)
if (!sDisableMgr->IsDisabledFor(DISABLE_TYPE_QUEST, quest->GetQuestId(), this) && SatisfyQuestClass(quest, false) && SatisfyQuestRace(quest, false) &&
SatisfyQuestSkill(quest, false) && SatisfyQuestExclusiveGroup(quest, false) && SatisfyQuestReputation(quest, false) &&
SatisfyQuestPreviousQuest(quest, false) && SatisfyQuestNextChain(quest, false) &&
SatisfyQuestPrevChain(quest, false) && SatisfyQuestDay(quest, false) && SatisfyQuestWeek(quest, false) &&
SatisfyQuestPrevChain(quest, false) && SatisfyQuestBreadcrumb(quest, false) && SatisfyQuestDay(quest, false) && SatisfyQuestWeek(quest, false) &&
SatisfyQuestMonth(quest, false) && SatisfyQuestSeasonal(quest, false))
{
return GetLevel() + sWorld->getIntConfig(CONFIG_QUEST_HIGH_LEVEL_HIDE_DIFF) >= quest->GetMinLevel();
@@ -256,6 +256,7 @@ bool Player::CanTakeQuest(Quest const* quest, bool msg)
&& SatisfyQuestSkill(quest, msg) && SatisfyQuestReputation(quest, msg)
&& SatisfyQuestPreviousQuest(quest, msg) && SatisfyQuestTimed(quest, msg)
&& SatisfyQuestNextChain(quest, msg) && SatisfyQuestPrevChain(quest, msg)
&& SatisfyQuestBreadcrumb(quest, msg)
&& SatisfyQuestDay(quest, msg) && SatisfyQuestWeek(quest, msg)
&& SatisfyQuestMonth(quest, msg) && SatisfyQuestSeasonal(quest, msg)
&& SatisfyQuestConditions(quest, msg);
@@ -1217,6 +1218,39 @@ bool Player::SatisfyQuestExclusiveGroup(Quest const* qInfo, bool msg) const
return true;
}
bool Player::SatisfyQuestBreadcrumb(Quest const* qInfo, bool msg) const
{
uint32 breadcrumbForQuestId = qInfo->GetBreadcrumbForQuestId();
if (breadcrumbForQuestId)
{
QuestStatus status = GetQuestStatus(breadcrumbForQuestId);
if (status != QUEST_STATUS_NONE || IsQuestRewarded(breadcrumbForQuestId))
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
return false;
}
}
if (std::vector<uint32> const* breadcrumbs = sObjectMgr->GetBreadcrumbsForQuest(qInfo->GetQuestId()))
{
for (uint32 breadcrumbQuestId : *breadcrumbs)
{
QuestStatus status = GetQuestStatus(breadcrumbQuestId);
if (status == QUEST_STATUS_INCOMPLETE || status == QUEST_STATUS_COMPLETE || status == QUEST_STATUS_FAILED)
{
if (msg)
SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
return false;
}
}
}
return true;
}
bool Player::SatisfyQuestNextChain(Quest const* qInfo, bool msg) const
{
uint32 nextQuest = qInfo->GetNextQuestInChain();

View File

@@ -4738,10 +4738,10 @@ void ObjectMgr::LoadQuests()
}
// Load `quest_template_addon`
// 0 1 2 3 4 5 6 7 8
result = WorldDatabase.Query("SELECT ID, MaxLevel, AllowableClasses, SourceSpellID, PrevQuestID, NextQuestID, ExclusiveGroup, RewardMailTemplateID, RewardMailDelay, "
//9 10 11 12 13 14 15 16 17
"RequiredSkillID, RequiredSkillPoints, RequiredMinRepFaction, RequiredMaxRepFaction, RequiredMinRepValue, RequiredMaxRepValue, ProvidedItemCount, RewardMailSenderEntry, SpecialFlags FROM quest_template_addon LEFT JOIN quest_mail_sender ON Id=QuestId");
// 0 1 2 3 4 5 6 7 8
result = WorldDatabase.Query("SELECT ID, MaxLevel, AllowableClasses, SourceSpellID, PrevQuestID, NextQuestID, ExclusiveGroup, BreadcrumbForQuestId, RewardMailTemplateID, "
//9 10 11 12 13 14 15 16 17 18
"RewardMailDelay, RequiredSkillID, RequiredSkillPoints, RequiredMinRepFaction, RequiredMaxRepFaction, RequiredMinRepValue, RequiredMaxRepValue, ProvidedItemCount, RewardMailSenderEntry, SpecialFlags FROM quest_template_addon LEFT JOIN quest_mail_sender ON Id=QuestId");
if (!result)
{
@@ -5318,12 +5318,54 @@ void ObjectMgr::LoadQuests()
if (qinfo->ExclusiveGroup)
mExclusiveQuestGroups.insert(std::pair<int32, uint32>(qinfo->ExclusiveGroup, qinfo->GetQuestId()));
if (uint32 breadcrumbForQuestId = qinfo->GetBreadcrumbForQuestId())
{
if (_questTemplates.find(breadcrumbForQuestId) == _questTemplates.end())
LOG_ERROR("sql.sql", "Quest {} has BreadcrumbForQuestId {}, but no such quest exists", qinfo->GetQuestId(), breadcrumbForQuestId);
else
_breadcrumbsForQuest[breadcrumbForQuestId].push_back(qinfo->GetQuestId());
if (qinfo->GetNextQuestId())
LOG_ERROR("sql.sql", "Quest {} is a breadcrumb quest but also has NextQuestID {} set", qinfo->GetQuestId(), qinfo->GetNextQuestId());
if (qinfo->GetExclusiveGroup())
LOG_ERROR("sql.sql", "Quest {} is a breadcrumb quest but also has ExclusiveGroup {} set", qinfo->GetQuestId(), qinfo->GetExclusiveGroup());
}
if (qinfo->TimeAllowed)
qinfo->SetSpecialFlag(QUEST_SPECIAL_FLAGS_TIMED);
if (qinfo->RequiredPlayerKills)
qinfo->SetSpecialFlag(QUEST_SPECIAL_FLAGS_PLAYER_KILL);
}
for (auto const& [questId, quest] : _questTemplates)
{
if (sDisableMgr->IsDisabledFor(DISABLE_TYPE_QUEST, questId, nullptr))
continue;
uint32 breadcrumbForQuestId = quest->GetBreadcrumbForQuestId();
if (!breadcrumbForQuestId)
continue;
std::set<uint32> visitedQuests;
visitedQuests.insert(questId);
while (breadcrumbForQuestId)
{
if (!visitedQuests.insert(breadcrumbForQuestId).second)
{
LOG_ERROR("sql.sql", "Breadcrumb quests {} and {} form a loop", questId, breadcrumbForQuestId);
break;
}
Quest const* targetQuest = GetQuestTemplate(breadcrumbForQuestId);
if (!targetQuest)
break;
breadcrumbForQuestId = targetQuest->GetBreadcrumbForQuestId();
}
}
// check QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT for spell with SPELL_EFFECT_QUEST_COMPLETE
for (uint32 i = 0; i < sSpellMgr->GetSpellInfoStoreSize(); ++i)
{

View File

@@ -1145,6 +1145,18 @@ public:
ExclusiveQuestGroups mExclusiveQuestGroups;
typedef std::unordered_map<uint32, std::vector<uint32>> BreadcrumbQuestMap;
BreadcrumbQuestMap _breadcrumbsForQuest;
[[nodiscard]] std::vector<uint32> const* GetBreadcrumbsForQuest(uint32 questId) const
{
auto itr = _breadcrumbsForQuest.find(questId);
if (itr != _breadcrumbsForQuest.end())
return &itr->second;
return nullptr;
}
MailLevelReward const* GetMailLevelReward(uint32 level, uint32 raceMask)
{
MailLevelRewardContainer::const_iterator map_itr = _mailLevelRewardStore.find(level);

View File

@@ -177,17 +177,18 @@ void Quest::LoadQuestTemplateAddon(Field* fields)
PrevQuestId = fields[4].Get<int32>();
NextQuestId = fields[5].Get<uint32>();
ExclusiveGroup = fields[6].Get<int32>();
RewardMailTemplateId = fields[7].Get<uint32>();
RewardMailDelay = fields[8].Get<uint32>();
RequiredSkillId = fields[9].Get<uint16>();
RequiredSkillPoints = fields[10].Get<uint16>();
RequiredMinRepFaction = fields[11].Get<uint16>();
RequiredMaxRepFaction = fields[12].Get<uint16>();
RequiredMinRepValue = fields[13].Get<int32>();
RequiredMaxRepValue = fields[14].Get<int32>();
StartItemCount = fields[15].Get<uint8>();
RewardMailSenderEntry = fields[16].Get<uint32>();
SpecialFlags = fields[17].Get<uint32>();
BreadcrumbForQuestId = fields[7].Get<uint32>();
RewardMailTemplateId = fields[8].Get<uint32>();
RewardMailDelay = fields[9].Get<uint32>();
RequiredSkillId = fields[10].Get<uint16>();
RequiredSkillPoints = fields[11].Get<uint16>();
RequiredMinRepFaction = fields[12].Get<uint16>();
RequiredMaxRepFaction = fields[13].Get<uint16>();
RequiredMinRepValue = fields[14].Get<int32>();
RequiredMaxRepValue = fields[15].Get<int32>();
StartItemCount = fields[16].Get<uint8>();
RewardMailSenderEntry = fields[17].Get<uint32>();
SpecialFlags = fields[18].Get<uint32>();
if ((SpecialFlags & QUEST_SPECIAL_FLAGS_AUTO_ACCEPT) && !sWorld->getBoolConfig(CONFIG_QUEST_IGNORE_AUTO_ACCEPT))
{

View File

@@ -249,6 +249,7 @@ public:
[[nodiscard]] int32 GetPrevQuestId() const { return PrevQuestId; }
[[nodiscard]] uint32 GetNextQuestId() const { return NextQuestId; }
[[nodiscard]] int32 GetExclusiveGroup() const { return ExclusiveGroup; }
[[nodiscard]] uint32 GetBreadcrumbForQuestId() const { return BreadcrumbForQuestId; }
[[nodiscard]] uint32 GetNextQuestInChain() const { return RewardNextQuest; }
[[nodiscard]] uint32 GetCharTitleId() const { return RewardTitleId; }
[[nodiscard]] uint32 GetPlayersSlain() const { return RequiredPlayerKills; }
@@ -390,6 +391,7 @@ protected:
int32 PrevQuestId = 0;
uint32 NextQuestId = 0;
int32 ExclusiveGroup = 0;
uint32 BreadcrumbForQuestId = 0;
uint32 RewardMailTemplateId = 0;
uint32 RewardMailDelay = 0;
uint32 RequiredSkillId = 0;