Core/ Implement bonus quest system

This commit is contained in:
luis
2026-09-26 11:12:34 -03:00
parent 2640fdfa8e
commit 40ae64dd29
19 changed files with 369 additions and 41 deletions
@@ -0,0 +1,71 @@
--
-- Table structure for table `quest_v2_cli_task`
--
DROP TABLE IF EXISTS `quest_v2_cli_task`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `quest_v2_cli_task` (
`ID` int unsigned NOT NULL DEFAULT '0',
`QuestTitle` text,
`BulletText` text,
`UniqueBitFlag` int NOT NULL DEFAULT '0',
`ConditionID` int unsigned NOT NULL DEFAULT '0',
`FiltActiveQuest` int unsigned NOT NULL DEFAULT '0',
`FiltClasses` smallint NOT NULL DEFAULT '0',
`FiltCompletedQuestLogic` int unsigned NOT NULL DEFAULT '0',
`FiltMaxFactionID` int unsigned NOT NULL DEFAULT '0',
`FiltMaxFactionValue` int unsigned NOT NULL DEFAULT '0',
`FiltMinFactionID` int unsigned NOT NULL DEFAULT '0',
`FiltMinFactionValue` int unsigned NOT NULL DEFAULT '0',
`FiltMinSkillID` int unsigned NOT NULL DEFAULT '0',
`FiltMinSkillValue` int unsigned NOT NULL DEFAULT '0',
`FiltNonActiveQuest` int unsigned NOT NULL DEFAULT '0',
`BreadCrumbID` int unsigned NOT NULL DEFAULT '0',
`StartItem` int NOT NULL DEFAULT '0',
`WorldStateExpressionID` int NOT NULL DEFAULT '0',
`QuestInfoID` int unsigned NOT NULL DEFAULT '0',
`ContentTuningID` int NOT NULL DEFAULT '0',
`CovenantID` int NOT NULL DEFAULT '0',
`Field_9_0_2_36206_022` int NOT NULL DEFAULT '0',
`Flags1` int NOT NULL DEFAULT '0',
`Flags2` int NOT NULL DEFAULT '0',
`Flags3` int NOT NULL DEFAULT '0',
`FiltCompletedQuest1` int unsigned NOT NULL DEFAULT '0',
`FiltCompletedQuest2` int unsigned NOT NULL DEFAULT '0',
`FiltCompletedQuest3` int unsigned NOT NULL DEFAULT '0',
`FiltRaceMasks1` int NOT NULL DEFAULT '0',
`FiltRaceMasks2` int NOT NULL DEFAULT '0',
`VerifiedBuild` int NOT NULL DEFAULT '0',
PRIMARY KEY (`ID`,`VerifiedBuild`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `quest_v2_cli_task_locale`
--
DROP TABLE IF EXISTS `quest_v2_cli_task_locale`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `quest_v2_cli_task_locale` (
`ID` int unsigned NOT NULL DEFAULT '0',
`locale` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`QuestTitle_lang` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
`BulletText_lang` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
`VerifiedBuild` int NOT NULL DEFAULT '0',
PRIMARY KEY (`ID`,`locale`,`VerifiedBuild`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
/*!50500 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) */;
/*!40101 SET character_set_client = @saved_cs_client */;
@@ -1935,6 +1935,16 @@ void HotfixDatabaseConnection::DoPrepareStatements()
PrepareStatement(HOTFIX_SEL_QUEST_V2, "SELECT ID, UniqueBitFlag, UiQuestDetailsTheme FROM quest_v2 WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); PrepareStatement(HOTFIX_SEL_QUEST_V2, "SELECT ID, UniqueBitFlag, UiQuestDetailsTheme FROM quest_v2 WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
PREPARE_MAX_ID_STMT(HOTFIX_SEL_QUEST_V2, "SELECT MAX(ID) + 1 FROM quest_v2", CONNECTION_SYNCH); PREPARE_MAX_ID_STMT(HOTFIX_SEL_QUEST_V2, "SELECT MAX(ID) + 1 FROM quest_v2", CONNECTION_SYNCH);
// QuestV2CliTask.db2
PrepareStatement(HOTFIX_SEL_QUEST_V2_CLI_TASK, "SELECT ID, QuestTitle, BulletText, UniqueBitFlag, ConditionID, FiltActiveQuest, FiltClasses, "
"FiltCompletedQuestLogic, FiltMaxFactionID, FiltMaxFactionValue, FiltMinFactionID, FiltMinFactionValue, FiltMinSkillID, FiltMinSkillValue, "
"FiltNonActiveQuest, BreadCrumbID, StartItem, WorldStateExpressionID, QuestInfoID, ContentTuningID, CovenantID, Field_9_0_2_36206_022, "
"Flags1, Flags2, Flags3, FiltCompletedQuest1, FiltCompletedQuest2, FiltCompletedQuest3, FiltRaceMasks1, FiltRaceMasks2 "
"FROM quest_v2_cli_task WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
PREPARE_MAX_ID_STMT(HOTFIX_SEL_QUEST_V2_CLI_TASK, "SELECT MAX(ID) + 1 FROM quest_v2_cli_task", CONNECTION_SYNCH);
PREPARE_LOCALE_STMT(HOTFIX_SEL_QUEST_V2_CLI_TASK, "SELECT ID, QuestTitle_lang, BulletText_lang FROM quest_v2_cli_task_locale"
" WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH);
// QuestXp.db2 // QuestXp.db2
PrepareStatement(HOTFIX_SEL_QUEST_XP, "SELECT ID, Difficulty1, Difficulty2, Difficulty3, Difficulty4, Difficulty5, Difficulty6, Difficulty7, " PrepareStatement(HOTFIX_SEL_QUEST_XP, "SELECT ID, Difficulty1, Difficulty2, Difficulty3, Difficulty4, Difficulty5, Difficulty6, Difficulty7, "
"Difficulty8, Difficulty9, Difficulty10 FROM quest_xp WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); "Difficulty8, Difficulty9, Difficulty10 FROM quest_xp WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
+39
View File
@@ -5441,6 +5441,45 @@ struct QuestV2LoadInfo
static constexpr DB2LoadInfo Instance{ Fields, 3, &QuestV2Meta::Instance, HOTFIX_SEL_QUEST_V2 }; static constexpr DB2LoadInfo Instance{ Fields, 3, &QuestV2Meta::Instance, HOTFIX_SEL_QUEST_V2 };
}; };
struct QuestV2CliTaskLoadInfo
{
static constexpr DB2FieldMeta Fields[30] =
{
{.IsSigned = false, .Type = FT_INT, .Name = "ID" },
{.IsSigned = false, .Type = FT_STRING, .Name = "QuestTitle" },
{.IsSigned = false, .Type = FT_STRING, .Name = "BulletText" },
{.IsSigned = true, .Type = FT_INT, .Name = "UniqueBitFlag" },
{.IsSigned = false, .Type = FT_INT, .Name = "ConditionID" },
{.IsSigned = false, .Type = FT_INT, .Name = "FiltActiveQuest" },
{.IsSigned = true, .Type = FT_SHORT, .Name = "FiltClasses" },
{.IsSigned = false, .Type = FT_INT, .Name = "FiltCompletedQuestLogic" },
{.IsSigned = false, .Type = FT_INT, .Name = "FiltMaxFactionID" },
{.IsSigned = false, .Type = FT_INT, .Name = "FiltMaxFactionValue" },
{.IsSigned = false, .Type = FT_INT, .Name = "FiltMinFactionID" },
{.IsSigned = false, .Type = FT_INT, .Name = "FiltMinFactionValue" },
{.IsSigned = false, .Type = FT_INT, .Name = "FiltMinSkillID" },
{.IsSigned = false, .Type = FT_INT, .Name = "FiltMinSkillValue" },
{.IsSigned = false, .Type = FT_INT, .Name = "FiltNonActiveQuest" },
{.IsSigned = false, .Type = FT_INT, .Name = "BreadCrumbID" },
{.IsSigned = true, .Type = FT_INT, .Name = "StartItem" },
{.IsSigned = true, .Type = FT_INT, .Name = "WorldStateExpressionID" },
{.IsSigned = false, .Type = FT_INT, .Name = "QuestInfoID" },
{.IsSigned = true, .Type = FT_INT, .Name = "ContentTuningID" },
{.IsSigned = true, .Type = FT_INT, .Name = "CovenantID" },
{.IsSigned = true, .Type = FT_INT, .Name = "Field_9_0_2_36206_022" },
{.IsSigned = true, .Type = FT_INT, .Name = "Flags1" },
{.IsSigned = true, .Type = FT_INT, .Name = "Flags2" },
{.IsSigned = true, .Type = FT_INT, .Name = "Flags3" },
{.IsSigned = false, .Type = FT_INT, .Name = "FiltCompletedQuest1" },
{.IsSigned = false, .Type = FT_INT, .Name = "FiltCompletedQuest2" },
{.IsSigned = false, .Type = FT_INT, .Name = "FiltCompletedQuest3" },
{.IsSigned = true, .Type = FT_INT, .Name = "FiltRaceMasks1" },
{.IsSigned = true, .Type = FT_INT, .Name = "FiltRaceMasks2" },
};
static constexpr DB2LoadInfo Instance{ Fields, 30, &QuestV2CliTaskMeta::Instance, HOTFIX_SEL_QUEST_V2_CLI_TASK };
};
struct QuestXpLoadInfo struct QuestXpLoadInfo
{ {
static constexpr DB2FieldMeta Fields[11] = static constexpr DB2FieldMeta Fields[11] =
+2
View File
@@ -379,6 +379,7 @@ DB2Storage<QuestMoneyRewardEntry> sQuestMoneyRewardStore("QuestMon
DB2Storage<QuestPackageItemEntry> sQuestPackageItemStore("QuestPackageItem.db2", &QuestPackageItemLoadInfo::Instance); DB2Storage<QuestPackageItemEntry> sQuestPackageItemStore("QuestPackageItem.db2", &QuestPackageItemLoadInfo::Instance);
DB2Storage<QuestSortEntry> sQuestSortStore("QuestSort.db2", &QuestSortLoadInfo::Instance); DB2Storage<QuestSortEntry> sQuestSortStore("QuestSort.db2", &QuestSortLoadInfo::Instance);
DB2Storage<QuestV2Entry> sQuestV2Store("QuestV2.db2", &QuestV2LoadInfo::Instance); DB2Storage<QuestV2Entry> sQuestV2Store("QuestV2.db2", &QuestV2LoadInfo::Instance);
DB2Storage<QuestV2CliTaskEntry> sQuestV2CliTaskStore("QuestV2CliTask.db2", &QuestV2CliTaskLoadInfo::Instance);
DB2Storage<QuestXPEntry> sQuestXPStore("QuestXP.db2", &QuestXpLoadInfo::Instance); DB2Storage<QuestXPEntry> sQuestXPStore("QuestXP.db2", &QuestXpLoadInfo::Instance);
DB2Storage<RandPropPointsEntry> sRandPropPointsStore("RandPropPoints.db2", &RandPropPointsLoadInfo::Instance); DB2Storage<RandPropPointsEntry> sRandPropPointsStore("RandPropPoints.db2", &RandPropPointsLoadInfo::Instance);
DB2Storage<RewardPackEntry> sRewardPackStore("RewardPack.db2", &RewardPackLoadInfo::Instance); DB2Storage<RewardPackEntry> sRewardPackStore("RewardPack.db2", &RewardPackLoadInfo::Instance);
@@ -1505,6 +1506,7 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul
LOAD_DB2(sQuestPackageItemStore); LOAD_DB2(sQuestPackageItemStore);
LOAD_DB2(sQuestSortStore); LOAD_DB2(sQuestSortStore);
LOAD_DB2(sQuestV2Store); LOAD_DB2(sQuestV2Store);
LOAD_DB2(sQuestV2CliTaskStore);
LOAD_DB2(sQuestXPStore); LOAD_DB2(sQuestXPStore);
LOAD_DB2(sRandPropPointsStore); LOAD_DB2(sRandPropPointsStore);
LOAD_DB2(sRewardPackStore); LOAD_DB2(sRewardPackStore);
+1
View File
@@ -446,6 +446,7 @@ TC_GAME_API extern DB2Storage<RenownRewardsEntry> sRenownRewar
TC_GAME_API extern DB2Storage<RenownRewardsPlunderstormEntry> sRenownRewardsPlunderstormStore; TC_GAME_API extern DB2Storage<RenownRewardsPlunderstormEntry> sRenownRewardsPlunderstormStore;
TC_GAME_API extern DB2Storage<CampaignXConditionEntry> sCampaignXConditionStore; TC_GAME_API extern DB2Storage<CampaignXConditionEntry> sCampaignXConditionStore;
TC_GAME_API extern DB2Storage<QuestLineEntry> sQuestLineStore; TC_GAME_API extern DB2Storage<QuestLineEntry> sQuestLineStore;
TC_GAME_API extern DB2Storage<QuestV2CliTaskEntry> sQuestV2CliTaskStore;
TC_GAME_API extern DB2Storage<CurrencyCategoryEntry> sCurrencyCategoryStore; TC_GAME_API extern DB2Storage<CurrencyCategoryEntry> sCurrencyCategoryStore;
TC_GAME_API extern DB2Storage<FactionGroupEntry> sFactionGroupStore; TC_GAME_API extern DB2Storage<FactionGroupEntry> sFactionGroupStore;
TC_GAME_API extern DB2Storage<UiTextureKitEntry> sUiTextureKitStore; TC_GAME_API extern DB2Storage<UiTextureKitEntry> sUiTextureKitStore;
+28
View File
@@ -6761,6 +6761,34 @@ struct BattlePetStateEntry
uint16 BattlePetVisualID; uint16 BattlePetVisualID;
}; };
struct QuestV2CliTaskEntry
{
uint32 ID;
LocalizedString QuestTitle;
LocalizedString BulletText;
int32 UniqueBitFlag;
uint32 ConditionID;
uint32 FiltActiveQuest;
int16 FiltClasses;
uint32 FiltCompletedQuestLogic;
uint32 FiltMaxFactionID;
uint32 FiltMaxFactionValue;
uint32 FiltMinFactionID;
uint32 FiltMinFactionValue;
uint32 FiltMinSkillID;
uint32 FiltMinSkillValue;
uint32 FiltNonActiveQuest;
uint32 BreadCrumbID;
int32 StartItem;
int32 WorldStateExpressionID;
uint32 QuestInfoID;
int32 ContentTuningID;
int32 CovenantID;
int32 Field_9_0_2_36206_022;
std::array<int32, 3> Flags;
std::array<uint32, 3> FiltCompletedQuest;
std::array<int32, 2> FiltRaceMasks;
};
//WowCommunity //WowCommunity
#pragma pack(pop) #pragma pack(pop)
#endif #endif
+1 -1
View File
@@ -23,7 +23,7 @@
#include <span> #include <span>
#include <vector> #include <vector>
class ItemTemplate; struct ItemTemplate;
class Player; class Player;
struct ItemBonusEntry; struct ItemBonusEntry;
struct MapDifficultyEntry; struct MapDifficultyEntry;
@@ -168,6 +168,7 @@
#include "OmniumFolioMgr.h" #include "OmniumFolioMgr.h"
#include "CovenantPackets.h" #include "CovenantPackets.h"
#include "WeeklyRewardsMgr.h" #include "WeeklyRewardsMgr.h"
#include "WorldQuestMgr.h"
#include "QuaternionData.h" #include "QuaternionData.h"
#include "LFGPackets.h" #include "LFGPackets.h"
#include <cmath> #include <cmath>
@@ -6279,6 +6280,7 @@ bool Player::UpdatePosition(float x, float y, float z, float orientation, bool t
CheckAreaExplore(); CheckAreaExplore();
//WowCommunity //WowCommunity
UpdateWorldQuestPosition(x, y);
_UpdateArchaeologySurveyIndicator(); _UpdateArchaeologySurveyIndicator();
//WowCommunity //WowCommunity
@@ -15196,6 +15198,12 @@ void Player::AddQuest(Quest const* quest, Object* questGiver)
if (questGiver && questGiver->GetTypeId() == TYPEID_PLAYER) if (questGiver && questGiver->GetTypeId() == TYPEID_PLAYER)
limittime = questGiver->ToPlayer()->getQuestStatusMap()[quest_id].Timer / IN_MILLISECONDS; limittime = questGiver->ToPlayer()->getQuestStatusMap()[quest_id].Timer / IN_MILLISECONDS;
// World quests rotate on their own schedule, so the static LimitTime from the quest
// template is wrong. Override before the timer is derived below, otherwise this is a no-op.
if (quest->IsWorldQuest())
if (uint32 worldQuestTimer = sWorldQuestMgr->GetTimerForQuest(uint32(quest_id)))
limittime = worldQuestTimer;
AddTimedQuest(quest_id); AddTimedQuest(quest_id);
questStatusData.Timer = limittime * IN_MILLISECONDS; questStatusData.Timer = limittime * IN_MILLISECONDS;
endTime = GameTime::GetGameTime() + limittime; endTime = GameTime::GetGameTime() + limittime;
@@ -15203,6 +15211,7 @@ void Player::AddQuest(Quest const* quest, Object* questGiver)
else else
questStatusData.Timer = 0; questStatusData.Timer = 0;
if (quest->HasFlag(QUEST_FLAGS_FLAGS_PVP)) if (quest->HasFlag(QUEST_FLAGS_FLAGS_PVP))
{ {
pvpInfo.IsHostile = true; pvpInfo.IsHostile = true;
@@ -36116,3 +36125,56 @@ void Player::UpdateDynamicFlight(bool apply)
RemoveAura(SPELL_DYNAMIC_FLIGHT); RemoveAura(SPELL_DYNAMIC_FLIGHT);
} }
} }
void Player::UpdateWorldQuestPosition(float x, float y)
{
if (time(nullptr) < m_areaQuestTimer)
return;
m_areaQuestTimer = time(nullptr) + 2;
for (auto bonus_quest : sObjectMgr->BonusQuestsRects)
{
if (IsQuestRewarded(bonus_quest.first))
continue;
bool found = false;
for (uint32 i = 0; i < bonus_quest.second.size(); ++i)
{
if (bonus_quest.second[i].IsIn(GetMapId(), x, y))
found = true;
}
uint32 slot = FindQuestSlot(bonus_quest.first);
Quest const* quest = sObjectMgr->GetQuestTemplate(bonus_quest.first);
if (!quest)
continue;
if (!found && slot < MAX_QUEST_LOG_SIZE)
{
SetQuestSlot(slot, 0);
RemoveActiveQuest(uint32(bonus_quest.first), true);
}
else if (found && slot >= MAX_QUEST_LOG_SIZE)
{
if (!CanTakeQuest(quest, false))
continue;
//if (quest->IsWorldQuest())
//{
// // Uniting the Isles, required for Legion world quests
// if (!HasWorldQuestEnabled(quest->GetExpansion()))
// continue;
// if (!sWorldQuestMgr->IsQuestActive(quest->GetQuestId()))
// continue;
//}
// AddQuest -> SetQuestSlot already zeroes UF::QuestLog::ObjectiveProgress for the
// slot, and AdjustQuestObjectiveProgress seeds real progress, so there are no
// server-side per-objective counters left to copy here.
AddQuest(quest, this);
}
}
}
+4
View File
@@ -3973,6 +3973,10 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
int32 GetQuestObjectiveProgress(uint32 questId, int8 storageIndex) const; int32 GetQuestObjectiveProgress(uint32 questId, int8 storageIndex) const;
void PlayConversation(uint32 conversationId); void PlayConversation(uint32 conversationId);
void UpdateDynamicFlight(bool apply = false); void UpdateDynamicFlight(bool apply = false);
void UpdateWorldQuestPosition(float x, float y);
protected:
uint32 m_areaQuestTimer;
//WowCommunity //WowCommunity
}; };
+68
View File
@@ -12411,3 +12411,71 @@ bool ObjectMgr::HasEnabledSpellScript(uint32 spellId, std::string_view scriptNam
return script.second.second && GetScriptName(script.second.first) == scriptName; return script.second.second && GetScriptName(script.second.first) == scriptName;
}); });
} }
void ObjectMgr::LoadQuestTasks()
{
for (auto const& it : _questTemplates)
{
Quest const* quest_template = it.second.get();
//CriteriaEntry const* criteria = sCriteriaStore.LookupEntry(l_I);
//if (!criteria || criteria->Type != CRITERIA_TYPE_COMPLETE_QUEST)
// continue;
QuestV2CliTaskEntry const* cliTask = sQuestV2CliTaskStore.LookupEntry(quest_template->GetQuestId());
if (!cliTask)
continue;
if (!quest_template || quest_template->GetQuestType() != QUEST_TYPE_TASK)
continue;
if (QuestPOIData const* questPOIs = GetQuestPOIData(quest_template->GetQuestId()))
{
if (questPOIs->Blobs.size() <= 0)
continue;
for (auto itr = questPOIs->Blobs.begin(); itr != questPOIs->Blobs.end(); ++itr)
{
BonusQuestRectEntry rec;
rec.MapID = itr->MapID;
rec.MinX = 5000000.f;
rec.MinY = 5000000.f;
rec.MaxX = -5000000.f;
rec.MaxY = -5000000.f;
for (auto const& poi : itr->Points)
{
if (poi.X == 0 && poi.Y == 0)
continue;
rec.MaxX = std::max(rec.MaxX, poi.X);
rec.MaxY = std::max(rec.MaxY, poi.Y);
rec.MinX = std::min(rec.MinX, poi.X);
rec.MinY = std::min(rec.MinY, poi.Y);
}
// If the area is a single point, we assume 50m
if (itr->Points.size() == 1)
{
rec.MinX -= 50;
rec.MaxX += 50;
rec.MinY -= 50;
rec.MaxY += 50;
}
else
{
int32 areaWidth = std::abs(rec.MaxX - rec.MinX);
rec.MinX -= 0.175f * float(areaWidth);
rec.MaxX += 0.175f * float(areaWidth);
int32 areaHeight = std::abs(rec.MaxY - rec.MinY);
rec.MinY -= 0.175f * float(areaHeight);
rec.MaxY += 0.175f * float(areaHeight);
}
BonusQuestsRects[quest_template->GetQuestId()].push_back(rec);
}
}
}
}
+13
View File
@@ -1199,6 +1199,19 @@ class TC_GAME_API ObjectMgr
SpellScriptsBounds GetSpellScriptsBounds(uint32 spellId); SpellScriptsBounds GetSpellScriptsBounds(uint32 spellId);
//WowCommunity //WowCommunity
bool HasEnabledSpellScript(uint32 spellId, std::string_view scriptName); bool HasEnabledSpellScript(uint32 spellId, std::string_view scriptName);
void LoadQuestTasks();
struct BonusQuestRectEntry
{
int32 MinX, MinY, MaxX, MaxY;
uint32 MapID;
bool IsIn(uint32 mapID, int x, int y)
{
return MapID == mapID && MinX <= x && MaxX >= x && MinY <= y && MaxY >= y;
}
};
std::map<uint32, std::vector<BonusQuestRectEntry>> BonusQuestsRects;
//WowCommunity //WowCommunity
RepRewardRate const* GetRepRewardRate(uint32 factionId) const RepRewardRate const* GetRepRewardRate(uint32 factionId) const
{ {
+34
View File
@@ -165,3 +165,37 @@ void WorldQuestMgr::FillActiveWorldQuests(std::vector<WorldPackets::Quest::World
updates.emplace_back(active.StartTime, active.QuestID, remaining, active.VariableID, active.Value); updates.emplace_back(active.StartTime, active.QuestID, remaining, active.VariableID, active.Value);
} }
} }
TeamId WorldQuestMgr::GetQuestTeamId(Quest const* quest)
{
// Quest::GetAllowableRaces() is a RaceMask<int32, 2>, not a raw integer - RawValue is a
// std::array, so it cannot be compared/masked directly. Use the RaceMask operators with
// the matching <int32, 2> specialisations.
Trinity::RaceMask<int32, 2> const allowableRaces = quest->GetAllowableRaces();
// No race restriction at all: usable by both teams.
if (allowableRaces == RACEMASK_ALL_v<int32, 2>)
return TEAM_NEUTRAL;
if (!(allowableRaces & RACEMASK_ALLIANCE_v<int32, 2>).IsEmpty())
return TEAM_ALLIANCE;
if (!(allowableRaces & RACEMASK_HORDE_v<int32, 2>).IsEmpty())
return TEAM_HORDE;
return TEAM_NEUTRAL;
}
ActiveWorldQuest* WorldQuestMgr::GetActiveWorldQuest(uint32 questId)
{
auto const itr = _active.find(questId);
return itr != _active.end() ? &itr->second : nullptr;
}
uint32 WorldQuestMgr::GetTimerForQuest(uint32 questId)
{
if (ActiveWorldQuest const* worldQuest = GetActiveWorldQuest(questId))
return worldQuest->GetRemainingTime();
return 0;
}
+16
View File
@@ -19,10 +19,14 @@
#define TRINITY_WORLDQUESTMGR_H #define TRINITY_WORLDQUESTMGR_H
#include "Define.h" #include "Define.h"
#include "GameTime.h"
#include "SharedDefines.h"
#include <ctime> #include <ctime>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
class Quest;
namespace WorldPackets::Quest namespace WorldPackets::Quest
{ {
struct WorldQuestUpdateInfo; struct WorldQuestUpdateInfo;
@@ -45,6 +49,15 @@ struct ActiveWorldQuest
time_t EndTime = 0; // StartTime + template Duration time_t EndTime = 0; // StartTime + template Duration
int32 VariableID = 0; int32 VariableID = 0;
int32 Value = 0; int32 Value = 0;
// Remaining seconds until expiry, clamped at 0. Derived from EndTime (populated by
// WorldQuestMgr::Activate) rather than a template lookup, so this stays valid without
// needing WorldQuestMgr to be complete at this point in the header.
uint32 GetRemainingTime() const
{
time_t const now = GameTime::GetGameTime();
return EndTime > now ? uint32(EndTime - now) : 0;
}
}; };
// Activates world quests from `world_quest_template`, tracks their expiry, refreshes them on a // Activates world quests from `world_quest_template`, tracks their expiry, refreshes them on a
@@ -73,6 +86,9 @@ class TC_GAME_API WorldQuestMgr
// Appends every currently-active world quest to the SMSG_WORLD_QUEST_UPDATE_RESPONSE payload. // Appends every currently-active world quest to the SMSG_WORLD_QUEST_UPDATE_RESPONSE payload.
void FillActiveWorldQuests(std::vector<WorldPackets::Quest::WorldQuestUpdateInfo>& updates) const; void FillActiveWorldQuests(std::vector<WorldPackets::Quest::WorldQuestUpdateInfo>& updates) const;
ActiveWorldQuest* GetActiveWorldQuest(uint32 questId);
TeamId GetQuestTeamId(Quest const* quest);
uint32 GetTimerForQuest(uint32 questId);
bool IsWorldQuestActive(uint32 questId) const { return _active.find(questId) != _active.end(); } bool IsWorldQuestActive(uint32 questId) const { return _active.find(questId) != _active.end(); }
private: private:
+3
View File
@@ -2220,6 +2220,9 @@ bool World::SetInitialWorldSettings()
TC_LOG_INFO("server.loading", "Loading delve data..."); TC_LOG_INFO("server.loading", "Loading delve data...");
sDelveMgr->Initialize(); sDelveMgr->Initialize();
TC_LOG_INFO("server.loading", "Loading Quest task...");
sObjectMgr->LoadQuestTasks();
TC_LOG_INFO("server.loading", "Loading Perks Program vendor items..."); TC_LOG_INFO("server.loading", "Loading Perks Program vendor items...");
sPerksProgramMgr->LoadVendorItems(); sPerksProgramMgr->LoadVendorItems();
@@ -27,7 +27,13 @@ enum Spells
SPELL_NECROTIC_BOLT = 320170, SPELL_NECROTIC_BOLT = 320170,
SPELL_UNHOLY_FRENZY = 320012, SPELL_UNHOLY_FRENZY = 320012,
SPELL_NECROTIC_BREATH_TRIGGER = 337074, SPELL_NECROTIC_BREATH_TRIGGER = 337074,
EVENT_LAND_OF_THE_DEAD = 1, };
enum events
{
EVENT_NECROTIC_BOLT = 1,
EVENT_LAND_OF_THE_DEAD,
EVENT_FINAL_HARVEST,
EVENT_NECROTIC_BREATH EVENT_NECROTIC_BREATH
}; };
@@ -48,9 +54,9 @@ struct boss_amarth : public BossAI
{ {
_JustEngagedWith(who); _JustEngagedWith(who);
Talk(0); Talk(0);
events.ScheduleEvent(SPELL_NECROTIC_BOLT, 3s); events.ScheduleEvent(EVENT_NECROTIC_BOLT, 3s);
events.ScheduleEvent(EVENT_LAND_OF_THE_DEAD, 5s); events.ScheduleEvent(EVENT_LAND_OF_THE_DEAD, 5s);
events.ScheduleEvent(SPELL_FINAL_HARVEST, 8s); events.ScheduleEvent(EVENT_FINAL_HARVEST, 8s);
events.ScheduleEvent(EVENT_NECROTIC_BREATH, 10s); events.ScheduleEvent(EVENT_NECROTIC_BREATH, 10s);
if (Creature* amarth = me->FindNearestCreature(NPC_AMARTH, 30.0f, true)) if (Creature* amarth = me->FindNearestCreature(NPC_AMARTH, 30.0f, true))
amarth->EnterVehicle(me); amarth->EnterVehicle(me);
@@ -70,7 +76,7 @@ struct boss_amarth : public BossAI
{ {
switch (eventId) switch (eventId)
{ {
case SPELL_NECROTIC_BOLT: case EVENT_NECROTIC_BOLT:
DoCastVictim(SPELL_NECROTIC_BOLT, false); DoCastVictim(SPELL_NECROTIC_BOLT, false);
events.Repeat(15s); events.Repeat(15s);
break; break;
@@ -83,7 +89,7 @@ struct boss_amarth : public BossAI
events.Repeat(20s); events.Repeat(20s);
break; break;
case SPELL_FINAL_HARVEST: case EVENT_FINAL_HARVEST:
me->CastSpell(nullptr, SPELL_FINAL_HARVEST, false); me->CastSpell(nullptr, SPELL_FINAL_HARVEST, false);
events.Repeat(25s); events.Repeat(25s);
break; break;
@@ -47,7 +47,8 @@ enum Events
EVENT_SPELL_CAST_START = 1, EVENT_SPELL_CAST_START = 1,
EVENT_SPELL_PERIODIC_DAMAGE, EVENT_SPELL_PERIODIC_DAMAGE,
EVENT_SPELL_PERIODIC_MISSED, EVENT_SPELL_PERIODIC_MISSED,
EVENT_UNIT_SPELLCAST_START EVENT_UNIT_SPELLCAST_START,
EVENT_BLOOD_GORGE
}; };
//162691 //162691
@@ -184,7 +185,7 @@ struct npc_carrion_worm_nw : public ScriptedAI
void JustEngagedWith(Unit* /*who*/) override void JustEngagedWith(Unit* /*who*/) override
{ {
_events.ScheduleEvent(SPELL_BLOOD_GORGE, 3s); _events.ScheduleEvent(EVENT_BLOOD_GORGE, 3s);
} }
void ExecuteEvent(uint32 eventId) override void ExecuteEvent(uint32 eventId) override
@@ -198,7 +199,7 @@ struct npc_carrion_worm_nw : public ScriptedAI
switch (eventId) switch (eventId)
{ {
case SPELL_BLOOD_GORGE: case EVENT_BLOOD_GORGE:
DoCastVictim(SPELL_BLOOD_GORGE, false); DoCastVictim(SPELL_BLOOD_GORGE, false);
me->AddAura(SPELL_BLOOD_GORGE_BUFF, me); me->AddAura(SPELL_BLOOD_GORGE_BUFF, me);
_events.Repeat(10s); _events.Repeat(10s);
@@ -51,16 +51,6 @@ public:
if (!leaderguid) if (!leaderguid)
return; return;
if (Player* leader = ObjectAccessor::GetPlayer(*player, leaderguid))
{
if (leader->GetTeam() == HORDE)
faction_group = HORDE;
else if (leader->GetTeam() == ALLIANCE)
faction_group = ALLIANCE;
else
faction_group = HORDE;
}
player->GetGroup()->AddFollowerModeBots(player); player->GetGroup()->AddFollowerModeBots(player);
} }
@@ -49,16 +49,6 @@ public:
if (!leaderguid) if (!leaderguid)
return; return;
if (Player* leader = ObjectAccessor::GetPlayer(*player, leaderguid))
{
if (leader->GetTeam() == HORDE)
faction_group = HORDE;
else if (leader->GetTeam() == ALLIANCE)
faction_group = ALLIANCE;
else
faction_group = HORDE;
}
player->GetGroup()->AddFollowerModeBots(player); player->GetGroup()->AddFollowerModeBots(player);
} }
@@ -54,16 +54,6 @@ public:
if (!leaderguid) if (!leaderguid)
return; return;
if (Player* leader = ObjectAccessor::GetPlayer(*player, leaderguid))
{
if (leader->GetTeam() == HORDE)
faction_group = HORDE;
else if (leader->GetTeam() == ALLIANCE)
faction_group = ALLIANCE;
else
faction_group = HORDE;
}
player->GetGroup()->AddFollowerModeBots(player); player->GetGroup()->AddFollowerModeBots(player);
} }