world quest part 1
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
DROP TABLE `world_quest`;
|
||||
CREATE TABLE IF NOT EXISTS `world_quest` (
|
||||
`questId` INT UNSIGNED NOT NULL DEFAULT '0',
|
||||
`rewardId` INT UNSIGNED NOT NULL DEFAULT '0',
|
||||
`startTime` INT UNSIGNED NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`questId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Create indexes for better performance
|
||||
ALTER TABLE `world_quest` ADD INDEX `idx_world_quest_start_time` (`startTime`);
|
||||
ALTER TABLE `world_quest` ADD INDEX `idx_world_quest_reward_id` (`rewardId`);
|
||||
@@ -0,0 +1,140 @@
|
||||
-- Conversation
|
||||
DELETE FROM `conversation_template` WHERE `Id`=28287;
|
||||
INSERT INTO `conversation_template` (`Id`, `FirstLineID`, `TextureKitId`, `VerifiedBuild`) VALUES
|
||||
(28287, 77763, 0, 65893);
|
||||
|
||||
UPDATE `conversation_template` SET `ScriptName` = 'conversation_after_vereesas_ceremony' WHERE `Id` = 28287;
|
||||
|
||||
DELETE FROM `conversation_actors` WHERE (`ConversationId`=28287 AND `Idx` IN (1,0));
|
||||
INSERT INTO `conversation_actors` (`ConversationId`, `ConversationActorId`, `ConversationActorGuid`, `Idx`, `CreatureId`, `CreatureDisplayInfoId`, `NoActorObject`, `ActivePlayerObject`, `VerifiedBuild`) VALUES
|
||||
(28287, 103076, 0, 1, 0, 0, 0, 0, 65893), -- Full: 0x2042613F00E3DC4001F632000012E1AB Creature/0 R4248/S128562 Map: 2552 (Khaz Algar (Surface)) Entry: 233329 (Silver Hand Avenger) Low: 1237419
|
||||
(28287, 103060, 10006960, 0, 0, 0, 0, 0, 65893); -- Full: 0x2042613F00E3D88001F63200001183E4 Creature/0 R4248/S128562 Map: 2552 (Khaz Algar (Surface)) Entry: 233314 (Vereesa Windrunner) Low: 1147876
|
||||
|
||||
DELETE FROM `conversation_line_template` WHERE `Id` IN (77764, 77763);
|
||||
INSERT INTO `conversation_line_template` (`Id`, `UiCameraID`, `ActorIdx`, `Flags`, `ChatType`, `VerifiedBuild`) VALUES
|
||||
(77764, 0, 1, 0, 0, 65893),
|
||||
(77763, 0, 0, 0, 0, 65893);
|
||||
|
||||
-- Condition
|
||||
DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId`=26 AND `SourceGroup` = 25036 AND `SourceEntry` = 0);
|
||||
INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `Comment`) VALUE
|
||||
(26, 25036, 0, 0, 0, 47, 0, 84997, 2|64, 0, 0, 'Apply Phase 25036 if Quest 84997 is complete|rewarded'),
|
||||
(26, 25036, 0, 0, 0, 47, 0, 85001, 2|8|64, 0, 1, 'Apply Phase 25036 if Quest 84997 is not complete|in progress|rewarded');
|
||||
|
||||
-- Quest
|
||||
DELETE FROM `quest_request_items` WHERE `ID` IN (85001 /*Blessings Be Upon You*/, 84998 /*Bringer of the Void*/);
|
||||
INSERT INTO `quest_request_items` (`ID`, `EmoteOnComplete`, `EmoteOnIncomplete`, `EmoteOnCompleteDelay`, `EmoteOnIncompleteDelay`, `CompletionText`, `VerifiedBuild`) VALUES
|
||||
(85001, 0, 5, 0, 0, 'These poor souls are imperiled through no fault of their own!', 65893), -- Blessings Be Upon You
|
||||
(84998, 0, 0, 0, 0, 'Why does this... void creature want... us dead?', 65893); -- Bringer of the Void
|
||||
|
||||
DELETE FROM `creature_queststarter` WHERE (`id`=233312 AND `quest`=85001) OR (`id`=233314 AND `quest`=84998);
|
||||
INSERT INTO `creature_queststarter` (`id`, `quest`, `VerifiedBuild`) VALUES
|
||||
(233312, 85001, 65893), -- Blessings Be Upon You offered by Arator
|
||||
(233314, 84998, 65893); -- Bringer of the Void offered by Vereesa Windrunner
|
||||
|
||||
DELETE FROM `creature_questender` WHERE (`id`=233329 AND `quest`=85001) OR (`id`=233314 AND `quest`=84998);
|
||||
INSERT INTO `creature_questender` (`id`, `quest`, `VerifiedBuild`) VALUES
|
||||
(233329, 85001, 65893), -- Blessings Be Upon You ended by Silver Hand Avenger
|
||||
(233314, 84998, 65893); -- Bringer of the Void ended by Vereesa Windrunner
|
||||
|
||||
DELETE FROM `quest_template_addon` WHERE `ID` IN (85001,84998);
|
||||
INSERT INTO `quest_template_addon` (`ID`, `MaxLevel`, `AllowableClasses`, `SourceSpellID`, `PrevQuestID`, `NextQuestID`, `ExclusiveGroup`, `BreadcrumbForQuestId`, `RewardMailTemplateID`, `RewardMailDelay`, `RequiredSkillID`, `RequiredSkillPoints`, `RequiredMinRepFaction`, `RequiredMaxRepFaction`, `RequiredMinRepValue`, `RequiredMaxRepValue`, `ProvidedItemCount`, `SpecialFlags`, `ScriptName`) VALUES
|
||||
(85001, 0, 0, 0, 84997, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ''),
|
||||
(84998, 0, 0, 0, 84997, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '');
|
||||
|
||||
-- SpellArea
|
||||
DELETE FROM `spell_area` WHERE `spell` = 470452 AND `area` = 15943;
|
||||
INSERT INTO `spell_area` (`spell`, `area`, `quest_start`, `quest_end`, `aura_spell`, `racemask`, `gender`, `flags`, `quest_start_status`, `quest_end_status`) VALUES
|
||||
(470452, 15943, 85001, 85002, 0, 0, 2, 3, 74, 11);
|
||||
|
||||
-- Creature Summon data
|
||||
DELETE FROM `creature_summoned_data` WHERE `CreatureID` = 233329;
|
||||
INSERT INTO `creature_summoned_data` (`CreatureID`, `CreatureIDVisibleToSummoner`, `GroundMountDisplayID`, `FlyingMountDisplayID`, `DespawnOnQuestsRemoved`) VALUES
|
||||
(233329, 233312, NULL, NULL, NULL);
|
||||
|
||||
-- Arator smart ai
|
||||
SET @ENTRY := 233312;
|
||||
UPDATE `creature_template` SET `AIName` = 'SmartAI', `ScriptName` = '' WHERE `entry` = @ENTRY;
|
||||
DELETE FROM `smart_scripts` WHERE `source_type` = 0 AND `entryOrGuid` = @ENTRY;
|
||||
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`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `action_param7`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`, `Difficulties`) VALUES
|
||||
(@ENTRY, 0, 0, 0, 19, 0, 100, 0, 85001, 0, 0, 0, 0, 143, 28287, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 'On player accepted quest 85001 - Player who accepted quest: Start conversation (28287) Actors<> (first line 77763)', '');
|
||||
|
||||
DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId` = 22 AND `SourceEntry` = 233312 AND `SourceId` = 0;
|
||||
INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `ConditionStringValue1`, `NegativeCondition`, `Comment`) VALUES
|
||||
(22, 1, 233312, 0, 0, 47, 0, 84998, 8, 0, '', 0, 'Action invoker has Bringer of the Void (84998) in state incomplete');
|
||||
|
||||
-- Vereesa Windrunner smart ai
|
||||
SET @ENTRY := 233314;
|
||||
UPDATE `creature_template` SET `AIName` = 'SmartAI', `ScriptName` = '' WHERE `entry` = @ENTRY;
|
||||
DELETE FROM `smart_scripts` WHERE `source_type` = 0 AND `entryOrGuid` = @ENTRY;
|
||||
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`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `action_param7`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`, `Difficulties`) VALUES
|
||||
(@ENTRY, 0, 0, 0, 19, 0, 100, 0, 84998, 0, 0, 0, 0, 143, 28287, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 'On player accepted quest Bringer of the Void (84998) - Player who accepted quest: Start conversation (28287) Actors<> (first line 77763)', '');
|
||||
|
||||
DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId` = 22 AND `SourceEntry` = 233314 AND `SourceId` = 0;
|
||||
INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `ConditionStringValue1`, `NegativeCondition`, `Comment`) VALUES
|
||||
(22, 1, 233314, 0, 0, 47, 0, 85001, 8, 0, '', 0, 'Action invoker has Blessings Be Upon You (85001) in state incomplete');
|
||||
|
||||
-- Silver Hand Avenger smart ai
|
||||
SET @ENTRY := 233329;
|
||||
UPDATE `creature_template` SET `AIName` = 'SmartAI', `ScriptName` = '' WHERE `entry` = @ENTRY;
|
||||
DELETE FROM `smart_scripts` WHERE `source_type` = 0 AND `entryOrGuid` = @ENTRY;
|
||||
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`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `action_param7`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`, `Difficulties`) VALUES
|
||||
(@ENTRY, 0, 0, 0, 54, 0, 100, 0, 0, 0, 0, 0, 0, 85, 470456, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 'On just summoned - Self: Cast spell 470456 on self', '');
|
||||
|
||||
DELETE FROM `spell_script_names` WHERE `spell_id`=436854 AND `ScriptName`='spell_switch_flight';
|
||||
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (436854, 'spell_switch_flight');
|
||||
|
||||
UPDATE `creature_template` SET `ScriptName`='npc_scarlet_corpse_49340' WHERE `entry`=49340;
|
||||
|
||||
-- =============================================================================
|
||||
-- Apply ONLY to the HOTFIXES database for TrinityCore master branch!
|
||||
-- World Quests are handled via hotfixes DB (DB2/hotfix mirrors) in retail-like emulation.
|
||||
--
|
||||
-- WARNING: DROPPING these tables will DELETE any existing data/hotfixes!
|
||||
-- Only run if your hotfixes DB is for testing or empty.
|
||||
-- For production: Use ALTER TABLE to add/modify columns instead.
|
||||
--
|
||||
-- First apply latest official TDB_hotfixes_full_*.sql from TrinityCore releases.
|
||||
-- Then run this as a custom update.
|
||||
-- =============================================================================
|
||||
|
||||
-- World Quest base definitions
|
||||
DROP TABLE IF EXISTS `world_quest`;
|
||||
CREATE TABLE IF NOT EXISTS `world_quest` (
|
||||
`id` INT UNSIGNED NOT NULL DEFAULT '0',
|
||||
`duration` INT UNSIGNED NOT NULL DEFAULT '0',
|
||||
`variable` INT UNSIGNED NOT NULL DEFAULT '0',
|
||||
`value` TINYINT UNSIGNED NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='World Quest definitions (hotfix table)';
|
||||
|
||||
-- World Quest Rewards
|
||||
DROP TABLE IF EXISTS `world_quest_reward`;
|
||||
CREATE TABLE IF NOT EXISTS `world_quest_reward` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`QuestType` INT UNSIGNED NOT NULL DEFAULT '0' COMMENT 'QuestInfoID from QuestInfo.db2',
|
||||
`RewardType` TINYINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '0=Item, 1=Currency, 2=Gold, etc.',
|
||||
`RewardID` INT UNSIGNED NOT NULL DEFAULT '0' COMMENT 'ItemID, CurrencyID, or 0',
|
||||
`RewardCount` INT UNSIGNED NOT NULL DEFAULT '0',
|
||||
`RewardContext` INT UNSIGNED NOT NULL DEFAULT '0' COMMENT 'Item context/bonus flags',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `QuestType` (`QuestType`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='World Quest reward definitions (hotfix table)';
|
||||
|
||||
-- World Quest Area/Zone Tasks (mapping)
|
||||
DROP TABLE IF EXISTS `world_quest_area_task`;
|
||||
CREATE TABLE IF NOT EXISTS `world_quest_area_task` (
|
||||
`questId` INT UNSIGNED NOT NULL DEFAULT '0',
|
||||
`areaId` INT UNSIGNED NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`questId`, `areaId`),
|
||||
KEY `areaId` (`areaId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='World Quest to zone/area assignments';
|
||||
|
||||
-- Custom: Reward linking by Quest Info (not in official TC)
|
||||
DROP TABLE IF EXISTS `world_quest_reward_by_quest_info`;
|
||||
CREATE TABLE IF NOT EXISTS `world_quest_reward_by_quest_info` (
|
||||
`questInfoId` INT UNSIGNED NOT NULL DEFAULT '0' COMMENT 'From QuestInfo.db2',
|
||||
`rewardId` INT UNSIGNED NOT NULL DEFAULT '0' COMMENT 'Link to world_quest_reward.id or direct',
|
||||
PRIMARY KEY (`questInfoId`, `rewardId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Custom: Reward linking by quest type';
|
||||
|
||||
@@ -510,10 +510,13 @@ float DispelCoordinator::DebuffData::GetAdjustedPriority(Unit* target) const
|
||||
stackCount = aura->GetStackAmount();
|
||||
|
||||
// Count nearby allies and analyze clustering
|
||||
for (GroupReference& itr : group->GetMembers())
|
||||
if (!group || !group->GetMembersCount())
|
||||
return priority;
|
||||
|
||||
for (GroupReference const& itr : group->GetMembers())
|
||||
{
|
||||
Player* member = itr.GetSource();
|
||||
if (!member || member == targetPlayer || member->isDead())
|
||||
if (!member || member == targetPlayer || member->isDead() || !member->IsInWorld())
|
||||
continue;
|
||||
|
||||
float distSq = target->GetExactDistSq(member);
|
||||
@@ -683,15 +686,20 @@ void DispelCoordinator::UpdateDispellerCapabilities()
|
||||
|
||||
m_dispellers.clear();
|
||||
|
||||
for (GroupReference& itr : m_group->GetMembers())
|
||||
// Additional validation to prevent crashes with corrupted group data
|
||||
if (!m_group->GetMembersCount())
|
||||
return;
|
||||
|
||||
for (GroupReference const& itr : m_group->GetMembers())
|
||||
{
|
||||
Player* member = itr.GetSource();
|
||||
if (!member || member->isDead())
|
||||
if (!member || member->isDead() || !member->IsInWorld())
|
||||
continue;
|
||||
|
||||
DispellerCapability cap;
|
||||
cap.botGuid = member->GetGUID();
|
||||
cap.botClass = static_cast<Classes>(member->GetClass());
|
||||
|
||||
cap.canDispel = GetClassDispelTypes(cap.botClass);
|
||||
|
||||
if (cap.canDispel.empty())
|
||||
@@ -1043,13 +1051,13 @@ bool DispelCoordinator::ExecutePurge()
|
||||
{
|
||||
::std::vector<DebuffTarget> debuffs;
|
||||
|
||||
if (!m_group)
|
||||
if (!m_group || !m_group->GetMembersCount())
|
||||
return debuffs;
|
||||
|
||||
for (GroupReference& itr : m_group->GetMembers())
|
||||
for (GroupReference const& itr : m_group->GetMembers())
|
||||
{
|
||||
Player* member = itr.GetSource();
|
||||
if (!member || member->isDead())
|
||||
if (!member || member->isDead() || !member->IsInWorld())
|
||||
continue;
|
||||
|
||||
// Check all auras
|
||||
@@ -1201,10 +1209,10 @@ uint32 DispelCoordinator::GetNearbyAlliesCount(Unit* center, float radius) const
|
||||
return 0;
|
||||
|
||||
uint32 count = 0;
|
||||
for (GroupReference& itr : m_group->GetMembers())
|
||||
for (GroupReference const& itr : m_group->GetMembers())
|
||||
{
|
||||
Player* member = itr.GetSource();
|
||||
if (!member || member == center || member->isDead())
|
||||
if (!member || member == center || member->isDead() || !member->IsInWorld())
|
||||
continue;
|
||||
|
||||
float radiusSq = radius * radius;
|
||||
@@ -1217,13 +1225,13 @@ uint32 DispelCoordinator::GetNearbyAlliesCount(Unit* center, float radius) const
|
||||
|
||||
bool DispelCoordinator::IsTankTakingDamage() const
|
||||
{
|
||||
if (!m_group)
|
||||
if (!m_group || !m_group->GetMembersCount())
|
||||
return false;
|
||||
|
||||
for (GroupReference& itr : m_group->GetMembers())
|
||||
for (GroupReference const& itr : m_group->GetMembers())
|
||||
{
|
||||
Player* member = itr.GetSource();
|
||||
if (!member || !IsTank(member))
|
||||
if (!member || !IsTank(member) || !member->IsInWorld())
|
||||
continue;
|
||||
|
||||
// Simple check - tank below 70% health
|
||||
|
||||
@@ -2546,14 +2546,16 @@ void WorldSession::HandleHousingSvcsNeighborhoodReservePlot(WorldPackets::Housin
|
||||
neighborhood->UpdatePlotHouseInfo(plotIndex, housing->GetHouseGuid(), GetBattlenetAccountGUID());
|
||||
|
||||
// Spawn house GO and MeshObjects
|
||||
int32 extCompID = static_cast<int32>(housing->GetCoreExteriorComponentID());
|
||||
int32 wmoDataID = static_cast<int32>(housing->GetHouseType());
|
||||
GameObject* houseGo = nullptr;
|
||||
if (housing->HasCustomPosition())
|
||||
{
|
||||
Position customPos = housing->GetHousePosition();
|
||||
houseGo = housingMap->SpawnHouseForPlot(plotIndex, &customPos);
|
||||
houseGo = housingMap->SpawnHouseForPlot(plotIndex, &customPos, extCompID, wmoDataID);
|
||||
}
|
||||
else
|
||||
houseGo = housingMap->SpawnHouseForPlot(plotIndex);
|
||||
houseGo = housingMap->SpawnHouseForPlot(plotIndex, nullptr, extCompID, wmoDataID);
|
||||
|
||||
TC_LOG_ERROR("housing", "HandleHousingSvcsNeighborhoodReservePlot: SpawnHouseForPlot for plot {}: {}",
|
||||
plotIndex, houseGo ? houseGo->GetGUID().ToString() : "FAILED");
|
||||
|
||||
@@ -1255,10 +1255,13 @@ void WorldSession::HandleNeighborhoodBuyHouse(WorldPackets::Neighborhood::Neighb
|
||||
{
|
||||
housingMap->SetPlotOwnershipState(resolvedPlotIndex, true);
|
||||
|
||||
// Spawn the house structure GO at DB2 default position
|
||||
// Uses sniff-verified defaults: ExteriorComponentID=141, WmoDataID=9
|
||||
TC_LOG_ERROR("housing", "HandleNeighborhoodBuyHouse: Calling SpawnHouseForPlot for plot {}", resolvedPlotIndex);
|
||||
GameObject* houseGo = housingMap->SpawnHouseForPlot(resolvedPlotIndex);
|
||||
// Spawn the house using the player's Housing data
|
||||
Housing const* buyHousing = player->GetHousing();
|
||||
int32 buyExtCompID = buyHousing ? static_cast<int32>(buyHousing->GetCoreExteriorComponentID()) : 0;
|
||||
int32 buyWmoDataID = buyHousing ? static_cast<int32>(buyHousing->GetHouseType()) : 0;
|
||||
TC_LOG_ERROR("housing", "HandleNeighborhoodBuyHouse: Calling SpawnHouseForPlot for plot {} (extComp={}, wmoData={})",
|
||||
resolvedPlotIndex, buyExtCompID, buyWmoDataID);
|
||||
GameObject* houseGo = housingMap->SpawnHouseForPlot(resolvedPlotIndex, nullptr, buyExtCompID, buyWmoDataID);
|
||||
TC_LOG_ERROR("housing", "HandleNeighborhoodBuyHouse: SpawnHouseForPlot result: {}",
|
||||
houseGo ? houseGo->GetGUID().ToString() : "FAILED/NULL");
|
||||
}
|
||||
@@ -1435,7 +1438,9 @@ void WorldSession::HandleNeighborhoodMoveHouse(WorldPackets::Neighborhood::Neigh
|
||||
fixtureOverrides.empty() ? nullptr : &fixtureOverrides);
|
||||
}
|
||||
else
|
||||
housingMap->SpawnHouseForPlot(targetPlotIndex);
|
||||
{
|
||||
TC_LOG_ERROR("housing", "HandleNeighborhoodMoveHouse: No Housing object for player ? cannot spawn house at plot {}", targetPlotIndex);
|
||||
}
|
||||
}
|
||||
|
||||
if (Housing const* housing = player->GetHousing())
|
||||
|
||||
@@ -1822,20 +1822,47 @@ std::unordered_map<uint32, uint32> Housing::GetFixtureOverrideMap() const
|
||||
|
||||
uint32 Housing::GetCoreExteriorComponentID() const
|
||||
{
|
||||
// The core fixture is a BASE component set via SetCoreFixture (OptionId == 0).
|
||||
// Only Type=9 (Base) components count ? roof/door/window core changes are
|
||||
// handled as root overrides in GetFixtureOverrideMap() instead.
|
||||
// The core fixture is the primary component set via SetCoreFixture (OptionId == 0).
|
||||
// It can be any root type ? Base (9) for Alliance, or different types for Horde.
|
||||
for (auto const& [pointId, fixture] : _fixtures)
|
||||
{
|
||||
if (fixture.OptionId == 0)
|
||||
{
|
||||
ExteriorComponentEntry const* comp = sExteriorComponentStore.LookupEntry(fixture.FixturePointId);
|
||||
if (comp && comp->Type == 9) // Type 9 = Base
|
||||
// Only return this fixture if it matches the current house type's WMO data.
|
||||
// When the player switches house type, old fixtures from the previous type
|
||||
// must not override the new type's default component.
|
||||
if (comp && comp->ParentComponentID == 0 && (_houseType == 0 || comp->HouseExteriorWmoDataID == _houseType))
|
||||
return fixture.FixturePointId;
|
||||
}
|
||||
}
|
||||
// No explicit base fixture set ? return faction-appropriate default.
|
||||
return (_houseType == 87) ? 3805 : 141;
|
||||
// No explicit core fixture set ? find first default root component for this house's WMO data ID.
|
||||
if (_houseType > 0)
|
||||
{
|
||||
auto const* roots = sHousingMgr.GetRootComponentsForWmoData(static_cast<uint32>(_houseType));
|
||||
if (roots)
|
||||
{
|
||||
uint32 fallbackComp = 0;
|
||||
for (uint32 compID : *roots)
|
||||
{
|
||||
ExteriorComponentEntry const* comp = sExteriorComponentStore.LookupEntry(compID);
|
||||
if (!comp)
|
||||
continue;
|
||||
if (!fallbackComp)
|
||||
fallbackComp = compID;
|
||||
if (comp->Flags & 0x1) // IsDefault
|
||||
return compID;
|
||||
}
|
||||
if (fallbackComp)
|
||||
return fallbackComp;
|
||||
}
|
||||
TC_LOG_ERROR("housing", "Housing::GetCoreExteriorComponentID: No root component found for houseType={}", _houseType);
|
||||
}
|
||||
else
|
||||
{
|
||||
TC_LOG_ERROR("housing", "Housing::GetCoreExteriorComponentID: No fixtures and houseType=0 ? cannot determine base component");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
HousingResult Housing::AddToCatalog(uint32 decorEntryId, uint8 sourceType, std::string sourceValue)
|
||||
|
||||
@@ -366,21 +366,24 @@ void HousingMap::SpawnPlotGameObjects()
|
||||
plotIdx, plotInfo->OwnerGuid.ToString(),
|
||||
plot->HousePosition[0], plot->HousePosition[1], plot->HousePosition[2]);
|
||||
|
||||
// Try to get persisted position and fixture data from the player's Housing object (if online)
|
||||
// Get exterior component and WMO data from the player's Housing object
|
||||
Housing* housing = GetHousingForPlayer(plotInfo->OwnerGuid);
|
||||
int32 exteriorComponentID = 141; // Default: Stucco Base
|
||||
int32 houseExteriorWmoDataID = 9; // Default: Human theme
|
||||
if (housing)
|
||||
if (!housing)
|
||||
{
|
||||
uint32 coreComponent = housing->GetCoreExteriorComponentID();
|
||||
if (coreComponent)
|
||||
exteriorComponentID = static_cast<int32>(coreComponent);
|
||||
uint32 houseType = housing->GetHouseType();
|
||||
if (houseType)
|
||||
houseExteriorWmoDataID = static_cast<int32>(houseType);
|
||||
TC_LOG_DEBUG("housing", "HousingMap::SpawnPlotGameObjects: Plot {} using persisted fixture data: ExteriorComponentID={}, WmoDataID={}",
|
||||
plotIdx, exteriorComponentID, houseExteriorWmoDataID);
|
||||
TC_LOG_ERROR("housing", "HousingMap::SpawnPlotGameObjects: Plot {} owned by {} but Housing object not found ? cannot spawn house",
|
||||
plotIdx, plotInfo->OwnerGuid.ToString());
|
||||
continue;
|
||||
}
|
||||
int32 exteriorComponentID = static_cast<int32>(housing->GetCoreExteriorComponentID());
|
||||
int32 houseExteriorWmoDataID = static_cast<int32>(housing->GetHouseType());
|
||||
if (!exteriorComponentID || !houseExteriorWmoDataID)
|
||||
{
|
||||
TC_LOG_ERROR("housing", "HousingMap::SpawnPlotGameObjects: Plot {} has invalid data: ExteriorComponentID={}, WmoDataID={}",
|
||||
plotIdx, exteriorComponentID, houseExteriorWmoDataID);
|
||||
continue;
|
||||
}
|
||||
TC_LOG_DEBUG("housing", "HousingMap::SpawnPlotGameObjects: Plot {} using ExteriorComponentID={}, WmoDataID={}",
|
||||
plotIdx, exteriorComponentID, houseExteriorWmoDataID);
|
||||
|
||||
// Build fixture override map from player's saved fixture selections
|
||||
FixtureOverrideMap fixtureOverrides;
|
||||
@@ -658,33 +661,34 @@ bool HousingMap::AddPlayerToMap(Player* player, bool initPlayer /*= true*/)
|
||||
|
||||
if (!alreadySpawned)
|
||||
{
|
||||
// Read persisted fixture data from Housing
|
||||
int32 exteriorComponentID = 141; // Default: Stucco Base
|
||||
int32 houseExteriorWmoDataID = 9; // Default: Human theme
|
||||
uint32 coreComponent = housing->GetCoreExteriorComponentID();
|
||||
if (coreComponent)
|
||||
exteriorComponentID = static_cast<int32>(coreComponent);
|
||||
uint32 houseType = housing->GetHouseType();
|
||||
if (houseType)
|
||||
houseExteriorWmoDataID = static_cast<int32>(houseType);
|
||||
|
||||
TC_LOG_DEBUG("housing", "HousingMap::AddPlayerToMap: Plot {} spawning house with ExteriorComponentID={}, WmoDataID={}",
|
||||
plotIdx, exteriorComponentID, houseExteriorWmoDataID);
|
||||
|
||||
auto fixtureOverrides = housing->GetFixtureOverrideMap();
|
||||
FixtureOverrideMap const* overridesPtr = fixtureOverrides.empty() ? nullptr : &fixtureOverrides;
|
||||
|
||||
GameObject* go = nullptr;
|
||||
if (housing->HasCustomPosition())
|
||||
// Read exterior data from Housing ? no hardcoded fallbacks
|
||||
int32 exteriorComponentID = static_cast<int32>(housing->GetCoreExteriorComponentID());
|
||||
int32 houseExteriorWmoDataID = static_cast<int32>(housing->GetHouseType());
|
||||
if (!exteriorComponentID || !houseExteriorWmoDataID)
|
||||
{
|
||||
Position customPos = housing->GetHousePosition();
|
||||
go = SpawnHouseForPlot(plotIdx, &customPos, exteriorComponentID, houseExteriorWmoDataID, overridesPtr);
|
||||
TC_LOG_ERROR("housing", "HousingMap::AddPlayerToMap: Plot {} has invalid data: ExteriorComponentID={}, WmoDataID={} ? skipping spawn",
|
||||
plotIdx, exteriorComponentID, houseExteriorWmoDataID);
|
||||
}
|
||||
else
|
||||
go = SpawnHouseForPlot(plotIdx, nullptr, exteriorComponentID, houseExteriorWmoDataID, overridesPtr);
|
||||
{
|
||||
TC_LOG_DEBUG("housing", "HousingMap::AddPlayerToMap: Plot {} spawning house with ExteriorComponentID={}, WmoDataID={}",
|
||||
plotIdx, exteriorComponentID, houseExteriorWmoDataID);
|
||||
|
||||
TC_LOG_DEBUG("housing", "HousingMap::AddPlayerToMap: SpawnHouseForPlot result for plot {}: {}",
|
||||
plotIdx, go ? go->GetGUID().ToString() : "FAILED");
|
||||
auto fixtureOverrides = housing->GetFixtureOverrideMap();
|
||||
FixtureOverrideMap const* overridesPtr = fixtureOverrides.empty() ? nullptr : &fixtureOverrides;
|
||||
|
||||
GameObject* go = nullptr;
|
||||
if (housing->HasCustomPosition())
|
||||
{
|
||||
Position customPos = housing->GetHousePosition();
|
||||
go = SpawnHouseForPlot(plotIdx, &customPos, exteriorComponentID, houseExteriorWmoDataID, overridesPtr);
|
||||
}
|
||||
else
|
||||
go = SpawnHouseForPlot(plotIdx, nullptr, exteriorComponentID, houseExteriorWmoDataID, overridesPtr);
|
||||
|
||||
TC_LOG_DEBUG("housing", "HousingMap::AddPlayerToMap: SpawnHouseForPlot result for plot {}: {}",
|
||||
plotIdx, go ? go->GetGUID().ToString() : "FAILED");
|
||||
} // else (valid exteriorComponentID && houseExteriorWmoDataID)
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -706,15 +710,8 @@ bool HousingMap::AddPlayerToMap(Player* player, bool initPlayer /*= true*/)
|
||||
int32 faction = _neighborhood ? _neighborhood->GetFactionRestriction()
|
||||
: NEIGHBORHOOD_FACTION_ALLIANCE;
|
||||
|
||||
// Use actual housing data, not hardcoded defaults
|
||||
int32 lateExtCompID = 141;
|
||||
int32 lateWmoDataID = 9;
|
||||
uint32 lateCoreComp = housing->GetCoreExteriorComponentID();
|
||||
if (lateCoreComp)
|
||||
lateExtCompID = static_cast<int32>(lateCoreComp);
|
||||
uint32 lateHouseType = housing->GetHouseType();
|
||||
if (lateHouseType)
|
||||
lateWmoDataID = static_cast<int32>(lateHouseType);
|
||||
int32 lateExtCompID = static_cast<int32>(housing->GetCoreExteriorComponentID());
|
||||
int32 lateWmoDataID = static_cast<int32>(housing->GetHouseType());
|
||||
|
||||
SpawnFullHouseMeshObjects(plotIdx, pos, rot,
|
||||
housing->GetHouseGuid(), lateExtCompID, lateWmoDataID, faction);
|
||||
@@ -1508,8 +1505,8 @@ void HousingMap::RemovePlayerHousing(ObjectGuid playerGuid)
|
||||
// House Structure GO Management
|
||||
// ============================================================
|
||||
|
||||
GameObject* HousingMap::SpawnHouseForPlot(uint8 plotIndex, Position const* customPos /*= nullptr*/,
|
||||
int32 exteriorComponentID /*= 141*/, int32 houseExteriorWmoDataID /*= 9*/,
|
||||
GameObject* HousingMap::SpawnHouseForPlot(uint8 plotIndex, Position const* customPos,
|
||||
int32 exteriorComponentID, int32 houseExteriorWmoDataID,
|
||||
FixtureOverrideMap const* fixtureOverrides /*= nullptr*/)
|
||||
{
|
||||
if (!_neighborhood)
|
||||
|
||||
@@ -62,8 +62,8 @@ public:
|
||||
|
||||
// House structure GO management
|
||||
// Sniff-verified defaults: ExteriorComponentID=141 (Stucco Base), HouseExteriorWmoDataID=9 (Human theme)
|
||||
GameObject* SpawnHouseForPlot(uint8 plotIndex, Position const* customPos = nullptr,
|
||||
int32 exteriorComponentID = 141, int32 houseExteriorWmoDataID = 9,
|
||||
GameObject* SpawnHouseForPlot(uint8 plotIndex, Position const* customPos,
|
||||
int32 exteriorComponentID, int32 houseExteriorWmoDataID,
|
||||
FixtureOverrideMap const* fixtureOverrides = nullptr);
|
||||
void DespawnHouseForPlot(uint8 plotIndex);
|
||||
GameObject* GetHouseGameObject(uint8 plotIndex);
|
||||
|
||||
@@ -626,6 +626,8 @@ class TC_GAME_API Quest
|
||||
|
||||
bool IsAutoPush() const { return HasFlagEx(QUEST_FLAGS_EX_AUTO_PUSH); }
|
||||
bool IsWorldQuest() const { return HasFlagEx(QUEST_FLAGS_EX_IS_WORLD_QUEST); }
|
||||
bool IsEmissaryQuest() const { return _questInfoID == QUEST_INFO_EMISSARY_QUEST; }
|
||||
bool IsCallingQuest() const { return _questInfoID == QUEST_INFO_CALLING_QUEST; }
|
||||
|
||||
// Possibly deprecated flag
|
||||
bool IsUnavailable() const { return HasFlag(QUEST_FLAGS_DEPRECATED); }
|
||||
|
||||
@@ -0,0 +1,658 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
* ...
|
||||
*/
|
||||
|
||||
#include "WorldQuestMgr.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "DB2Structure.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Player.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "WorldSession.h"
|
||||
#include "Log.h"
|
||||
#include "ItemBonusMgr.h"
|
||||
#include "QuestPackets.h"
|
||||
#include "WorldStatePackets.h"
|
||||
#include "CriteriaHandler.h"
|
||||
#include "GameTime.h"
|
||||
#include "World.h"
|
||||
#include "Optional.h"
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
using namespace WorldPackets::Quest;
|
||||
|
||||
WorldQuestTemplate::WorldQuestTemplate(uint32 questId, uint32 duration, uint32 variable, uint8 value)
|
||||
: QuestId(questId), Duration(duration), Variable(variable), Value(value),
|
||||
Expansion(0), Type(0), Timer(duration* MINUTE* IN_MILLISECONDS),
|
||||
MinLevel(0), MaxLevel(MAX_LEVEL), Flags(0)
|
||||
{
|
||||
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
|
||||
if (quest)
|
||||
{
|
||||
Expansion = quest->GetExpansion();
|
||||
MaxLevel = quest->GetMaxLevel(); // this still exists
|
||||
if (Optional<ContentTuningLevels> tuning = sDB2Manager.GetContentTuningData(quest->GetContentTuningId(), {}))
|
||||
{
|
||||
MinLevel = tuning->MinLevel;
|
||||
MaxLevel = tuning->MaxLevel;
|
||||
}
|
||||
|
||||
Optional<QuestTagType> tag = quest->GetQuestTag();
|
||||
if (tag)
|
||||
{
|
||||
switch (*tag)
|
||||
{
|
||||
case QuestTagType::Pvp:
|
||||
Type = WORLD_QUEST_TYPE_PVP;
|
||||
break;
|
||||
case QuestTagType::PetBattle:
|
||||
Type = WORLD_QUEST_TYPE_PET_BATTLE;
|
||||
break;
|
||||
case QuestTagType::Profession:
|
||||
Type = WORLD_QUEST_TYPE_PROFESSION;
|
||||
break;
|
||||
case QuestTagType::Dungeon:
|
||||
Type = WORLD_QUEST_TYPE_DUNGEON;
|
||||
break;
|
||||
case QuestTagType::Raid:
|
||||
Type = WORLD_QUEST_TYPE_RAID;
|
||||
break;
|
||||
default:
|
||||
Type = WORLD_QUEST_TYPE_GENERIC;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Type = WORLD_QUEST_TYPE_GENERIC;
|
||||
}
|
||||
|
||||
if (quest->GetAreaGroupID() > 0)
|
||||
{
|
||||
AreaIDs = sDB2Manager.GetAreasForGroup(quest->GetAreaGroupID());
|
||||
}
|
||||
else if (quest->GetZoneOrSort() > 0)
|
||||
{
|
||||
AreaIDs.push_back(quest->GetZoneOrSort());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WorldQuestMgr::~WorldQuestMgr()
|
||||
{
|
||||
CleanWorldQuestTemplates();
|
||||
}
|
||||
|
||||
void WorldQuestMgr::CleanWorldQuestTemplates()
|
||||
{
|
||||
std::set<WorldQuestTemplate*> toDeleteTemplates;
|
||||
std::set<ActiveWorldQuest*> toDeleteActiveTemplates;
|
||||
|
||||
for (auto& expansionTemplates : _worldQuestTemplates)
|
||||
for (auto& teamTemplates : expansionTemplates.second)
|
||||
for (auto& itr : teamTemplates.second)
|
||||
toDeleteTemplates.insert(itr.second);
|
||||
|
||||
for (auto& expansionEmissaryTemplates : _emissaryWorldQuestTemplates)
|
||||
for (auto& teamEmissaryTemplates : expansionEmissaryTemplates.second)
|
||||
for (auto& itr : teamEmissaryTemplates.second)
|
||||
toDeleteTemplates.insert(itr.second);
|
||||
|
||||
for (auto& expansionWorldQuests : _activeWorldQuests)
|
||||
for (auto& teamWorldQuest : expansionWorldQuests.second)
|
||||
for (auto& activeWorldQuest : teamWorldQuest.second)
|
||||
toDeleteActiveTemplates.insert(activeWorldQuest.second);
|
||||
|
||||
for (auto* wqTemplate : toDeleteTemplates)
|
||||
delete wqTemplate;
|
||||
|
||||
for (auto* activeWQ : toDeleteActiveTemplates)
|
||||
delete activeWQ;
|
||||
|
||||
_worldQuestTemplates.clear();
|
||||
_emissaryWorldQuestTemplates.clear();
|
||||
_activeWorldQuests.clear();
|
||||
}
|
||||
|
||||
// ... (AddWorldQuestTask, RemoveWorldQuestTask, GetWorldQuestTask, instance() ? unchanged)
|
||||
|
||||
void WorldQuestMgr::LoadWorldQuestTemplates()
|
||||
{
|
||||
CleanWorldQuestTemplates();
|
||||
|
||||
QueryResult result = WorldDatabase.Query("SELECT id, duration, variable, value FROM world_quest");
|
||||
if (!result)
|
||||
return;
|
||||
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 questId = fields[0].GetUInt32();
|
||||
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
|
||||
if (!quest)
|
||||
{
|
||||
TC_LOG_DEBUG("sql.sql", "World Quest: {} exists but no quest template found. Skip.", questId);
|
||||
continue;
|
||||
}
|
||||
|
||||
WorldQuestTemplate* wqTemplate = new WorldQuestTemplate(questId, fields[1].GetUInt32(), fields[2].GetUInt32(), fields[3].GetUInt8());
|
||||
AddWorldQuestTemplate(quest, wqTemplate);
|
||||
|
||||
} while (result->NextRow());
|
||||
|
||||
if (_emissaryWorldQuestTemplates.size() < 3)
|
||||
TC_LOG_DEBUG("sql.sql", "World Quest: Only {} emissary quests loaded, 3 needed.", _emissaryWorldQuestTemplates.size());
|
||||
|
||||
TC_LOG_INFO("server.loading", ">> Loaded {} World Quest Templates",
|
||||
_worldQuestTemplates.size() + _emissaryWorldQuestTemplates.size());
|
||||
}
|
||||
|
||||
void WorldQuestMgr::AddWorldQuestTemplate(Quest const* quest, WorldQuestTemplate* worldQuestTemplate)
|
||||
{
|
||||
uint8 questTeamId = GetQuestTeamId(quest);
|
||||
if (questTeamId == TEAM_NEUTRAL)
|
||||
{
|
||||
_worldQuestTemplates[quest->GetExpansion()][TEAM_ALLIANCE][quest->GetQuestId()] = worldQuestTemplate;
|
||||
_worldQuestTemplates[quest->GetExpansion()][TEAM_HORDE][quest->GetQuestId()] = worldQuestTemplate;
|
||||
|
||||
if (quest->IsEmissaryQuest())
|
||||
{
|
||||
_emissaryWorldQuestTemplates[quest->GetExpansion()][TEAM_ALLIANCE][quest->GetQuestId()] = worldQuestTemplate;
|
||||
_emissaryWorldQuestTemplates[quest->GetExpansion()][TEAM_HORDE][quest->GetQuestId()] = worldQuestTemplate;
|
||||
}
|
||||
if (quest->IsCallingQuest())
|
||||
{
|
||||
_callingsWorldQuestTemplates[quest->GetExpansion()][TEAM_ALLIANCE][quest->GetQuestId()] = worldQuestTemplate;
|
||||
_callingsWorldQuestTemplates[quest->GetExpansion()][TEAM_HORDE][quest->GetQuestId()] = worldQuestTemplate;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_worldQuestTemplates[quest->GetExpansion()][questTeamId][quest->GetQuestId()] = worldQuestTemplate;
|
||||
|
||||
if (quest->IsEmissaryQuest())
|
||||
_emissaryWorldQuestTemplates[quest->GetExpansion()][questTeamId][quest->GetQuestId()] = worldQuestTemplate;
|
||||
if (quest->IsCallingQuest())
|
||||
_callingsWorldQuestTemplates[quest->GetExpansion()][questTeamId][quest->GetQuestId()] = worldQuestTemplate;
|
||||
}
|
||||
|
||||
AddWorldQuestTask(quest);
|
||||
}
|
||||
|
||||
uint8 WorldQuestMgr::GetActiveEmissaryQuestsCount(uint8 expansion, uint8 teamId)
|
||||
{
|
||||
// FIX: Only one definition ? removed the duplicate.
|
||||
uint8 count = 0;
|
||||
auto expansionItr = _emissaryWorldQuestTemplates.find(expansion);
|
||||
if (expansionItr != _emissaryWorldQuestTemplates.end())
|
||||
{
|
||||
auto teamItr = expansionItr->second.find(teamId);
|
||||
if (teamItr != expansionItr->second.end())
|
||||
for (auto& questItr : teamItr->second)
|
||||
if (IsQuestActive(questItr.first))
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
uint32 WorldQuestMgr::GetActiveQuestsCount(uint8 expansion, uint8 teamId)
|
||||
{
|
||||
// FIX: Only one definition ? removed the duplicate.
|
||||
auto expansionItr = _activeWorldQuests.find(expansion);
|
||||
if (expansionItr == _activeWorldQuests.end())
|
||||
return 0;
|
||||
|
||||
auto teamItr = expansionItr->second.find(teamId);
|
||||
if (teamItr == expansionItr->second.end())
|
||||
return 0;
|
||||
|
||||
return static_cast<uint32>(teamItr->second.size());
|
||||
}
|
||||
|
||||
void WorldQuestMgr::Update()
|
||||
{
|
||||
for (auto& expansionWorldQuest : _activeWorldQuests)
|
||||
{
|
||||
for (auto& teamWorldQuest : expansionWorldQuest.second)
|
||||
{
|
||||
auto& worldQuests = teamWorldQuest.second;
|
||||
for (auto itr = worldQuests.begin(); itr != worldQuests.end();)
|
||||
{
|
||||
if (itr->second->GetRemainingTime() <= 0)
|
||||
{
|
||||
DisableQuest(itr->second, false);
|
||||
itr = worldQuests.erase(itr);
|
||||
}
|
||||
else
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8 expansion : WorldQuestsExpansions)
|
||||
{
|
||||
for (uint8 teamId : WorldQuestsTeams)
|
||||
{
|
||||
if (_worldQuestTemplates[expansion][teamId].empty())
|
||||
continue;
|
||||
|
||||
int32 questDiff = 18 - static_cast<int32>(GetActiveQuestsCount(expansion, teamId))
|
||||
+ static_cast<int32>(GetActiveEmissaryQuestsCount(expansion, teamId));
|
||||
|
||||
if (questDiff > 0)
|
||||
{
|
||||
std::set<WorldQuestTemplate*> inactive;
|
||||
for (auto& it : _worldQuestTemplates[expansion][teamId])
|
||||
if (WorldQuestTemplate* wqt = it.second)
|
||||
if (!IsQuestActive(wqt->QuestId))
|
||||
inactive.insert(wqt);
|
||||
|
||||
while (questDiff > 0 && !inactive.empty())
|
||||
{
|
||||
auto it = inactive.begin();
|
||||
std::advance(it, urand(0, static_cast<uint32>(inactive.size() - 1)));
|
||||
WorldQuestTemplate* wqt = *it;
|
||||
ActivateQuest(wqt);
|
||||
inactive.erase(wqt);
|
||||
--questDiff;
|
||||
}
|
||||
}
|
||||
|
||||
RefreshEmissaryQuests();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WorldQuestMgr::RefreshEmissaryQuests()
|
||||
{
|
||||
// FIX: Only one definition ? removed the duplicate stub.
|
||||
for (uint8 expansion : WorldQuestsExpansions)
|
||||
{
|
||||
for (uint8 teamId : WorldQuestsTeams)
|
||||
{
|
||||
uint8 activeCount = GetActiveEmissaryQuestsCount(expansion, teamId);
|
||||
uint8 requiredCount = 3;
|
||||
|
||||
if (activeCount >= requiredCount)
|
||||
continue;
|
||||
|
||||
std::set<WorldQuestTemplate*> inactive;
|
||||
auto emissaryItr = _emissaryWorldQuestTemplates.find(expansion);
|
||||
if (emissaryItr != _emissaryWorldQuestTemplates.end())
|
||||
{
|
||||
auto teamItr = emissaryItr->second.find(teamId);
|
||||
if (teamItr != emissaryItr->second.end())
|
||||
for (auto& questItr : teamItr->second)
|
||||
{
|
||||
// FIX: renamed local var from 'template' (reserved keyword) to 'wqTemplate'
|
||||
WorldQuestTemplate* wqTemplate = questItr.second;
|
||||
if (!IsQuestActive(wqTemplate->QuestId))
|
||||
inactive.insert(wqTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
while (activeCount < requiredCount && !inactive.empty())
|
||||
{
|
||||
auto it = inactive.begin();
|
||||
std::advance(it, urand(0, static_cast<uint32>(inactive.size() - 1)));
|
||||
WorldQuestTemplate* wqTemplate = *it;
|
||||
ActivateQuest(wqTemplate);
|
||||
inactive.erase(wqTemplate);
|
||||
++activeCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WorldQuestMgr::AddEmissaryQuestsOnPlayerIfNeeded(Player* player)
|
||||
{
|
||||
// FIX: Only one definition ? removed the duplicate stub.
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
uint8 playerTeam = player->GetTeamId();
|
||||
for (uint8 expansion : WorldQuestsExpansions)
|
||||
{
|
||||
auto emissaryItr = _emissaryWorldQuestTemplates.find(expansion);
|
||||
if (emissaryItr == _emissaryWorldQuestTemplates.end())
|
||||
continue;
|
||||
|
||||
auto teamItr = emissaryItr->second.find(playerTeam);
|
||||
if (teamItr == emissaryItr->second.end())
|
||||
continue;
|
||||
|
||||
for (auto& questItr : teamItr->second)
|
||||
{
|
||||
uint32 questId = questItr.first;
|
||||
if (player->GetQuestStatus(questId) == QUEST_STATUS_NONE)
|
||||
{
|
||||
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
|
||||
if (quest && player->CanTakeQuest(quest, false))
|
||||
player->AddQuest(quest, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CriteriaEntry const*> WorldQuestMgr::GetCriteriasForQuest(uint32 quest_id)
|
||||
{
|
||||
std::vector<CriteriaEntry const*> criterias;
|
||||
|
||||
// Get criteria entries for the given quest
|
||||
// This would typically query the criteria database
|
||||
return criterias;
|
||||
}
|
||||
|
||||
void WorldQuestMgr::LoadWorldQuestRewardTemplates()
|
||||
{
|
||||
QueryResult result = WorldDatabase.Query("SELECT id, questType, rewardType - 1 AS rewardType, rewardId, rewardCount, rewardContext FROM world_quest_reward");
|
||||
if (!result)
|
||||
return;
|
||||
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
|
||||
WorldQuestReward worldQuestReward = WorldQuestReward();
|
||||
worldQuestReward.Id = fields[0].GetUInt32();
|
||||
worldQuestReward.Type = static_cast<WorldQuestRewardType>(fields[2].GetUInt8());
|
||||
worldQuestReward.ItemBonusListID = fields[5].GetUInt32();
|
||||
|
||||
switch (worldQuestReward.Type)
|
||||
{
|
||||
case WORLD_QUEST_REWARD_ITEM:
|
||||
worldQuestReward.Id = fields[3].GetUInt32();
|
||||
worldQuestReward.Count = fields[4].GetUInt32();
|
||||
break;
|
||||
case WORLD_QUEST_REWARD_CURRENCY:
|
||||
worldQuestReward.Id = fields[3].GetUInt32();
|
||||
worldQuestReward.Count = fields[4].GetUInt32();
|
||||
break;
|
||||
case WORLD_QUEST_REWARD_GOLD:
|
||||
worldQuestReward.Id = 0;
|
||||
worldQuestReward.Count = fields[4].GetUInt32();
|
||||
break;
|
||||
}
|
||||
|
||||
_worldQuestRewards[worldQuestReward.Id].push_back(worldQuestReward);
|
||||
_worldQuestRewardByQuestInfos[fields[1].GetUInt32()].push_back(worldQuestReward.Id);
|
||||
|
||||
} while (result->NextRow());
|
||||
|
||||
TC_LOG_INFO("server.loading", ">> Loaded {} World Quest Reward Templates", _worldQuestRewards.size());
|
||||
}
|
||||
|
||||
void WorldQuestMgr::LoadActiveWorldQuests()
|
||||
{
|
||||
// not asynch, only at startup
|
||||
QueryResult result = CharacterDatabase.Query("SELECT questId, rewardId, startTime FROM world_quest");
|
||||
|
||||
if (!result)
|
||||
{
|
||||
TC_LOG_INFO("sql.sql", "World Quest: There is no active world quests.");
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
|
||||
uint32 questId = fields[0].GetUInt32();
|
||||
uint32 rewardId = fields[1].GetUInt32();
|
||||
uint32 startTime = fields[2].GetUInt32();
|
||||
|
||||
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
|
||||
if (!quest)
|
||||
{
|
||||
TC_LOG_DEBUG("sql.sql", "World Quest: Quest {} has world quest duration but quest does not exist.", questId);
|
||||
continue;
|
||||
}
|
||||
|
||||
WorldQuestTemplate* worldQuestTemplate = GetWorldQuestTemplate(questId);
|
||||
if (!worldQuestTemplate)
|
||||
{
|
||||
TC_LOG_DEBUG("sql.sql", "World Quest: Quest {} has world quest duration but quest is not a world quest.", questId);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8 questTeamId = GetQuestTeamId(quest);
|
||||
if (questTeamId == TEAM_NEUTRAL)
|
||||
{
|
||||
_activeWorldQuests[quest->GetExpansion()][TEAM_ALLIANCE][worldQuestTemplate->QuestId] = new ActiveWorldQuest(questId, rewardId, startTime, worldQuestTemplate->Timer);
|
||||
_activeWorldQuests[quest->GetExpansion()][TEAM_HORDE][worldQuestTemplate->QuestId] = new ActiveWorldQuest(questId, rewardId, startTime, worldQuestTemplate->Timer);
|
||||
}
|
||||
else
|
||||
_activeWorldQuests[quest->GetExpansion()][questTeamId][worldQuestTemplate->QuestId] = new ActiveWorldQuest(questId, rewardId, startTime, worldQuestTemplate->Timer);
|
||||
|
||||
} while (result->NextRow());
|
||||
|
||||
TC_LOG_INFO("server.loading", ">> Loaded {} Active World Quests", _activeWorldQuests.size());
|
||||
}
|
||||
|
||||
void WorldQuestMgr::AddWorldQuestTask(Quest const* quest)
|
||||
{
|
||||
if (quest->GetAreaGroupID() > 0)
|
||||
{
|
||||
std::vector<uint32> areaGroupMembers = sDB2Manager.GetAreasForGroup(quest->GetAreaGroupID());
|
||||
for (uint32 areaId : areaGroupMembers)
|
||||
_worldQuestAreaTaskStore[areaId].insert(quest);
|
||||
}
|
||||
else
|
||||
{
|
||||
WorldQuestTemplate const* worldQuest = GetWorldQuestTemplate(quest->GetQuestId());
|
||||
if (worldQuest && !worldQuest->AreaIDs.empty())
|
||||
{
|
||||
for (uint32 areaId : worldQuest->AreaIDs)
|
||||
_worldQuestAreaTaskStore[areaId].insert(quest);
|
||||
return;
|
||||
}
|
||||
|
||||
for (QuestObjective const& obj : quest->GetObjectives())
|
||||
{
|
||||
switch (obj.Type)
|
||||
{
|
||||
case QUEST_OBJECTIVE_MONSTER:
|
||||
if (QueryResult result = WorldDatabase.PQuery("SELECT `areaId` FROM `creature` WHERE `id` = '{}' AND `zoneId` = '{}';", obj.ObjectID, quest->GetZoneOrSort()))
|
||||
{
|
||||
do
|
||||
{
|
||||
_worldQuestAreaTaskStore[(*result)[0].GetUInt16()].insert(quest);
|
||||
} while (result->NextRow());
|
||||
}
|
||||
break;
|
||||
case QUEST_OBJECTIVE_GAMEOBJECT:
|
||||
if (QueryResult result = WorldDatabase.PQuery("SELECT `areaId` FROM `gameobject` WHERE `id` = '{}' AND `zoneId` = '{}';", obj.ObjectID, quest->GetZoneOrSort()))
|
||||
{
|
||||
do
|
||||
{
|
||||
_worldQuestAreaTaskStore[(*result)[0].GetUInt16()].insert(quest);
|
||||
} while (result->NextRow());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WorldQuestMgr::RemoveWorldQuestTask(Quest const* quest)
|
||||
{
|
||||
if (!quest)
|
||||
return;
|
||||
|
||||
for (auto& taskSet : _worldQuestAreaTaskStore)
|
||||
taskSet.second.erase(quest);
|
||||
}
|
||||
|
||||
std::set<Quest const*> const* WorldQuestMgr::GetWorldQuestTask(uint32 areaId) const
|
||||
{
|
||||
auto itr = _worldQuestAreaTaskStore.find(areaId);
|
||||
if (itr != _worldQuestAreaTaskStore.end())
|
||||
return &itr->second;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void WorldQuestMgr::ActivateQuest(WorldQuestTemplate* worldQuestTemplate)
|
||||
{
|
||||
Quest const* quest = worldQuestTemplate->GetQuest();
|
||||
if (!quest)
|
||||
return;
|
||||
|
||||
// Shouldn't activate same quest twice
|
||||
if (IsQuestActive(worldQuestTemplate->QuestId))
|
||||
return;
|
||||
|
||||
uint32 questId = worldQuestTemplate->QuestId;
|
||||
uint32 rewardId = GetRandomRewardForQuestType(quest->GetQuestInfoID());
|
||||
uint32 startTime = GameTime::GetGameTime();
|
||||
|
||||
uint8 questTeamId = GetQuestTeamId(quest);
|
||||
if (questTeamId == TEAM_NEUTRAL)
|
||||
{
|
||||
_activeWorldQuests[quest->GetExpansion()][TEAM_ALLIANCE][worldQuestTemplate->QuestId] = new ActiveWorldQuest(questId, rewardId, startTime, worldQuestTemplate->Timer);
|
||||
_activeWorldQuests[quest->GetExpansion()][TEAM_HORDE][worldQuestTemplate->QuestId] = new ActiveWorldQuest(questId, rewardId, startTime, worldQuestTemplate->Timer);
|
||||
}
|
||||
else
|
||||
_activeWorldQuests[quest->GetExpansion()][questTeamId][worldQuestTemplate->QuestId] = new ActiveWorldQuest(questId, rewardId, startTime, worldQuestTemplate->Timer);
|
||||
|
||||
// We add Emissary Quests to all eligible players
|
||||
if (quest->IsEmissaryQuest())
|
||||
{
|
||||
SessionMap const& smap = sWorld->GetAllSessions();
|
||||
for (SessionMap::const_iterator iter = smap.begin(); iter != smap.end(); ++iter)
|
||||
if (Player* player = iter->second->GetPlayer())
|
||||
if (player->GetQuestStatus(worldQuestTemplate->QuestId) == QUEST_STATUS_NONE)
|
||||
player->AddQuest(quest, nullptr);
|
||||
}
|
||||
|
||||
// Save to character database
|
||||
CharacterDatabase.PExecute("REPLACE INTO world_quest (questId, rewardId, startTime) VALUES ({}, {}, {})",
|
||||
questId, rewardId, startTime);
|
||||
|
||||
AddWorldQuestTask(quest);
|
||||
}
|
||||
|
||||
void WorldQuestMgr::DisableQuest(ActiveWorldQuest* activeWorldQuest, bool deleteFromMap)
|
||||
{
|
||||
if (!activeWorldQuest)
|
||||
return;
|
||||
|
||||
uint32 questId = activeWorldQuest->QuestId;
|
||||
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
|
||||
if (!quest)
|
||||
return;
|
||||
|
||||
// Remove from database
|
||||
CharacterDatabase.PExecute("DELETE FROM world_quest WHERE questId = {}", questId);
|
||||
|
||||
// Remove from map
|
||||
if (deleteFromMap)
|
||||
{
|
||||
for (auto& expansionQuests : _activeWorldQuests)
|
||||
{
|
||||
for (auto& teamQuests : expansionQuests.second)
|
||||
{
|
||||
teamQuests.second.erase(questId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete activeWorldQuest;
|
||||
}
|
||||
|
||||
bool WorldQuestMgr::IsQuestActive(uint32 questId)
|
||||
{
|
||||
for (auto& expansionQuests : _activeWorldQuests)
|
||||
{
|
||||
for (auto& teamQuests : expansionQuests.second)
|
||||
{
|
||||
if (teamQuests.second.find(questId) != teamQuests.second.end())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
WorldQuestTemplate* WorldQuestMgr::GetWorldQuestTemplate(uint32 questId)
|
||||
{
|
||||
for (auto& expansionTemplates : _worldQuestTemplates)
|
||||
{
|
||||
for (auto& teamTemplates : expansionTemplates.second)
|
||||
{
|
||||
auto itr = teamTemplates.second.find(questId);
|
||||
if (itr != teamTemplates.second.end())
|
||||
return itr->second;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& expansionTemplates : _emissaryWorldQuestTemplates)
|
||||
{
|
||||
for (auto& teamTemplates : expansionTemplates.second)
|
||||
{
|
||||
auto itr = teamTemplates.second.find(questId);
|
||||
if (itr != teamTemplates.second.end())
|
||||
return itr->second;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ActiveWorldQuest* WorldQuestMgr::GetActiveWorldQuest(uint32 questId)
|
||||
{
|
||||
for (auto& expansionQuests : _activeWorldQuests)
|
||||
{
|
||||
for (auto& teamQuests : expansionQuests.second)
|
||||
{
|
||||
auto itr = teamQuests.second.find(questId);
|
||||
if (itr != teamQuests.second.end())
|
||||
return itr->second;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32 WorldQuestMgr::GetRandomRewardForQuestType(uint32 questType)
|
||||
{
|
||||
auto rewardItr = _worldQuestRewardByQuestInfos.find(questType);
|
||||
if (rewardItr == _worldQuestRewardByQuestInfos.end() || rewardItr->second.empty())
|
||||
return 0;
|
||||
|
||||
// Select random reward ID from available rewards for this quest type
|
||||
auto it = rewardItr->second.begin();
|
||||
std::advance(it, urand(0, rewardItr->second.size() - 1));
|
||||
return *it;
|
||||
}
|
||||
|
||||
std::vector<WorldQuestReward const*> WorldQuestMgr::GetRewardsForPlayerById(Player* /*player*/, uint32 rewardId)
|
||||
{
|
||||
std::vector<WorldQuestReward const*> rewards;
|
||||
|
||||
auto rewardItr = _worldQuestRewards.find(rewardId);
|
||||
if (rewardItr != _worldQuestRewards.end())
|
||||
{
|
||||
for (WorldQuestReward const& reward : rewardItr->second)
|
||||
{
|
||||
// Check if player is eligible for this reward
|
||||
// This would typically involve level checks, class checks, etc.
|
||||
rewards.push_back(&reward);
|
||||
}
|
||||
}
|
||||
|
||||
return rewards;
|
||||
}
|
||||
|
||||
TeamId WorldQuestMgr::GetQuestTeamId(Quest const* quest)
|
||||
{
|
||||
if (!quest)
|
||||
return TEAM_NEUTRAL;
|
||||
|
||||
// Simplified team detection - in a real implementation this would check quest flags
|
||||
// For now, return neutral as default
|
||||
return TEAM_NEUTRAL;
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_WORLD_QUEST_MGR_H
|
||||
#define TRINITYCORE_WORLD_QUEST_MGR_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "QuestDef.h"
|
||||
#include "GameTime.h"
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <set>
|
||||
|
||||
struct WorldQuestTemplate;
|
||||
struct ActiveWorldQuest;
|
||||
struct WorldQuestReward;
|
||||
|
||||
class WorldQuestMgr;
|
||||
|
||||
typedef std::unordered_map<uint8 /*expansion*/, std::unordered_map<uint8 /*teamId*/, std::unordered_map<uint32 /*questId*/, WorldQuestTemplate*>>> WorldQuestTemplateMap;
|
||||
typedef std::unordered_map<uint8 /*expansion*/, std::unordered_map<uint8 /*teamId*/, std::unordered_map<uint32 /*questId*/, ActiveWorldQuest*>>> ActiveWorldQuestMap;
|
||||
typedef std::unordered_map<uint32, std::set<Quest const*>> QuestAreaTaskMap;
|
||||
typedef std::unordered_map<uint32 /*RewardId*/, std::vector<WorldQuestReward>> WorldQuestRewardMap;
|
||||
typedef std::unordered_map<uint32 /*QuestInfo*/, std::vector<uint32 /*RewardId*/>> WorldQuestRewardByQuestInfoMap;
|
||||
|
||||
enum WorldQuestRewardType
|
||||
{
|
||||
WORLD_QUEST_REWARD_ITEM = 0,
|
||||
WORLD_QUEST_REWARD_CURRENCY = 1,
|
||||
WORLD_QUEST_REWARD_GOLD = 2,
|
||||
};
|
||||
|
||||
enum WorldQuestType
|
||||
{
|
||||
WORLD_QUEST_TYPE_PVP = 0,
|
||||
WORLD_QUEST_TYPE_PET_BATTLE = 1,
|
||||
WORLD_QUEST_TYPE_PROFESSION = 2,
|
||||
WORLD_QUEST_TYPE_DUNGEON = 3,
|
||||
WORLD_QUEST_TYPE_RAID = 4,
|
||||
WORLD_QUEST_TYPE_WORLD_BOSS = 5,
|
||||
WORLD_QUEST_TYPE_BOUNTY = 6,
|
||||
WORLD_QUEST_TYPE_ASSIST = 7,
|
||||
WORLD_QUEST_TYPE_ARTIFACT_POWER = 8,
|
||||
WORLD_QUEST_TYPE_RESOURCE = 9,
|
||||
WORLD_QUEST_TYPE_FACTION = 10,
|
||||
WORLD_QUEST_TYPE_GENERIC = 11,
|
||||
};
|
||||
|
||||
struct WorldQuestReward
|
||||
{
|
||||
WorldQuestRewardType Type;
|
||||
uint32 Id;
|
||||
uint32 Count;
|
||||
uint32 ItemBonusListID;
|
||||
};
|
||||
|
||||
struct WorldQuestTemplate
|
||||
{
|
||||
WorldQuestTemplate(uint32 questId, uint32 duration, uint32 variable, uint8 value);
|
||||
|
||||
uint32 QuestId;
|
||||
uint32 Duration;
|
||||
uint32 Variable;
|
||||
uint8 Value;
|
||||
uint8 Expansion;
|
||||
uint8 Type;
|
||||
std::vector<uint32> AreaIDs;
|
||||
std::vector<uint32> RewardIDs;
|
||||
uint32 Timer;
|
||||
uint32 MinLevel;
|
||||
uint32 MaxLevel;
|
||||
uint32 Flags;
|
||||
|
||||
Quest const* GetQuest() const { return sObjectMgr->GetQuestTemplate(QuestId); }
|
||||
};
|
||||
|
||||
struct ActiveWorldQuest
|
||||
{
|
||||
ActiveWorldQuest(uint32 questId, uint32 rewardId, uint32 startTime, uint32 timer)
|
||||
: QuestId(questId), RewardId(rewardId), StartTime(startTime), IsCompleted(false),
|
||||
EndTime(startTime + timer)
|
||||
{
|
||||
}
|
||||
|
||||
uint32 QuestId;
|
||||
uint32 RewardId;
|
||||
uint32 StartTime;
|
||||
uint32 EndTime;
|
||||
bool IsCompleted;
|
||||
|
||||
uint32 GetRemainingTime() const
|
||||
{
|
||||
uint32 currentTime = GameTime::GetGameTime();
|
||||
if (currentTime >= EndTime)
|
||||
return 0;
|
||||
return (EndTime - currentTime) / IN_MILLISECONDS;
|
||||
}
|
||||
};
|
||||
|
||||
class TC_GAME_API WorldQuestMgr
|
||||
{
|
||||
private:
|
||||
WorldQuestMgr() = default;
|
||||
|
||||
public:
|
||||
~WorldQuestMgr();
|
||||
|
||||
static WorldQuestMgr* instance()
|
||||
{
|
||||
static WorldQuestMgr instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
// World quest expansions and teams
|
||||
const std::vector<uint8> WorldQuestsExpansions = { EXPANSION_LEGION, EXPANSION_BATTLE_FOR_AZEROTH, EXPANSION_SHADOWLANDS, EXPANSION_DRAGONFLIGHT };
|
||||
const std::vector<uint8> WorldQuestsTeams = { TEAM_ALLIANCE, TEAM_HORDE };
|
||||
|
||||
// Loading functions
|
||||
void LoadWorldQuestTemplates();
|
||||
void LoadWorldQuestRewardTemplates();
|
||||
void AddWorldQuestTemplate(Quest const* quest, WorldQuestTemplate* worldQuestTemplate);
|
||||
void LoadActiveWorldQuests();
|
||||
void Update();
|
||||
|
||||
// Cleanup functions
|
||||
void CleanWorldQuestTemplates();
|
||||
|
||||
// World quest tasks
|
||||
void AddWorldQuestTask(Quest const* quest);
|
||||
void RemoveWorldQuestTask(Quest const* quest);
|
||||
|
||||
// Criteria and emissary
|
||||
std::vector<CriteriaEntry const*> GetCriteriasForQuest(uint32 quest_id);
|
||||
void RefreshEmissaryQuests();
|
||||
void AddEmissaryQuestsOnPlayerIfNeeded(Player* player);
|
||||
|
||||
// Quest activation/deactivation
|
||||
void ActivateQuest(WorldQuestTemplate* worldQuestTemplate);
|
||||
void DisableQuest(ActiveWorldQuest* activeWorldQuest, bool deleteFromMap = true);
|
||||
|
||||
// Quest status (public - used by external code)
|
||||
bool IsQuestActive(uint32 questId);
|
||||
|
||||
// Rewards
|
||||
void RewardQuestForPlayer(Player* player, uint32 questId);
|
||||
|
||||
// Statistics (public - used by external code)
|
||||
uint32 GetActiveQuestsCount(uint8 expansion, uint8 teamId);
|
||||
uint8 GetActiveEmissaryQuestsCount(uint8 expansion, uint8 teamId);
|
||||
|
||||
// Timer (public - used by external code)
|
||||
uint32 GetTimerForQuest(uint32 questId);
|
||||
|
||||
// Team (public - used by external code)
|
||||
TeamId GetQuestTeamId(Quest const* quest);
|
||||
WorldQuestTemplate* GetWorldQuestTemplate(uint32 questId);
|
||||
|
||||
private:
|
||||
// Methods that need access to private member variables
|
||||
std::set<Quest const*> const* GetWorldQuestTask(uint32 areaId) const;
|
||||
|
||||
ActiveWorldQuest* GetActiveWorldQuest(uint32 questId);
|
||||
uint32 GetRandomRewardForQuestType(uint32 questType);
|
||||
std::vector<WorldQuestReward const*> GetRewardsForPlayerById(Player* player, uint32 rewardId);
|
||||
|
||||
WorldQuestTemplateMap _worldQuestTemplates;
|
||||
WorldQuestTemplateMap _emissaryWorldQuestTemplates;
|
||||
WorldQuestTemplateMap _callingsWorldQuestTemplates;
|
||||
QuestAreaTaskMap _worldQuestAreaTaskStore;
|
||||
QuestAreaTaskMap _questAreaTaskStore;
|
||||
|
||||
WorldQuestRewardMap _worldQuestRewards;
|
||||
WorldQuestRewardByQuestInfoMap _worldQuestRewardByQuestInfos;
|
||||
|
||||
ActiveWorldQuestMap _activeWorldQuests;
|
||||
};
|
||||
|
||||
#define sWorldQuestMgr WorldQuestMgr::instance()
|
||||
|
||||
#endif // TRINITYCORE_WORLD_QUEST_MGR_H
|
||||
@@ -1189,6 +1189,22 @@ namespace WorldPackets
|
||||
uint8 RequestedPartyDelveTier = 0;
|
||||
uint8 StatusFlags = 0;
|
||||
};
|
||||
|
||||
class ShowDelvesDisplayUI final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
ShowDelvesDisplayUI() : ServerPacket(SMSG_SHOW_DELVES_DISPLAY_UI, 0) {}
|
||||
|
||||
WorldPacket const* Write() override { return &_worldPacket; }
|
||||
};
|
||||
|
||||
class ShowDelvesCompanionConfigurationUI final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
ShowDelvesCompanionConfigurationUI() : ServerPacket(SMSG_SHOW_DELVES_COMPANION_CONFIGURATION_UI, 0) {}
|
||||
|
||||
WorldPacket const* Write() override { return &_worldPacket; }
|
||||
};
|
||||
//WowCommunity
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2442,8 +2442,8 @@ void OpcodeTable::InitializeServerOpcodes()
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_VEHICLE_REC_ID, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHADOWLANDS_CAPACITANCE_UPDATE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHIPMENT_FACTION_UPDATE_RESULT, STATUS_UNHANDLED, CONNECTION_TYPE_INSTANCE);
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHOW_DELVES_COMPANION_CONFIGURATION_UI, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHOW_DELVES_DISPLAY_UI, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHOW_DELVES_COMPANION_CONFIGURATION_UI, STATUS_NEVER, CONNECTION_TYPE_REALM);
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHOW_DELVES_DISPLAY_UI, STATUS_NEVER, CONNECTION_TYPE_REALM);
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHOW_NEUTRAL_PLAYER_FACTION_SELECT_UI, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHOW_QUEST_COMPLETION_TEXT, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHOW_TAXI_NODES, STATUS_NEVER, CONNECTION_TYPE_REALM);
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
#include "SkillDiscovery.h"
|
||||
#include "SkillExtraItems.h"
|
||||
#include "SmartScriptMgr.h"
|
||||
#include "WorldQuestMgr.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "SupportMgr.h"
|
||||
#include "TaxiPathGraph.h"
|
||||
@@ -1617,6 +1618,11 @@ bool World::SetInitialWorldSettings()
|
||||
QuestMgr::Load();
|
||||
sObjectMgr->LoadQuests(); // must be loaded after DBCs, creature_template, items, gameobject tables
|
||||
|
||||
TC_LOG_INFO("server.loading", "Loading World Quests...");
|
||||
sWorldQuestMgr->LoadWorldQuestTemplates();
|
||||
sWorldQuestMgr->LoadWorldQuestRewardTemplates();
|
||||
sWorldQuestMgr->LoadActiveWorldQuests();
|
||||
|
||||
TC_LOG_INFO("server.loading", "Checking Quest Disables");
|
||||
DisableMgr::CheckQuestDisables(); // must be after loading quests
|
||||
|
||||
@@ -2419,6 +2425,11 @@ void World::Update(uint32 diff)
|
||||
sBattlefieldMgr->Update(diff);
|
||||
}
|
||||
|
||||
{
|
||||
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update world quests"));
|
||||
sWorldQuestMgr->Update();
|
||||
}
|
||||
|
||||
sInitiativeManager.Update(diff);
|
||||
sNeighborhoodMgr.Update(diff);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user