bot + packets
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user