world quest part 1

This commit is contained in:
luis
2026-03-14 13:10:10 -03:00
parent 787e355835
commit c67de7f095
14 changed files with 1146 additions and 75 deletions
@@ -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
+4 -2
View File
@@ -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())
+33 -6
View File
@@ -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)
+30 -33
View File
@@ -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,16 +661,16 @@ 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);
// 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)
{
TC_LOG_ERROR("housing", "HousingMap::AddPlayerToMap: Plot {} has invalid data: ExteriorComponentID={}, WmoDataID={} ? skipping spawn",
plotIdx, exteriorComponentID, houseExteriorWmoDataID);
}
else
{
TC_LOG_DEBUG("housing", "HousingMap::AddPlayerToMap: Plot {} spawning house with ExteriorComponentID={}, WmoDataID={}",
plotIdx, exteriorComponentID, houseExteriorWmoDataID);
@@ -685,6 +688,7 @@ bool HousingMap::AddPlayerToMap(Player* player, bool initPlayer /*= true*/)
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)
+2 -2
View File
@@ -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);
+2
View File
@@ -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); }
+658
View File
@@ -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;
}
+194
View File
@@ -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
}
}
+2 -2
View File
@@ -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);
+11
View File
@@ -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);