bot + packets

This commit is contained in:
luis
2026-03-17 20:34:19 -03:00
parent 4127c8485a
commit 34f88a8963
6 changed files with 221 additions and 138 deletions
+146 -134
View File
@@ -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<uint8>(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<DoubleBufferedSpatialGrid::CreatureSnapshot> 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;
}
}
+2
View File
@@ -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<int32, UiMapAssignmentEntry const*> uiMapAssignmentByUiMap;
for (UiMapAssignmentEntry const* uiMapAssignment : sUiMapAssignmentStore)
+2
View File
@@ -2044,6 +2044,8 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
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);
+69 -4
View File
@@ -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);
@@ -853,6 +853,7 @@ WorldPacket const* AccountWarbandSceneUpdate::Write()
//WowCommunity
void ChromieTimeSelectExpansion::Read()
{
_worldPacket >> GUID;
_worldPacket >> ExpansionID;
}
@@ -1053,6 +1053,7 @@ namespace WorldPackets
void Read() override;
ObjectGuid GUID;
int32 ExpansionID = 0;
};