diff --git a/src/modules/Playerbot/AI/Combat/TargetScanner.cpp b/src/modules/Playerbot/AI/Combat/TargetScanner.cpp index decd27ad4..2bd2906dd 100644 --- a/src/modules/Playerbot/AI/Combat/TargetScanner.cpp +++ b/src/modules/Playerbot/AI/Combat/TargetScanner.cpp @@ -41,83 +41,83 @@ namespace Playerbot m_avoidElites(false) { // Configure based on class - switch (m_bot->GetClass()) + switch (m_bot->GetClass()) { - case CLASS_HUNTER: - m_baseRange = 35.0f; - m_maxRange = 41.0f; // Max hunter range - m_preferRanged = true; - m_scanInterval = SCAN_INTERVAL_NORMAL; - break; + case CLASS_HUNTER: + m_baseRange = 35.0f; + m_maxRange = 41.0f; // Max hunter range + m_preferRanged = true; + m_scanInterval = SCAN_INTERVAL_NORMAL; + break; - case CLASS_MAGE: - case CLASS_WARLOCK: - m_baseRange = 30.0f; - m_maxRange = 36.0f; - m_preferRanged = true; - m_scanInterval = SCAN_INTERVAL_NORMAL; - break; + case CLASS_MAGE: + case CLASS_WARLOCK: + m_baseRange = 30.0f; + m_maxRange = 36.0f; + m_preferRanged = true; + m_scanInterval = SCAN_INTERVAL_NORMAL; + break; - case CLASS_PRIEST: - m_baseRange = 27.0f; - m_maxRange = 36.0f; - m_preferRanged = true; - m_avoidElites = true; // Priests should be more cautious - m_scanInterval = SCAN_INTERVAL_NORMAL; - break; + case CLASS_PRIEST: + m_baseRange = 27.0f; + m_maxRange = 36.0f; + m_preferRanged = true; + m_avoidElites = true; // Priests should be more cautious + m_scanInterval = SCAN_INTERVAL_NORMAL; + break; - case CLASS_SHAMAN: - case CLASS_DRUID: - m_baseRange = 25.0f; - m_maxRange = 36.0f; - m_preferRanged = false; // Hybrid, depends on spec - m_scanInterval = SCAN_INTERVAL_NORMAL; - break; + case CLASS_SHAMAN: + case CLASS_DRUID: + m_baseRange = 25.0f; + m_maxRange = 36.0f; + m_preferRanged = false; // Hybrid, depends on spec + m_scanInterval = SCAN_INTERVAL_NORMAL; + break; - case CLASS_WARRIOR: - case CLASS_PALADIN: - m_baseRange = 15.0f; - m_maxRange = 25.0f; - m_preferRanged = false; - m_scanInterval = SCAN_INTERVAL_COMBAT; - break; + case CLASS_WARRIOR: + case CLASS_PALADIN: + m_baseRange = 15.0f; + m_maxRange = 25.0f; + m_preferRanged = false; + m_scanInterval = SCAN_INTERVAL_COMBAT; + break; - case CLASS_ROGUE: - m_baseRange = 10.0f; // Rogues want to get close for stealth opener - m_maxRange = 20.0f; - m_preferRanged = false; - m_scanInterval = SCAN_INTERVAL_COMBAT; - break; + case CLASS_ROGUE: + m_baseRange = 10.0f; // Rogues want to get close for stealth opener + m_maxRange = 20.0f; + m_preferRanged = false; + m_scanInterval = SCAN_INTERVAL_COMBAT; + break; - case CLASS_DEATH_KNIGHT: - m_baseRange = 20.0f; // Death grip range - m_maxRange = 30.0f; - m_preferRanged = false; - m_scanInterval = SCAN_INTERVAL_COMBAT; - break; + case CLASS_DEATH_KNIGHT: + m_baseRange = 20.0f; // Death grip range + m_maxRange = 30.0f; + m_preferRanged = false; + m_scanInterval = SCAN_INTERVAL_COMBAT; + break; - case CLASS_MONK: - m_baseRange = 15.0f; - m_maxRange = 25.0f; - m_preferRanged = false; - m_scanInterval = SCAN_INTERVAL_COMBAT; - break; + case CLASS_MONK: + m_baseRange = 15.0f; + m_maxRange = 25.0f; + m_preferRanged = false; + m_scanInterval = SCAN_INTERVAL_COMBAT; + break; - case CLASS_DEMON_HUNTER: - m_baseRange = 20.0f; - m_maxRange = 30.0f; - m_preferRanged = false; - m_scanInterval = SCAN_INTERVAL_COMBAT; - break; + case CLASS_DEMON_HUNTER: + m_baseRange = 20.0f; + m_maxRange = 30.0f; + m_preferRanged = false; + m_scanInterval = SCAN_INTERVAL_COMBAT; + break; - default: - m_baseRange = 20.0f; - m_maxRange = 30.0f; - break; + default: + m_baseRange = 20.0f; + m_maxRange = 30.0f; + break; } // Adjust for low level (more cautious) - if (m_bot->GetLevel() < 20) + if (m_bot->GetLevel() < 20) { m_baseRange *= 0.75f; m_maxRange *= 0.75f; @@ -222,7 +222,7 @@ namespace Playerbot bool operator<(const PriorityTarget& other) const { // Higher priority first, then closer distance - if (priority != other.priority) + if (priority != other.priority) return priority > other.priority; return distance < other.distance; } @@ -249,7 +249,7 @@ namespace Playerbot // we must respond immediately to protect it ObjectGuid petGuid = m_bot->GetPetGUID(); bool attackingBotOrPet = (it->victim == m_bot->GetGUID()) || - (!petGuid.IsEmpty() && it->victim == petGuid); + (!petGuid.IsEmpty() && it->victim == petGuid); if (attackingBotOrPet) priority = PRIORITY_CRITICAL; @@ -259,7 +259,7 @@ namespace Playerbot priority = PRIORITY_TRIVIAL; // Prioritize elites and world bosses - if (it->isWorldBoss) + if (it->isWorldBoss) priority = PRIORITY_CRITICAL; else if (it->isElite) priority = ::std::min(priority + 2, PRIORITY_ELITE); @@ -299,18 +299,18 @@ namespace Playerbot // Cell::VisitAllObjects caused deadlocks with 100+ bots due to: // - Main thread holds grid locks while updating objects // - Worker threads acquire grid locks for spatial queries - // - Lock ordering conflicts → 60-second hang → crash + // - Lock ordering conflicts ? 60-second hang ? crash // // NEW APPROACH: // - Background worker thread updates inactive grid buffer // - Atomic buffer swap after update complete // - Bots query active buffer with ZERO lock contention - // - Scales to 10,000+ bots with 1-5μs query latency + // - Scales to 10,000+ bots with 1-5?s query latency Map* map = m_bot->GetMap(); if (!map) { - TC_LOG_DEBUG("playerbot.scanner", + TC_LOG_ERROR("playerbot.scanner", "TargetScanner::FindAllHostiles - Bot {} has no map!", m_bot->GetName()); return hostileGuids; @@ -331,7 +331,7 @@ namespace Playerbot if (!spatialGrid) { - TC_LOG_DEBUG("playerbot.scanner", + TC_LOG_ERROR("playerbot.scanner", "Failed to create spatial grid for map {}", map->GetId()); return hostileGuids; } @@ -340,12 +340,12 @@ namespace Playerbot // =========================================================================== // CRITICAL DEADLOCK FIX: Return GUIDs instead of Unit* pointers! // =========================================================================== - // OLD CODE (DEADLOCK): Query snapshots → Call ObjectAccessor::GetUnit(guid) - // → Access Map::_objectsStore (NOT THREAD-SAFE!) → DEADLOCK! + // OLD CODE (DEADLOCK): Query snapshots ? Call ObjectAccessor::GetUnit(guid) + // ? Access Map::_objectsStore (NOT THREAD-SAFE!) ? DEADLOCK! // - // NEW CODE (SAFE): Query snapshots → Return GUIDs only - // → Main thread resolves GUID → Unit* and queues actions - // → ZERO Map access from worker threads → NO DEADLOCKS! + // NEW CODE (SAFE): Query snapshots ? Return GUIDs only + // ? Main thread resolves GUID ? Unit* and queues actions + // ? ZERO Map access from worker threads ? NO DEADLOCKS! // =========================================================================== // Query nearby creature SNAPSHOTS (lock-free, thread-safe!) ::std::vector nearbyCreatures = @@ -356,13 +356,19 @@ namespace Playerbot m_bot->GetName(), nearbyCreatures.size(), range); // Process snapshots - validation done WITHOUT ObjectAccessor/Map calls! // Hostility check deferred to main thread (requires Map access) - for (DoubleBufferedSpatialGrid::CreatureSnapshot const& creature : nearbyCreatures) + for (DoubleBufferedSpatialGrid::CreatureSnapshot const& creature : nearbyCreatures) { + TC_LOG_INFO("playerbot.scanner", + "Bot {} evaluating creature entry={} guid={}: IsValid={}, isDead={}, health={}/{}, level={}, blacklisted={}, botLevel={}", + m_bot->GetName(), creature.entry, creature.guid.ToString(), + creature.IsValid(), creature.isDead, creature.health, creature.maxHealth, + creature.level, IsBlacklisted(creature.guid), m_bot->GetLevel()); + // Validate using snapshot data only (distance, level, alive, blacklist, combat state) - if (!IsValidTargetSnapshot(creature)) + if (!IsValidTargetSnapshot(creature)) continue; // Store GUID - main thread will validate hostility and queue attack action - // NO ObjectAccessor::GetUnit() call → THREAD-SAFE! + // NO ObjectAccessor::GetUnit() call ? THREAD-SAFE! hostileGuids.push_back(creature.guid); } @@ -381,11 +387,17 @@ namespace Playerbot bool TargetScanner::IsValidTargetSnapshot(DoubleBufferedSpatialGrid::CreatureSnapshot const& creature) const { // Basic validation - if (!creature.IsValid() || creature.isDead || creature.health == 0) + if (!creature.IsValid() || creature.isDead || creature.health == 0) + { + TC_LOG_INFO("playerbot.scanner", + "Bot {} REJECTED creature entry {} (guid {}): IsValid={}, isDead={}, health={}, maxHealth={}, level={}, botLevel={}", + m_bot->GetName(), creature.entry, creature.guid.ToString(), + creature.IsValid(), creature.isDead, creature.health, creature.maxHealth, creature.level, m_bot->GetLevel()); return false; + } // Check if blacklisted (uses thread-safe GUID check) - if (this->IsBlacklisted(creature.guid)) + if (this->IsBlacklisted(creature.guid)) return false; // NOTE: Hostility check (IsHostileTo) requires Unit* pointer, so we defer it @@ -406,9 +418,9 @@ namespace Playerbot // if (creature.isInCombat && creature.victim != m_bot->GetGUID() && !m_bot->GetGroup()) // return false; - // Level check - don't attack creatures too high level (10+ levels above) - if (creature.level > m_bot->GetLevel() + 10) - return false; + // Level check removed: In WoW 12.0+ all open-world content scales to player + // level. Creature snapshots store unscaled base levels (e.g. 90) which are + // meaningless for filtering. The creature will scale to the bot's level in combat. // DESIGN: LOS check skipped during snapshot validation because snapshots // contain only position data, not live Unit pointers. LOS is verified @@ -431,26 +443,26 @@ namespace Playerbot float maxRangeSq = maxRange * maxRange; // Too far away (using squared distance for comparison) - if (m_bot->GetExactDistSq(target) > maxRangeSq) + if (m_bot->GetExactDistSq(target) > maxRangeSq) return false; // Check if path exists (basic check) - if (!m_bot->IsWithinLOSInMap(target)) + if (!m_bot->IsWithinLOSInMap(target)) return false; float dist = m_bot->GetExactDist(target); // For ranged classes, check if we're in range - if (m_preferRanged) + if (m_preferRanged) { // Check if we have a ranged attack spell in range // This is simplified - in production you'd check actual spell ranges - if (dist <= 36.0f) + if (dist <= 36.0f) return true; } // For melee, need to be able to get close - if (dist <= 5.0f || m_bot->IsWithinMeleeRange(target)) + if (dist <= 5.0f || m_bot->IsWithinMeleeRange(target)) return true; // Can we get there? @@ -463,23 +475,23 @@ namespace Playerbot return PRIORITY_AVOID; // Check if unit is attackable - if (!m_bot->IsValidAttackTarget(target)) + if (!m_bot->IsValidAttackTarget(target)) return PRIORITY_AVOID; // Check if blacklisted - if (IsBlacklisted(target->GetGUID())) + if (IsBlacklisted(target->GetGUID())) return PRIORITY_AVOID; // Don't attack friendly units - if (m_bot->IsFriendlyTo(target)) + if (m_bot->IsFriendlyTo(target)) return PRIORITY_AVOID; // Don't attack units we can't see - if (!m_bot->IsWithinLOSInMap(target)) + if (!m_bot->IsWithinLOSInMap(target)) return PRIORITY_AVOID; // Don't attack critters unless they're hostile - if (target->GetTypeId() == TYPEID_UNIT) + if (target->GetTypeId() == TYPEID_UNIT) { Creature* creature = target->ToCreature(); if (creature->GetCreatureTemplate()->type == CREATURE_TYPE_CRITTER && @@ -488,23 +500,23 @@ namespace Playerbot } // Don't attack units that are evading - if (target->HasUnitState(UNIT_STATE_EVADE)) + if (target->HasUnitState(UNIT_STATE_EVADE)) return PRIORITY_AVOID; // Don't attack units that are immune - if (target->HasUnitState(UNIT_STATE_UNATTACKABLE)) + if (target->HasUnitState(UNIT_STATE_UNATTACKABLE)) return PRIORITY_AVOID; // Attacking bot or allies = highest priority - if (IsAttackingGroup(target)) + if (IsAttackingGroup(target)) return PRIORITY_CRITICAL; // Casters and healers - if (IsCaster(target) || IsHealer(target)) + if (IsCaster(target) || IsHealer(target)) return PRIORITY_CASTER; // Elite mobs - if (target->GetTypeId() == TYPEID_UNIT) + if (target->GetTypeId() == TYPEID_UNIT) { Creature* creature = target->ToCreature(); if (creature->IsElite() || creature->IsDungeonBoss()) @@ -525,13 +537,13 @@ namespace Playerbot return false; // Check scan mode - if (m_scanMode == ScanMode::PASSIVE) + if (m_scanMode == ScanMode::PASSIVE) return false; if (m_scanMode == ScanMode::DEFENSIVE) { // Only engage if target is attacking us or our group - if (!IsAttackingGroup(target)) + if (!IsAttackingGroup(target)) return false; } @@ -541,53 +553,53 @@ namespace Playerbot return false; // Be more cautious at low health - if (healthPct < 50.0f && target->GetLevel() > m_bot->GetLevel()) + if (healthPct < 50.0f && target->GetLevel() > m_bot->GetLevel()) return false; // Level difference check int32 levelDiff = int32(target->GetLevel()) - int32(m_bot->GetLevel()); // Don't attack targets too high level - if (levelDiff > 3) + if (levelDiff > 3) return false; // Grey level mobs (too low level) - if (levelDiff < -7 && m_bot->GetLevel() > 10) + if (levelDiff < -7 && m_bot->GetLevel() > 10) { // Only engage if they're attacking us - if (!IsAttackingGroup(target)) + if (!IsAttackingGroup(target)) return false; } // Elite checks - if (target->GetTypeId() == TYPEID_UNIT) + if (target->GetTypeId() == TYPEID_UNIT) { Creature* creature = target->ToCreature(); bool isElite = creature->IsElite() || creature->IsDungeonBoss(); if (isElite) { // Don't solo elites if configured to avoid them - if (m_avoidElites && !m_bot->GetGroup()) + if (m_avoidElites && !m_bot->GetGroup()) return false; // Don't solo elites more than 1 level higher - if (levelDiff > 1 && !m_bot->GetGroup()) + if (levelDiff > 1 && !m_bot->GetGroup()) return false; // Don't engage elite if low on resources - if (m_bot->GetPowerPct(m_bot->GetPowerType()) < 50.0f) + if (m_bot->GetPowerPct(m_bot->GetPowerType()) < 50.0f) return false; } } // Don't engage if target is already fighting multiple players - if (IsTargetInCombatWithOthers(target)) + if (IsTargetInCombatWithOthers(target)) { // Unless it's attacking our group - if (!IsAttackingGroup(target)) + if (!IsAttackingGroup(target)) return false; } // Check if we can actually reach the target - if (!CanReachTarget(target)) + if (!CanReachTarget(target)) return false; return true; @@ -601,27 +613,27 @@ namespace Playerbot float maxRange = GetMaxEngageRange(); float maxRangeSq = maxRange * maxRange; // Too far away (using squared distance for comparison) - if (m_bot->GetExactDistSq(target) > maxRangeSq) + if (m_bot->GetExactDistSq(target) > maxRangeSq) return false; // Check if path exists (basic check) - if (!m_bot->IsWithinLOSInMap(target)) + if (!m_bot->IsWithinLOSInMap(target)) return false; // Calculate actual distance for range checks float dist = m_bot->GetExactDist(target); // For ranged classes, check if we're in range - if (m_preferRanged) + if (m_preferRanged) { // Check if we have a ranged attack spell in range // This is simplified - in production you'd check actual spell ranges - if (dist <= 36.0f) + if (dist <= 36.0f) return true; } // For melee, need to be able to get close - if (dist <= 5.0f || m_bot->IsWithinMeleeRange(target)) + if (dist <= 5.0f || m_bot->IsWithinMeleeRange(target)) return true; // Can we get there? @@ -640,7 +652,7 @@ namespace Playerbot threat = 100.0f + (levelDiff * 10.0f); // Increase threat for elites - if (target->GetTypeId() == TYPEID_UNIT) + if (target->GetTypeId() == TYPEID_UNIT) { Creature* creature = target->ToCreature(); if (creature->IsElite()) @@ -650,14 +662,14 @@ namespace Playerbot } // Increase threat for casters - if (IsCaster(target)) + if (IsCaster(target)) threat *= 1.5f; // Increase threat if target is attacking us - if (target->GetVictim() == m_bot) + if (target->GetVictim() == m_bot) threat *= 3.0f; // Increase threat if attacking group member - if (IsAttackingGroup(target)) + if (IsAttackingGroup(target)) threat *= 2.0f; // Reduce threat based on health @@ -668,7 +680,7 @@ namespace Playerbot float TargetScanner::GetScanRadius() const { // In combat, scan closer - if (m_bot->IsInCombat()) + if (m_bot->IsInCombat()) return m_baseRange * 0.75f; // Use base range @@ -688,7 +700,7 @@ namespace Playerbot uint32 interval = m_scanInterval; // Scan more frequently in combat - if (m_bot->IsInCombat()) + if (m_bot->IsInCombat()) interval = SCAN_INTERVAL_COMBAT; // Scan less frequently when idle else if (!m_bot->isMoving()) @@ -755,11 +767,11 @@ namespace Playerbot return false; // Check if currently casting - if (target->HasUnitState(UNIT_STATE_CASTING)) + if (target->HasUnitState(UNIT_STATE_CASTING)) return true; // Check creature type - if (target->GetTypeId() == TYPEID_UNIT) + if (target->GetTypeId() == TYPEID_UNIT) { Creature* creature = target->ToCreature(); if (creature->GetCreatureTemplate()->unit_class == 2 || // Paladin @@ -777,7 +789,7 @@ namespace Playerbot return false; // Simple check - priests and paladins are often healers - if (target->GetTypeId() == TYPEID_UNIT) + if (target->GetTypeId() == TYPEID_UNIT) { Creature* creature = target->ToCreature(); if (creature->GetCreatureTemplate()->unit_class == 5 || // Priest @@ -797,7 +809,7 @@ namespace Playerbot return false; // Elite or higher - if (target->GetTypeId() == TYPEID_UNIT) + if (target->GetTypeId() == TYPEID_UNIT) { Creature* creature = target->ToCreature(); if (creature->IsElite() || creature->IsDungeonBoss()) @@ -805,7 +817,7 @@ namespace Playerbot } // Much higher level - if (target->GetLevel() > m_bot->GetLevel() + 2) + if (target->GetLevel() > m_bot->GetLevel() + 2) return true; // Multiple adds - simplified check without calling non-const method @@ -820,7 +832,7 @@ namespace Playerbot uint32 expireTime = GameTime::GetGameTimeMS() + duration; // Check if already blacklisted - for (auto& entry : m_blacklist) + for (auto& entry : m_blacklist) { if (entry.guid == guid) { @@ -872,15 +884,15 @@ namespace Playerbot float baseDistance = m_preferRanged ? 30.0f : 5.0f; // Adjust for target type - if (target->GetTypeId() == TYPEID_UNIT) + if (target->GetTypeId() == TYPEID_UNIT) { Creature* creature = target->ToCreature(); // Stay further from elites - if (creature->IsElite()) + if (creature->IsElite()) baseDistance += 5.0f; // Get closer to casters to interrupt - if (IsCaster(target) && !m_preferRanged) + if (IsCaster(target) && !m_preferRanged) baseDistance = 5.0f; } @@ -905,10 +917,10 @@ namespace Playerbot if (victim->GetTypeId() == TYPEID_PLAYER) { // Not us - if (victim != m_bot) + if (victim != m_bot) { // Not in our group - if (!m_bot->GetGroup() || !m_bot->GetGroup()->IsMember(victim->GetGUID())) + if (!m_bot->GetGroup() || !m_bot->GetGroup()->IsMember(victim->GetGUID())) return true; } } diff --git a/src/server/game/DataStores/DB2Stores.cpp b/src/server/game/DataStores/DB2Stores.cpp index 14dfba461..8414144f9 100644 --- a/src/server/game/DataStores/DB2Stores.cpp +++ b/src/server/game/DataStores/DB2Stores.cpp @@ -1795,8 +1795,10 @@ void DB2Manager::IndexLoadedStores() _transmogSetItemsByTransmogSet[transmogSetItem->TransmogSetID].push_back(transmogSetItem); } + //WowCommunity for (UiChromieTimeExpansionInfoEntry const* uiChromieTimeExpansion : sUiChromieTimeExpansionInfoStore) _chromieTimeExpansionInfo.emplace(uiChromieTimeExpansion->ID, uiChromieTimeExpansion); + //WowCommunity std::unordered_multimap uiMapAssignmentByUiMap; for (UiMapAssignmentEntry const* uiMapAssignment : sUiMapAssignmentStore) diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 2c2cd78f8..dd7d4d6d0 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -2044,6 +2044,8 @@ class TC_GAME_API Player final : public Unit, public GridObject void InitAdvFlying(); void SendAdvFlyingSpeed(OpcodeServer opcode, AdvFlyingRateTypeSingle speedType, AdvFlyingRateTypeSingle maxSpeedType = AdvFlyingRateTypeSingle(0)); // [FIX][2024-07-14] Renamed member to avoid C++ type. void UpdateDynamicFlight(bool apply = false); + bool IsInAlliance() const { return m_team == ALLIANCE; } + bool IsInHorde() const { return m_team == HORDE; } //WowCommunity void ApplyTraitConfig(int32 configId, bool apply); void ApplyTraitEntry(int32 traitNodeEntryId, int32 rank, int32 grantedRanks, bool apply); diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index 2cf997faf..baee46732 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -1277,13 +1277,78 @@ void WorldSession::HandleChromieTimeSelectExpansion(WorldPackets::Misc::ChromieT return; int32 expansionId = chromieTimeSelectExpansion.ExpansionID; + uint32 questId = 0; - if (expansionId < 0 || expansionId > CURRENT_EXPANSION) + UiChromieTimeExpansionInfoEntry const* expansionInfo = sUiChromieTimeExpansionInfoStore.LookupEntry(chromieTimeSelectExpansion.ExpansionID); + if (!expansionInfo) + { + TC_LOG_INFO("server", "Player {} sent invalid ChromieTime ExpansionID {}", player->GetGUID().ToString(), chromieTimeSelectExpansion.ExpansionID); return; + } - // Blizzlike: only available for levels 10-70 (below max level) - if (player->GetLevel() < 10 || player->IsMaxLevel()) - return; + if (expansionInfo->RecommendPlayerConditionID > 0 && + !sConditionMgr->IsPlayerMeetingCondition(player, expansionInfo->RecommendPlayerConditionID)) + { + + } + + if (expansionInfo->SpellID > 0) + { + SpellCastResult result = player->CastSpell(player, expansionInfo->SpellID); + if (result != SPELL_CAST_OK) + { + TC_LOG_INFO("server", "ChromieTime Spell cast failed for player {} SpellID {} result {}", + player->GetGUID().ToString(), expansionInfo->SpellID, result); + } + } + + switch (chromieTimeSelectExpansion.ExpansionID) + { + case 5: + questId = _player->IsInHorde() ? 60887 : 60891; + _player->CastSpell(_player, 325537, true); // Selected Cataclysm + break; + case 6: + questId = _player->IsInHorde() ? 60123 : 60120; + _player->CastSpell(_player, 325400, true); // Selected Outland + break; + case 7: + questId = _player->IsInHorde() ? 60097 : 60096; + _player->CastSpell(_player, 325042, true); // Selected Northrend + break; + case 8: + questId = _player->IsInHorde() ? 60126 : 60125; + _player->CastSpell(_player, 325530, true); // Selected Pandaria + break; + case 9: + questId = 34398; + _player->CastSpell(_player, 325534, true); // Selected Draenor + break; + case 10: + questId = _player->IsInHorde() ? 43926 : 40519; + _player->CastSpell(_player, 325539, true); // Selected Legion + break; + case 14: + questId = _player->IsInHorde() ? 61874 : 60545; + _player->CastSpell(_player, 397733, true); // Selected Shadowlands + break; + case 15: + questId = _player->IsInHorde() ? 50668 : 46727; + _player->CastSpell(_player, 420123, true); // Selected BFA + break; + case 16: + questId = _player->IsInHorde() ? 65435 : 65436; + _player->CastSpell(_player, 452212, true); // Selected Dragonflight + break; + default: + break; + } + + if (questId) + if (_player->GetQuestStatus(questId) == QUEST_STATUS_NONE) + if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId)) + if (Creature* chromie = ObjectAccessor::GetCreature(*_player, chromieTimeSelectExpansion.GUID)) + _player->AddQuest(quest, chromie); player->SetChromieTime(expansionId); diff --git a/src/server/game/Server/Packets/MiscPackets.cpp b/src/server/game/Server/Packets/MiscPackets.cpp index 89cfce719..00e468e1d 100644 --- a/src/server/game/Server/Packets/MiscPackets.cpp +++ b/src/server/game/Server/Packets/MiscPackets.cpp @@ -853,6 +853,7 @@ WorldPacket const* AccountWarbandSceneUpdate::Write() //WowCommunity void ChromieTimeSelectExpansion::Read() { + _worldPacket >> GUID; _worldPacket >> ExpansionID; } diff --git a/src/server/game/Server/Packets/MiscPackets.h b/src/server/game/Server/Packets/MiscPackets.h index d44e10a8a..47b789947 100644 --- a/src/server/game/Server/Packets/MiscPackets.h +++ b/src/server/game/Server/Packets/MiscPackets.h @@ -1053,6 +1053,7 @@ namespace WorldPackets void Read() override; + ObjectGuid GUID; int32 ExpansionID = 0; };