mirror of
https://github.com/araxiaonline/TrinityCore.git
synced 2026-06-13 03:32:28 -04:00
Core/Quests Implement quest campaigns
This commit is contained in:
59
sql/updates/hotfixes/master/2026_03_08_00_hotfixes.sql
Normal file
59
sql/updates/hotfixes/master/2026_03_08_00_hotfixes.sql
Normal file
@@ -0,0 +1,59 @@
|
||||
--
|
||||
-- Table structure for table `campaign`
|
||||
--
|
||||
DROP TABLE IF EXISTS `campaign`;
|
||||
CREATE TABLE `campaign` (
|
||||
`ID` int unsigned NOT NULL DEFAULT '0',
|
||||
`Title` text,
|
||||
`Description` text,
|
||||
`UiTextureKitID` int NOT NULL DEFAULT '0',
|
||||
`RewardQuestID` int NOT NULL DEFAULT '0',
|
||||
`Prerequisite` int NOT NULL DEFAULT '0',
|
||||
`Stalled` int NOT NULL DEFAULT '0',
|
||||
`Completed` int NOT NULL DEFAULT '0',
|
||||
`OnlyStallIf` int NOT NULL DEFAULT '0',
|
||||
`UiQuestDetailsThemeID` int NOT NULL DEFAULT '0',
|
||||
`Flags` int NOT NULL DEFAULT '0',
|
||||
`DisplayPriority` int NOT NULL DEFAULT '0',
|
||||
`SortAsNormalQuest` int NOT NULL DEFAULT '0',
|
||||
`UseMinimalHeader` int NOT NULL DEFAULT '0',
|
||||
`VerifiedBuild` int NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`ID`,`VerifiedBuild`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
--
|
||||
-- Table structure for table `campaign_locale`
|
||||
--
|
||||
DROP TABLE IF EXISTS `campaign_locale`;
|
||||
CREATE TABLE `campaign_locale` (
|
||||
`ID` int unsigned NOT NULL DEFAULT '0',
|
||||
`locale` varchar(4) NOT NULL,
|
||||
`Title_lang` text,
|
||||
`Description_lang` text,
|
||||
`VerifiedBuild` int NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`ID`,`locale`,`VerifiedBuild`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
PARTITION BY LIST COLUMNS(locale)
|
||||
(PARTITION deDE VALUES IN ('deDE') ENGINE = InnoDB,
|
||||
PARTITION esES VALUES IN ('esES') ENGINE = InnoDB,
|
||||
PARTITION esMX VALUES IN ('esMX') ENGINE = InnoDB,
|
||||
PARTITION frFR VALUES IN ('frFR') ENGINE = InnoDB,
|
||||
PARTITION itIT VALUES IN ('itIT') ENGINE = InnoDB,
|
||||
PARTITION koKR VALUES IN ('koKR') ENGINE = InnoDB,
|
||||
PARTITION ptBR VALUES IN ('ptBR') ENGINE = InnoDB,
|
||||
PARTITION ruRU VALUES IN ('ruRU') ENGINE = InnoDB,
|
||||
PARTITION zhCN VALUES IN ('zhCN') ENGINE = InnoDB,
|
||||
PARTITION zhTW VALUES IN ('zhTW') ENGINE = InnoDB);
|
||||
|
||||
--
|
||||
-- Table structure for table `campaign_x_quest_line`
|
||||
--
|
||||
DROP TABLE IF EXISTS `campaign_x_quest_line`;
|
||||
CREATE TABLE `campaign_x_quest_line` (
|
||||
`ID` int unsigned NOT NULL DEFAULT '0',
|
||||
`CampaignID` int unsigned NOT NULL DEFAULT '0',
|
||||
`QuestLineID` int unsigned NOT NULL DEFAULT '0',
|
||||
`OrderIndex` int unsigned NOT NULL DEFAULT '0',
|
||||
`VerifiedBuild` int NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`ID`,`VerifiedBuild`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
@@ -298,6 +298,18 @@ void HotfixDatabaseConnection::DoPrepareStatements()
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_BROADCAST_TEXT_DURATION, "SELECT MAX(ID) + 1 FROM broadcast_text_duration", CONNECTION_SYNCH);
|
||||
|
||||
// Campaign.db2
|
||||
PrepareStatement(HOTFIX_SEL_CAMPAIGN, "SELECT ID, Title, Description, UiTextureKitID, RewardQuestID, Prerequisite, Stalled, Completed, "
|
||||
"OnlyStallIf, UiQuestDetailsThemeID, Flags, DisplayPriority, SortAsNormalQuest, UseMinimalHeader FROM campaign WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_CAMPAIGN, "SELECT MAX(ID) + 1 FROM campaign", CONNECTION_SYNCH);
|
||||
PREPARE_LOCALE_STMT(HOTFIX_SEL_CAMPAIGN, "SELECT ID, Title_lang, Description_lang FROM campaign_locale WHERE (`VerifiedBuild` > 0) = ?"
|
||||
" AND locale = ?", CONNECTION_SYNCH);
|
||||
|
||||
// CampaignXQuestLine.db2
|
||||
PrepareStatement(HOTFIX_SEL_CAMPAIGN_X_QUEST_LINE, "SELECT ID, CampaignID, QuestLineID, OrderIndex FROM campaign_x_quest_line"
|
||||
" WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
PREPARE_MAX_ID_STMT(HOTFIX_SEL_CAMPAIGN_X_QUEST_LINE, "SELECT MAX(ID) + 1 FROM campaign_x_quest_line", CONNECTION_SYNCH);
|
||||
|
||||
// CfgCategories.db2
|
||||
PrepareStatement(HOTFIX_SEL_CFG_CATEGORIES, "SELECT ID, Name, LocaleMask, CreateCharsetMask, ExistingCharsetMask, Flags, `Order`"
|
||||
" FROM cfg_categories WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
|
||||
|
||||
@@ -188,6 +188,13 @@ enum HotfixDatabaseStatements : uint32
|
||||
HOTFIX_SEL_BROADCAST_TEXT_DURATION,
|
||||
HOTFIX_SEL_BROADCAST_TEXT_DURATION_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_CAMPAIGN,
|
||||
HOTFIX_SEL_CAMPAIGN_MAX_ID,
|
||||
HOTFIX_SEL_CAMPAIGN_LOCALE,
|
||||
|
||||
HOTFIX_SEL_CAMPAIGN_X_QUEST_LINE,
|
||||
HOTFIX_SEL_CAMPAIGN_X_QUEST_LINE_MAX_ID,
|
||||
|
||||
HOTFIX_SEL_CFG_CATEGORIES,
|
||||
HOTFIX_SEL_CFG_CATEGORIES_MAX_ID,
|
||||
HOTFIX_SEL_CFG_CATEGORIES_LOCALE,
|
||||
|
||||
@@ -3998,6 +3998,10 @@ bool CriteriaHandler::ModifierSatisfied(ModifierTreeEntry const* modifier, uint6
|
||||
if (referencePlayer->m_activePlayerData->TimerunningSeasonID != int32(reqValue))
|
||||
return false;
|
||||
break;
|
||||
case ModifierTreeType::PlayerHasCompletedCampaign: // 388
|
||||
if (!QuestMgr::IsCampaignCompletedByPlayer(reqValue, referencePlayer))
|
||||
return false;
|
||||
break;
|
||||
case ModifierTreeType::TargetCreatureClassificationEqual: // 389
|
||||
{
|
||||
Creature const* targetCreature = Object::ToCreature(ref);
|
||||
|
||||
@@ -810,6 +810,42 @@ struct BroadcastTextDurationLoadInfo
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 4, &BroadcastTextDurationMeta::Instance, HOTFIX_SEL_BROADCAST_TEXT_DURATION };
|
||||
};
|
||||
|
||||
struct CampaignLoadInfo
|
||||
{
|
||||
static constexpr DB2FieldMeta Fields[14] =
|
||||
{
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "ID" },
|
||||
{ .IsSigned = false, .Type = FT_STRING, .Name = "Title" },
|
||||
{ .IsSigned = false, .Type = FT_STRING, .Name = "Description" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "UiTextureKitID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "RewardQuestID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Prerequisite" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Stalled" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Completed" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "OnlyStallIf" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "UiQuestDetailsThemeID" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "Flags" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "DisplayPriority" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "SortAsNormalQuest" },
|
||||
{ .IsSigned = true, .Type = FT_INT, .Name = "UseMinimalHeader" },
|
||||
};
|
||||
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 14, &CampaignMeta::Instance, HOTFIX_SEL_CAMPAIGN };
|
||||
};
|
||||
|
||||
struct CampaignXQuestLineLoadInfo
|
||||
{
|
||||
static constexpr DB2FieldMeta Fields[4] =
|
||||
{
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "ID" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "CampaignID" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "QuestLineID" },
|
||||
{ .IsSigned = false, .Type = FT_INT, .Name = "OrderIndex" },
|
||||
};
|
||||
|
||||
static constexpr DB2LoadInfo Instance{ Fields, 4, &CampaignXQuestLineMeta::Instance, HOTFIX_SEL_CAMPAIGN_X_QUEST_LINE };
|
||||
};
|
||||
|
||||
struct CfgCategoriesLoadInfo
|
||||
{
|
||||
static constexpr DB2FieldMeta Fields[7] =
|
||||
|
||||
@@ -85,6 +85,8 @@ DB2Storage<BattlemasterListEntry> sBattlemasterListStore("Battlema
|
||||
DB2Storage<BattlemasterListXMapEntry> sBattlemasterListXMapStore("BattlemasterListXMap.db2", &BattlemasterListXMapLoadInfo::Instance);
|
||||
DB2Storage<BroadcastTextEntry> sBroadcastTextStore("BroadcastText.db2", &BroadcastTextLoadInfo::Instance);
|
||||
DB2Storage<BroadcastTextDurationEntry> sBroadcastTextDurationStore("BroadcastTextDuration.db2", &BroadcastTextDurationLoadInfo::Instance);
|
||||
DB2Storage<CampaignEntry> sCampaignStore("Campaign.db2", &CampaignLoadInfo::Instance);
|
||||
DB2Storage<CampaignXQuestLineEntry> sCampaignXQuestLineStore("CampaignXQuestLine.db2", &CampaignXQuestLineLoadInfo::Instance);
|
||||
DB2Storage<Cfg_CategoriesEntry> sCfgCategoriesStore("Cfg_Categories.db2", &CfgCategoriesLoadInfo::Instance);
|
||||
DB2Storage<Cfg_RegionsEntry> sCfgRegionsStore("Cfg_Regions.db2", &CfgRegionsLoadInfo::Instance);
|
||||
DB2Storage<ChallengeModeItemBonusOverrideEntry> sChallengeModeItemBonusOverrideStore("ChallengeModeItemBonusOverride.db2", &ChallengeModeItemBonusOverrideLoadInfo::Instance);
|
||||
@@ -714,6 +716,8 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul
|
||||
LOAD_DB2(sBattlemasterListXMapStore);
|
||||
LOAD_DB2(sBroadcastTextStore);
|
||||
LOAD_DB2(sBroadcastTextDurationStore);
|
||||
LOAD_DB2(sCampaignStore);
|
||||
LOAD_DB2(sCampaignXQuestLineStore);
|
||||
LOAD_DB2(sCfgCategoriesStore);
|
||||
LOAD_DB2(sCfgRegionsStore);
|
||||
LOAD_DB2(sChallengeModeItemBonusOverrideStore);
|
||||
|
||||
@@ -67,6 +67,8 @@ TC_GAME_API extern DB2Storage<BattlePetSpeciesStateEntry> sBattlePetSp
|
||||
TC_GAME_API extern DB2Storage<BattlemasterListEntry> sBattlemasterListStore;
|
||||
TC_GAME_API extern DB2Storage<BattlemasterListXMapEntry> sBattlemasterListXMapStore;
|
||||
TC_GAME_API extern DB2Storage<BroadcastTextEntry> sBroadcastTextStore;
|
||||
TC_GAME_API extern DB2Storage<CampaignEntry> sCampaignStore;
|
||||
TC_GAME_API extern DB2Storage<CampaignXQuestLineEntry> sCampaignXQuestLineStore;
|
||||
TC_GAME_API extern DB2Storage<Cfg_CategoriesEntry> sCfgCategoriesStore;
|
||||
TC_GAME_API extern DB2Storage<Cfg_RegionsEntry> sCfgRegionsStore;
|
||||
TC_GAME_API extern DB2Storage<ChallengeModeItemBonusOverrideEntry> sChallengeModeItemBonusOverrideStore;
|
||||
|
||||
@@ -571,6 +571,34 @@ struct BroadcastTextDurationEntry
|
||||
uint32 BroadcastTextID;
|
||||
};
|
||||
|
||||
struct CampaignEntry
|
||||
{
|
||||
uint32 ID;
|
||||
LocalizedString Title;
|
||||
LocalizedString Description;
|
||||
int32 UiTextureKitID;
|
||||
int32 RewardQuestID;
|
||||
int32 Prerequisite;
|
||||
int32 Stalled;
|
||||
int32 Completed;
|
||||
int32 OnlyStallIf;
|
||||
int32 UiQuestDetailsThemeID;
|
||||
int32 Flags;
|
||||
int32 DisplayPriority;
|
||||
int32 SortAsNormalQuest;
|
||||
int32 UseMinimalHeader;
|
||||
|
||||
bool HasFlag(CampaignFlags flag) const { return EnumFlag(static_cast<CampaignFlags>(Flags)).HasFlag(flag); }
|
||||
};
|
||||
|
||||
struct CampaignXQuestLineEntry
|
||||
{
|
||||
uint32 ID;
|
||||
uint32 CampaignID;
|
||||
uint32 QuestLineID;
|
||||
uint32 OrderIndex;
|
||||
};
|
||||
|
||||
struct Cfg_CategoriesEntry
|
||||
{
|
||||
uint32 ID;
|
||||
|
||||
@@ -284,6 +284,14 @@ enum class BattlemasterListFlags : uint32
|
||||
|
||||
DEFINE_ENUM_FLAG(BattlemasterListFlags);
|
||||
|
||||
enum class CampaignFlags : int32
|
||||
{
|
||||
DontUseJourneyQuestBang = 0x01,
|
||||
IsContainer = 0x02
|
||||
};
|
||||
|
||||
DEFINE_ENUM_FLAG(CampaignFlags);
|
||||
|
||||
enum class CfgCategoriesCharsets : uint8
|
||||
{
|
||||
Any = 0x00,
|
||||
@@ -2035,7 +2043,7 @@ enum class ModifierTreeType : int32
|
||||
PlayerHasActiveTraitSubTree = 385, // Player has active trait config with {TraitSubTree}
|
||||
PlayerIsInTimerunningSeason = 386, // Player is timerunning {TimerunningSeason}
|
||||
PlayerIsInSoloRBG = 387, /*NYI*/ // Player is in solo RBG (BG Blitz)
|
||||
PlayerHasCompletedCampaign = 388, /*NYI*/ // Player has completed campaign "{Campaign}"
|
||||
PlayerHasCompletedCampaign = 388, // Player has completed campaign "{Campaign}"
|
||||
TargetCreatureClassificationEqual = 389, // Creature classification is {CreatureClassification}
|
||||
PlayerDataElementCharacterBetween = 390, // Player {PlayerDataElementCharacter} is between {#Amount} and {#Amount2}
|
||||
PlayerDataElementAccountBetween = 391, // Player {PlayerDataElementAccount} is between {#Amount} and {#Amount2}
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
#include "QueryHolder.h"
|
||||
#include "QueryResultStructured.h"
|
||||
#include "QuestDef.h"
|
||||
#include "QuestMgr.h"
|
||||
#include "QuestObjectiveCriteriaMgr.h"
|
||||
#include "QuestPackets.h"
|
||||
#include "RealmList.h"
|
||||
@@ -16073,6 +16074,8 @@ QuestGiverStatus Player::GetQuestDialogStatus(Object const* questgiver) const
|
||||
result |= quest->HasFlag(QUEST_FLAGS_HIDE_REWARD_POI) ? QuestGiverStatus::CovenantCallingRewardCompleteNoPOI : QuestGiverStatus::CovenantCallingRewardCompletePOI;
|
||||
else if (quest->HasFlagEx(QUEST_FLAGS_EX_LEGENDARY))
|
||||
result |= quest->HasFlag(QUEST_FLAGS_HIDE_REWARD_POI) ? QuestGiverStatus::LegendaryRewardCompleteNoPOI : QuestGiverStatus::LegendaryRewardCompletePOI;
|
||||
else if (QuestMgr::IsCampaignQuestStatusVisibleForPlayer(questId, this))
|
||||
result |= quest->HasFlag(QUEST_FLAGS_HIDE_REWARD_POI) ? QuestGiverStatus::JourneyRewardCompleteNoPOI : QuestGiverStatus::JourneyRewardCompletePOI;
|
||||
else if (quest->IsDailyOrWeekly())
|
||||
result |= quest->HasFlag(QUEST_FLAGS_HIDE_REWARD_POI) ? QuestGiverStatus::RepeatableRewardCompleteNoPOI : QuestGiverStatus::RepeatableRewardCompletePOI;
|
||||
else
|
||||
@@ -16087,6 +16090,8 @@ QuestGiverStatus Player::GetQuestDialogStatus(Object const* questgiver) const
|
||||
result |= QuestGiverStatus::CovenantCallingReward;
|
||||
else if (quest->HasFlagEx(QUEST_FLAGS_EX_LEGENDARY))
|
||||
result |= QuestGiverStatus::LegendaryReward;
|
||||
else if (QuestMgr::IsCampaignQuestStatusVisibleForPlayer(questId, this))
|
||||
result |= QuestGiverStatus::JourneyReward;
|
||||
else if (quest->IsDailyOrWeekly())
|
||||
result |= QuestGiverStatus::RepeatableReward;
|
||||
else
|
||||
@@ -16130,6 +16135,8 @@ QuestGiverStatus Player::GetQuestDialogStatus(Object const* questgiver) const
|
||||
result |= QuestGiverStatus::CovenantCallingQuest;
|
||||
else if (quest->HasFlagEx(QUEST_FLAGS_EX_LEGENDARY))
|
||||
result |= isTrivial ? QuestGiverStatus::TrivialLegendaryQuest : QuestGiverStatus::LegendaryQuest;
|
||||
else if (QuestMgr::IsCampaignQuestStatusVisibleForPlayer(questId, this))
|
||||
result |= isTrivial ? QuestGiverStatus::TrivialJourneyQuest : QuestGiverStatus::JourneyQuest;
|
||||
else if (quest->IsDailyOrWeekly())
|
||||
result |= isTrivial ? QuestGiverStatus::TrivialRepeatableQuest : QuestGiverStatus::RepeatableQuest;
|
||||
else
|
||||
@@ -16139,6 +16146,8 @@ QuestGiverStatus Player::GetQuestDialogStatus(Object const* questgiver) const
|
||||
result |= QuestGiverStatus::FutureImportantQuest;
|
||||
else if (quest->HasFlagEx(QUEST_FLAGS_EX_LEGENDARY))
|
||||
result |= QuestGiverStatus::FutureLegendaryQuest;
|
||||
else if (QuestMgr::IsCampaignQuestStatusVisibleForPlayer(questId, this))
|
||||
result |= QuestGiverStatus::FutureJourneyQuest;
|
||||
else
|
||||
result |= QuestGiverStatus::Future;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "QuestMgr.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "MapUtils.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Player.h"
|
||||
#include <algorithm>
|
||||
@@ -24,13 +25,65 @@
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<uint32, std::vector<QuestLineXQuestEntry const*>> QuestsByQuestLine;
|
||||
|
||||
struct QuestLineData
|
||||
{
|
||||
QuestLineXQuestEntry const* QuestLineQuest = nullptr;
|
||||
std::vector<CampaignEntry const*>* Campaigns = nullptr;
|
||||
};
|
||||
std::map<uint32, std::vector<CampaignEntry const*>> CampaignsByQuestLine;
|
||||
std::unordered_map<uint32, std::vector<QuestLineData>> QuestLineDataByQuest;
|
||||
|
||||
struct CampaignQuestLine
|
||||
{
|
||||
uint32 CampaignId = 0;
|
||||
uint32 QuestLineId = 0;
|
||||
|
||||
friend std::strong_ordering operator<=>(CampaignQuestLine const& left, CampaignQuestLine const& right) = default;
|
||||
};
|
||||
std::vector<CampaignQuestLine> CampaignQuestLines;
|
||||
|
||||
struct CampaignQuestLinesSentinel
|
||||
{
|
||||
std::vector<CampaignQuestLine>::const_iterator End;
|
||||
uint32 CampaignId;
|
||||
|
||||
friend bool operator==(std::vector<CampaignQuestLine>::const_iterator const& itr, CampaignQuestLinesSentinel const& end)
|
||||
{
|
||||
return itr == end.End || itr->CampaignId != end.CampaignId;
|
||||
}
|
||||
};
|
||||
|
||||
Trinity::IteratorPair<std::vector<CampaignQuestLine>::iterator, CampaignQuestLinesSentinel> GetQuestLinesForCampaign(uint32 campaignId)
|
||||
{
|
||||
return Trinity::Containers::MakeIteratorPair(
|
||||
std::ranges::lower_bound(CampaignQuestLines, campaignId, std::ranges::less(), &CampaignQuestLine::CampaignId),
|
||||
CampaignQuestLinesSentinel{ .End = CampaignQuestLines.end(), .CampaignId = campaignId });
|
||||
}
|
||||
}
|
||||
|
||||
void QuestMgr::Load()
|
||||
{
|
||||
for (CampaignXQuestLineEntry const* campaignQuestLine : sCampaignXQuestLineStore)
|
||||
{
|
||||
if (CampaignEntry const* campaign = sCampaignStore.LookupEntry(campaignQuestLine->CampaignID))
|
||||
{
|
||||
CampaignsByQuestLine[campaignQuestLine->QuestLineID].push_back(campaign);
|
||||
CampaignQuestLines.push_back({ .CampaignId = campaignQuestLine->CampaignID, .QuestLineId = campaignQuestLine->QuestLineID });
|
||||
}
|
||||
}
|
||||
|
||||
for (QuestLineXQuestEntry const* questLineQuest : sQuestLineXQuestStore)
|
||||
{
|
||||
QuestsByQuestLine[questLineQuest->QuestLineID].push_back(questLineQuest);
|
||||
|
||||
QuestLineData& questLineData = QuestLineDataByQuest[questLineQuest->QuestID].emplace_back();
|
||||
questLineData.QuestLineQuest = questLineQuest;
|
||||
questLineData.Campaigns = Trinity::Containers::MapGetValuePtr(CampaignsByQuestLine, questLineQuest->QuestLineID);
|
||||
}
|
||||
|
||||
std::ranges::sort(CampaignQuestLines);
|
||||
|
||||
for (auto& [_, questLineQuests] : QuestsByQuestLine)
|
||||
std::ranges::sort(questLineQuests, std::ranges::less(), &QuestLineXQuestEntry::OrderIndex);
|
||||
}
|
||||
@@ -99,3 +152,67 @@ void QuestMgr::SkipQuestLineForPlayer(uint32 questLineId, Player* player)
|
||||
std::ranges::transform(questLineQuests, questIds.begin(), &QuestLineXQuestEntry::QuestID);
|
||||
player->SkipQuests(questIds);
|
||||
}
|
||||
|
||||
bool QuestMgr::IsCampaignCompletedByPlayer(uint32 campaignId, Player const* player)
|
||||
{
|
||||
auto questLines = GetQuestLinesForCampaign(campaignId);
|
||||
if (questLines.begin() == questLines.end())
|
||||
return false;
|
||||
|
||||
for (CampaignQuestLine const& campaignQuestLine : questLines)
|
||||
if (!IsQuestLineCompletedByPlayer(campaignQuestLine.QuestLineId, player))
|
||||
return false;
|
||||
|
||||
// all questlines completed
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QuestMgr::IsCampaignQuestStatusVisibleForPlayer(uint32 questId, Player const* player)
|
||||
{
|
||||
auto itr = QuestLineDataByQuest.find(questId);
|
||||
if (itr == QuestLineDataByQuest.end())
|
||||
return false;
|
||||
|
||||
for (QuestLineData const& questLineData : itr->second)
|
||||
{
|
||||
if (!questLineData.Campaigns)
|
||||
continue;
|
||||
|
||||
for (CampaignEntry const* campaign : *questLineData.Campaigns)
|
||||
{
|
||||
if (campaign->HasFlag(CampaignFlags::DontUseJourneyQuestBang))
|
||||
continue;
|
||||
|
||||
if (!ConditionMgr::IsPlayerMeetingCondition(player, campaign->Prerequisite))
|
||||
continue;
|
||||
|
||||
if (!ConditionMgr::IsPlayerMeetingCondition(player, campaign->Stalled))
|
||||
continue;
|
||||
|
||||
if (campaign->Completed && ConditionMgr::IsPlayerMeetingCondition(player, campaign->Completed))
|
||||
continue;
|
||||
|
||||
if (!ConditionMgr::IsPlayerMeetingCondition(player, campaign->OnlyStallIf))
|
||||
continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void QuestMgr::SkipCampaignForPlayer(uint32 campaignId, Player* player)
|
||||
{
|
||||
std::vector<uint32> questIds;
|
||||
|
||||
for (CampaignQuestLine const& campaignQuestLine : GetQuestLinesForCampaign(campaignId))
|
||||
{
|
||||
std::ptrdiff_t oldSize = std::ssize(questIds);
|
||||
std::span<QuestLineXQuestEntry const* const> questLineQuests = GetQuestsForQuestLine(campaignQuestLine.QuestLineId);
|
||||
questIds.resize(oldSize + questLineQuests.size());
|
||||
std::ranges::transform(questLineQuests, questIds.begin() + oldSize, &QuestLineXQuestEntry::QuestID);
|
||||
}
|
||||
|
||||
player->SkipQuests(questIds);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,13 @@ struct QuestLineStats { uint32 Completed = 0; uint32 Total = 0; };
|
||||
TC_GAME_API QuestLineStats GetQuestLineStatsForPlayer(uint32 questLineId, Player const* player);
|
||||
|
||||
TC_GAME_API void SkipQuestLineForPlayer(uint32 questLineId, Player* player);
|
||||
|
||||
// Campaign
|
||||
TC_GAME_API bool IsCampaignCompletedByPlayer(uint32 campaignId, Player const* player);
|
||||
|
||||
TC_GAME_API bool IsCampaignQuestStatusVisibleForPlayer(uint32 questId, Player const* player);
|
||||
|
||||
TC_GAME_API void SkipCampaignForPlayer(uint32 campaignId, Player* player);
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_CAMPAIGN_MGR_H
|
||||
|
||||
@@ -445,6 +445,7 @@ class TC_GAME_API Spell
|
||||
void EffectLearnAzeriteEssencePower();
|
||||
void EffectCreatePrivateConversation();
|
||||
void EffectApplyMountEquipment();
|
||||
void EffectSkipCampaign();
|
||||
void EffectSendChatMessage();
|
||||
void EffectGrantBattlePetExperience();
|
||||
void EffectLearnTransmogIllusion();
|
||||
|
||||
@@ -372,7 +372,7 @@ NonDefaultConstructible<SpellEffectHandlerFn> SpellEffectHandlers[TOTAL_SPELL_EF
|
||||
&Spell::EffectUnused, //280 SPELL_EFFECT_280
|
||||
&Spell::EffectNULL, //281 SPELL_EFFECT_LEARN_SOULBIND_CONDUIT
|
||||
&Spell::EffectNULL, //282 SPELL_EFFECT_CONVERT_ITEMS_TO_CURRENCY
|
||||
&Spell::EffectNULL, //283 SPELL_EFFECT_COMPLETE_CAMPAIGN
|
||||
&Spell::EffectSkipCampaign, //283 SPELL_EFFECT_COMPLETE_CAMPAIGN
|
||||
&Spell::EffectSendChatMessage, //284 SPELL_EFFECT_SEND_CHAT_MESSAGE
|
||||
&Spell::EffectNULL, //285 SPELL_EFFECT_MODIFY_KEYSTONE_2
|
||||
&Spell::EffectGrantBattlePetExperience, //286 SPELL_EFFECT_GRANT_BATTLEPET_EXPERIENCE
|
||||
@@ -6053,6 +6053,18 @@ void Spell::EffectApplyMountEquipment()
|
||||
playerTarget->SendDirectMessage(applyMountEquipmentResult.Write());
|
||||
}
|
||||
|
||||
void Spell::EffectSkipCampaign()
|
||||
{
|
||||
if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET)
|
||||
return;
|
||||
|
||||
Player* target = Object::ToPlayer(unitTarget);
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
QuestMgr::SkipCampaignForPlayer(effectInfo->MiscValue, target);
|
||||
}
|
||||
|
||||
void Spell::EffectSendChatMessage()
|
||||
{
|
||||
if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET)
|
||||
|
||||
Reference in New Issue
Block a user