Compare commits

..

No commits in common. "86390f90fd3a805d7d58443b9924deac6f8acb8a" and "64df7439d804e273af3051296e5e9b90344abf3c" have entirely different histories.

29 changed files with 268 additions and 374 deletions

View File

@ -656,10 +656,6 @@ AiPlayerbot.RandomGearQualityLimit = 3
# Default: 0 (no limit)
AiPlayerbot.RandomGearScoreLimit = 0
# If disabled, random bots can only upgrade equipment through looting and quests
# Default: 1 (enabled)
AiPlayerbot.IncrementalGearInit = 1
# Set minimum level of bots that will enchant their equipment (Maxlevel + 1 to disable)
# Default: 60
AiPlayerbot.MinEnchantingBotLevel = 60

View File

@ -80,13 +80,13 @@ uint8 AiFactory::GetPlayerSpecTab(Player* bot)
switch (bot->getClass())
{
case CLASS_MAGE:
tab = MAGE_TAB_FROST;
tab = 1;
break;
case CLASS_PALADIN:
tab = PALADIN_TAB_RETRIBUTION;
tab = 2;
break;
case CLASS_PRIEST:
tab = PRIEST_TAB_HOLY;
tab = 1;
break;
}

View File

@ -117,8 +117,6 @@ bool PlayerbotAIConfig::Initialize()
tellWhenAvoidAoe = sConfigMgr->GetOption<bool>("AiPlayerbot.TellWhenAvoidAoe", false);
randomGearLoweringChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomGearLoweringChance", 0.0f);
incrementalGearInit = sConfigMgr->GetOption<bool>("AiPlayerbot.IncrementalGearInit", true);
randomGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearQualityLimit", 3);
randomGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearScoreLimit", 0);

View File

@ -87,7 +87,6 @@ public:
std::vector<uint32> randomBotQuestIds;
uint32 randomBotTeleportDistance;
float randomGearLoweringChance;
bool incrementalGearInit;
int32 randomGearQualityLimit;
int32 randomGearScoreLimit;
float randomBotMinLevelChance, randomBotMaxLevelChance;

View File

@ -486,13 +486,6 @@ void RandomPlayerbotFactory::CreateRandomBots()
CharacterDatabase.Execute("DELETE FROM characters WHERE account IN (SELECT id FROM " + loginDBName + ".account WHERE username LIKE '{}%%')",
sPlayerbotAIConfig->randomBotAccountPrefix.c_str());
// Wait for the characters to be deleted before proceeding to dependent deletes
while (CharacterDatabase.QueueSize())
{
std::this_thread::sleep_for(1s);
}
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Extra 100ms fixed delay for safety.
// Clean up orphaned entries in playerbots_guild_tasks
PlayerbotsDatabase.Execute("DELETE FROM playerbots_guild_tasks WHERE owner NOT IN (SELECT guid FROM " + characterDBName + ".characters)");
@ -507,13 +500,11 @@ void RandomPlayerbotFactory::CreateRandomBots()
CharacterDatabase.Execute("DELETE FROM character_achievement WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_achievement_progress WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_action WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_arena_stats WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_aura WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_entry_point WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_glyphs WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_homebind WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_inventory WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM item_instance WHERE owner_guid NOT IN (SELECT guid FROM characters) AND owner_guid > 0");
CharacterDatabase.Execute("DELETE FROM character_inventory WHERE guid NOT IN (SELECT guid FROM characters)");
// Clean up pet data
CharacterDatabase.Execute("DELETE FROM character_pet WHERE owner NOT IN (SELECT guid FROM characters)");
@ -568,23 +559,11 @@ void RandomPlayerbotFactory::CreateRandomBots()
uint32 timer = getMSTime();
// After ALL deletions, make sure data is commited to DB
LoginDatabase.Execute("COMMIT");
CharacterDatabase.Execute("COMMIT");
PlayerbotsDatabase.Execute("COMMIT");
// Wait for all pending database operations to complete
while (LoginDatabase.QueueSize() || CharacterDatabase.QueueSize() || PlayerbotsDatabase.QueueSize())
{
std::this_thread::sleep_for(1s);
}
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Extra 100ms fixed delay for safety.
// Flush tables to ensure all data in memory are written to disk
LoginDatabase.Execute("FLUSH TABLES");
CharacterDatabase.Execute("FLUSH TABLES");
PlayerbotsDatabase.Execute("FLUSH TABLES");
LOG_INFO("playerbots", ">> Random bot accounts and data deleted in {} ms", GetMSTimeDiffToNow(timer));
LOG_INFO("playerbots", "Please reset the AiPlayerbot.DeleteRandomBotAccounts to 0 and restart the server...");
World::StopNow(SHUTDOWN_EXIT_CODE);

View File

@ -181,11 +181,6 @@ RandomPlayerbotMgr::RandomPlayerbotMgr() : PlayerbotHolder(), processTicks(0)
BattlegroundData.clear(); // Clear here and here only.
// Cleanup on server start: orphaned pet data that's often left behind by bot pets that no longer exist in the DB
CharacterDatabase.Execute("DELETE FROM pet_aura WHERE guid NOT IN (SELECT id FROM character_pet)");
CharacterDatabase.Execute("DELETE FROM pet_spell WHERE guid NOT IN (SELECT id FROM character_pet)");
CharacterDatabase.Execute("DELETE FROM pet_spell_cooldown WHERE guid NOT IN (SELECT id FROM character_pet)");
for (int bracket = BG_BRACKET_ID_FIRST; bracket < MAX_BATTLEGROUND_BRACKETS; ++bracket)
{
for (int queueType = BATTLEGROUND_QUEUE_AV; queueType < MAX_BATTLEGROUND_QUEUE_TYPES; ++queueType)
@ -374,7 +369,7 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/)
DelayLoginBotsTimer = time(nullptr) + sPlayerbotAIConfig->disabledWithoutRealPlayerLoginDelay;
}
}
else
else
{
if (DelayLoginBotsTimer)
{
@ -520,7 +515,7 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
{
maxAllowedBotCount -= currentBots.size();
maxAllowedBotCount = std::min(sPlayerbotAIConfig->randomBotsPerInterval, maxAllowedBotCount);
uint32 totalRatio = sPlayerbotAIConfig->randomBotAllianceRatio + sPlayerbotAIConfig->randomBotHordeRatio;
uint32 allowedAllianceCount = maxAllowedBotCount * (sPlayerbotAIConfig->randomBotAllianceRatio) / totalRatio;
@ -541,7 +536,7 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
{
// minus addclass bots account
int32 baseAccount = RandomPlayerbotFactory::CalculateTotalAccountCount() - sPlayerbotAIConfig->addClassAccountPoolSize;
if (baseAccount <= 0 || baseAccount > sPlayerbotAIConfig->randomBotAccounts.size())
{
LOG_ERROR("playerbots", "Account calculation error with PeriodicOnlineOffline");
@ -556,7 +551,7 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
PreparedQueryResult result = CharacterDatabase.Query(stmt);
if (!result)
continue;
std::vector<GuidClassRaceInfo> allGuidInfos;
do {
@ -724,7 +719,7 @@ void RandomPlayerbotMgr::CheckBgQueue()
LOG_DEBUG("playerbots", "Checking BG Queue...");
// Initialize Battleground Data (do not clear here)
for (int bracket = BG_BRACKET_ID_FIRST; bracket < MAX_BATTLEGROUND_BRACKETS; ++bracket)
{
for (int queueType = BATTLEGROUND_QUEUE_AV; queueType < MAX_BATTLEGROUND_QUEUE_TYPES; ++queueType)
@ -798,11 +793,11 @@ void RandomPlayerbotMgr::CheckBgQueue()
{
std::vector<uint32>* instanceIds = nullptr;
uint32 instanceId = player->GetBattleground()->GetInstanceID();
instanceIds = &BattlegroundData[queueTypeId][bracketId].bgInstances;
if (instanceIds && std::find(instanceIds->begin(), instanceIds->end(), instanceId) == instanceIds->end())
instanceIds->push_back(instanceId);
BattlegroundData[queueTypeId][bracketId].bgInstanceCount = instanceIds->size();
}
}
@ -1483,7 +1478,7 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
z = 0.05f + ground;
PlayerInfo const* pInfo = sObjectMgr->GetPlayerInfo(bot->getRace(true), bot->getClass());
float dis = loc.GetExactDist(pInfo->positionX, pInfo->positionY, pInfo->positionZ);
// yunfan: distance check for low level
if (bot->GetLevel() <= 4 && (loc.GetMapId() != pInfo->mapId || dis > 500.0f))
{
@ -1611,8 +1606,8 @@ void RandomPlayerbotMgr::PrepareZone2LevelBracket()
zone2LevelBracket[2817] = {77, 80}; // Crystalsong Forest
zone2LevelBracket[3537] = {68, 75}; // Borean Tundra
zone2LevelBracket[3711] = {75, 80}; // Sholazar Basin
zone2LevelBracket[4197] = {79, 80}; // Wintergrasp
zone2LevelBracket[4197] = {79, 80}; // Wintergrasp
// Override with values from config
for (auto const& [zoneId, bracketPair] : sPlayerbotAIConfig->zoneBrackets) {
zone2LevelBracket[zoneId] = {bracketPair.first, bracketPair.second};
@ -1704,8 +1699,7 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
"position_y, "
"position_z, "
"orientation, "
"t.faction, "
"t.entry "
"t.faction "
"FROM "
"creature c "
"INNER JOIN creature_template t on c.id1 = t.entry "
@ -1727,11 +1721,6 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
float z = fields[3].Get<float>();
float orient = fields[4].Get<float>();
uint32 faction = fields[5].Get<uint32>();
uint32 tEntry = fields[6].Get<uint32>();
if (tEntry == 3838 || tEntry == 29480)
continue;
const FactionTemplateEntry* entry = sFactionTemplateStore.LookupEntry(faction);
WorldLocation loc(mapId, x + cos(orient) * 5.0f, y + sin(orient) * 5.0f, z + 0.5f, orient + M_PI);
@ -1782,7 +1771,7 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
}
LOG_INFO("playerbots", ">> {} innkeepers locations for level collected.", collected_locs);
}
results = WorldDatabase.Query(
"SELECT "
"map, "
@ -1799,8 +1788,13 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
"AND t.npcflag != 135298 "
"AND t.minlevel != 55 "
"AND t.minlevel != 65 "
"AND t.faction not in (35, 474, 69, 57) "
"AND t.entry not in (30606, 30608, 29282) "
"AND t.faction != 35 "
"AND t.faction != 474 "
"AND t.faction != 69 "
"AND t.entry != 30606 "
"AND t.entry != 30608 "
"AND t.entry != 29282 "
"AND t.faction != 69 "
"AND map IN ({}) "
"ORDER BY "
"t.minlevel;",
@ -1883,12 +1877,12 @@ void RandomPlayerbotMgr::Init()
{
if (sPlayerbotAIConfig->addClassCommand)
sRandomPlayerbotMgr->PrepareAddclassCache();
if (sPlayerbotAIConfig->enabled)
{
sRandomPlayerbotMgr->PrepareTeleportCache();
}
if (sPlayerbotAIConfig->randomBotJoinBG)
sRandomPlayerbotMgr->LoadBattleMastersCache();
@ -2031,7 +2025,7 @@ void RandomPlayerbotMgr::RandomizeFirst(Player* bot)
if (sPlayerbotAIConfig->syncLevelWithPlayers)
maxLevel = std::max(sPlayerbotAIConfig->randomBotMinLevel,
std::min(playersLevel, sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)));
uint32 minLevel = sPlayerbotAIConfig->randomBotMinLevel;
if (bot->getClass() == CLASS_DEATH_KNIGHT)
{
@ -2628,7 +2622,7 @@ void RandomPlayerbotMgr::OnBotLoginInternal(Player* const bot)
{
LOG_INFO("playerbots", "{}/{} Bot {} logged in", playerBots.size(), sRandomPlayerbotMgr->GetMaxAllowedBotCount(),
bot->GetName().c_str());
if (playerBots.size() == sRandomPlayerbotMgr->GetMaxAllowedBotCount())
{
_isBotLogging = false;
@ -2763,19 +2757,14 @@ void RandomPlayerbotMgr::PrintStats()
std::map<uint8, uint32> perRace;
std::map<uint8, uint32> perClass;
std::map<uint8, uint32> lvlPerRace;
std::map<uint8, uint32> lvlPerClass;
for (uint8 race = RACE_HUMAN; race < MAX_RACES; ++race)
{
perRace[race] = 0;
lvlPerRace[race] = 0;
}
for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES; ++cls)
{
perClass[cls] = 0;
lvlPerClass[cls] = 0;
}
uint32 dps = 0;
@ -2813,9 +2802,6 @@ void RandomPlayerbotMgr::PrintStats()
++perRace[bot->getRace()];
++perClass[bot->getClass()];
lvlPerClass[bot->getClass()] += bot->GetLevel();
lvlPerRace[bot->getRace()] += bot->GetLevel();
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI->AllowActivity())
++active;
@ -2872,7 +2858,7 @@ void RandomPlayerbotMgr::PrintStats()
++tank;
else
++dps;
zoneCount[bot->GetZoneId()]++;
if (sPlayerbotAIConfig->enableNewRpgStrategy)
@ -2883,7 +2869,7 @@ void RandomPlayerbotMgr::PrintStats()
}
}
LOG_INFO("playerbots", "Bots level:");
// uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
uint32_t currentAlliance = 0, currentHorde = 0;
@ -2908,21 +2894,15 @@ void RandomPlayerbotMgr::PrintStats()
LOG_INFO("playerbots", "Bots race:");
for (uint8 race = RACE_HUMAN; race < MAX_RACES; ++race)
{
if (perRace[race]) {
uint32 lvl = lvlPerRace[race] * 10 / perRace[race];
float flvl = lvl / 10.0f;
LOG_INFO("playerbots", " {}: {}, avg lvl: {}", ChatHelper::FormatRace(race).c_str(), perRace[race], flvl);
}
if (perRace[race])
LOG_INFO("playerbots", " {}: {}", ChatHelper::FormatRace(race).c_str(), perRace[race]);
}
LOG_INFO("playerbots", "Bots class:");
for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES; ++cls)
{
if (perClass[cls]) {
uint32 lvl = lvlPerClass[cls] * 10 / perClass[cls];
float flvl = lvl / 10.0f;
LOG_INFO("playerbots", " {}: {}, avg lvl: {}", ChatHelper::FormatClass(cls).c_str(), perClass[cls], flvl);
}
if (perClass[cls])
LOG_INFO("playerbots", " {}: {}", ChatHelper::FormatClass(cls).c_str(), perClass[cls]);
}
LOG_INFO("playerbots", "Bots role:");
@ -2945,7 +2925,7 @@ void RandomPlayerbotMgr::PrintStats()
LOG_INFO("playerbots", " In BG: {}", inBg);
LOG_INFO("playerbots", " In Rest: {}", rest);
LOG_INFO("playerbots", " Dead: {}", dead);
// LOG_INFO("playerbots", "Bots zone:");
// for (auto &[zond_id, counter] : zoneCount)
// {
@ -2953,7 +2933,7 @@ void RandomPlayerbotMgr::PrintStats()
// std::string name = PlayerbotAI::GetLocalizedAreaName(entry);
// LOG_INFO("playerbots", " {}: {}", name, counter);
// }
if (sPlayerbotAIConfig->enableNewRpgStrategy)
{
LOG_INFO("playerbots", "Bots rpg status:");

View File

@ -224,22 +224,24 @@ void PlayerbotFactory::Randomize(bool incremental)
{
bot->resetTalents(true);
}
// bot->SaveToDB(false, false);
ClearSkills();
// bot->SaveToDB(false, false);
ClearSpells();
// bot->SaveToDB(false, false);
if (!incremental)
{
ClearSkills();
ClearSpells();
ResetQuests();
if (!sPlayerbotAIConfig->equipmentPersistence || level < sPlayerbotAIConfig->equipmentPersistenceLevel)
{
ClearAllItems();
}
}
ClearInventory();
if (!sPlayerbotAIConfig->equipmentPersistence || level < sPlayerbotAIConfig->equipmentPersistenceLevel)
{
ClearAllItems();
}
bot->RemoveAllSpellCooldown();
UnbindInstance();
bot->GiveLevel(level);
bot->InitStatsForLevel(true);
bot->InitStatsForLevel();
CancelAuras();
// bot->SaveToDB(false, false);
if (pmo)
@ -278,6 +280,7 @@ void PlayerbotFactory::Randomize(bool incremental)
LOG_DEBUG("playerbots", "Initializing skills (step 1)...");
pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "PlayerbotFactory_Skills1");
InitSkills();
// InitTradeSkills();
if (pmo)
pmo->finish();
@ -334,8 +337,7 @@ void PlayerbotFactory::Randomize(bool incremental)
if (!incremental || !sPlayerbotAIConfig->equipmentPersistence ||
bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
{
if (sPlayerbotAIConfig->incrementalGearInit || !incremental)
InitEquipment(incremental, incremental ? false : sPlayerbotAIConfig->twoRoundsGearInit);
InitEquipment(incremental, incremental ? false : sPlayerbotAIConfig->twoRoundsGearInit);
}
// bot->SaveToDB(false, false);
if (pmo)
@ -1022,21 +1024,9 @@ void PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_templa
/// @todo: match current talent with template
specTab = AiFactory::GetPlayerSpecTab(bot);
/// @todo: fix cat druid hardcode
if (bot->getClass() == CLASS_DRUID && specTab == DRUID_TAB_FERAL && bot->GetLevel() >= 20)
{
bool isCat = !bot->HasAura(16931);
if (!isCat && bot->GetLevel() == 20)
{
uint32 bearP = sPlayerbotAIConfig->randomClassSpecProb[cls][1];
uint32 catP = sPlayerbotAIConfig->randomClassSpecProb[cls][3];
if (urand(1, bearP + catP) <= catP)
isCat = true;
}
if (isCat)
{
specTab = 3;
}
}
if (bot->getClass() == CLASS_DRUID && specTab == DRUID_TAB_FERAL && bot->GetLevel() >= 20 &&
!bot->HasAura(16931))
specTab = 3;
}
else
{
@ -1609,51 +1599,9 @@ void Shuffle(std::vector<uint32>& items)
void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
{
if (incremental && !sPlayerbotAIConfig->incrementalGearInit)
return;
if (level < 5) {
// original items
if (CharStartOutfitEntry const* oEntry = GetCharStartOutfitEntry(bot->getRace(), bot->getClass(), bot->getGender()))
{
for (int j = 0; j < MAX_OUTFIT_ITEMS; ++j)
{
if (oEntry->ItemId[j] <= 0)
continue;
uint32 itemId = oEntry->ItemId[j];
// skip hearthstone
if (itemId == 6948)
continue;
// just skip, reported in ObjectMgr::LoadItemTemplates
ItemTemplate const* iProto = sObjectMgr->GetItemTemplate(itemId);
if (!iProto)
continue;
// BuyCount by default
uint32 count = iProto->BuyCount;
// special amount for food/drink
if (iProto->Class == ITEM_CLASS_CONSUMABLE && iProto->SubClass == ITEM_SUBCLASS_FOOD)
{
continue;
}
if (bot->HasItemCount(itemId, count)) {
continue;
}
bot->StoreNewItemInBestSlots(itemId, count);
}
}
return;
}
std::unordered_map<uint8, std::vector<uint32>> items;
// int tab = AiFactory::GetPlayerSpecTab(bot);
uint32 blevel = bot->GetLevel();
int32 delta = std::min(blevel, 10u);

View File

@ -29,12 +29,12 @@ void StatsCollector::CollectItemStats(ItemTemplate const* proto)
{
if (proto->IsRangedWeapon())
{
float val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
uint32 val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
stats[STATS_TYPE_RANGED_DPS] += val;
}
else if (proto->IsWeapon())
{
float val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
uint32 val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
stats[STATS_TYPE_MELEE_DPS] += val;
}
stats[STATS_TYPE_ARMOR] += proto->Armor;
@ -436,10 +436,10 @@ void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val)
switch (itemStatType)
{
case ITEM_MOD_MANA:
stats[STATS_TYPE_MANA_REGENERATION] += (float)val / 10;
stats[STATS_TYPE_MANA_REGENERATION] += val / 10;
break;
case ITEM_MOD_HEALTH:
stats[STATS_TYPE_STAMINA] += (float)val / 15;
stats[STATS_TYPE_STAMINA] += val / 15;
break;
case ITEM_MOD_AGILITY:
stats[STATS_TYPE_AGILITY] += val;
@ -747,11 +747,11 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu
}
}
float StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
int32 StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
{
// float basePointsPerLevel = effectInfo.RealPointsPerLevel; //not used, line marked for removal.
float basePoints = effectInfo.BasePoints;
int32 randomPoints = effectInfo.DieSides;
int32 basePoints = effectInfo.BasePoints;
int32 randomPoints = int32(effectInfo.DieSides);
switch (randomPoints)
{
@ -761,7 +761,7 @@ float StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
basePoints += 1;
break;
default:
float randvalue = (1 + randomPoints) / 2.0f;
int32 randvalue = (1 + randomPoints) / 2;
basePoints += randvalue;
break;
}

View File

@ -71,7 +71,7 @@ public:
bool CheckSpellValidation(uint32 spellFamilyName, flag96 spelFalimyFlags, bool strict = true);
public:
float stats[STATS_TYPE_MAX];
int32 stats[STATS_TYPE_MAX];
private:
void CollectByItemStatType(uint32 itemStatType, int32 val);
@ -80,7 +80,7 @@ private:
void HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger,
uint32 triggerCooldown);
float AverageValue(const SpellEffectInfo& effectInfo);
int32 AverageValue(const SpellEffectInfo& effectInfo);
private:
CollectorType type_;

View File

@ -33,7 +33,6 @@ StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player)
else
type_ = CollectorType::RANGED;
cls = player->getClass();
lvl = player->GetLevel();
tab = AiFactory::GetPlayerSpecTab(player);
collector_ = std::make_unique<StatsCollector>(type_, cls);
@ -71,7 +70,7 @@ float StatsWeightCalculator::CalculateItem(uint32 itemId, int32 randomPropertyId
Reset();
collector_->CollectItemStats(proto);
if (randomPropertyIds != 0)
CalculateRandomProperty(randomPropertyIds, itemId);
@ -182,7 +181,6 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_ARMOR] += 0.001f;
stats_weights_[STATS_TYPE_BONUS] += 1.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 0.01f;
stats_weights_[STATS_TYPE_RANGED_DPS] += 0.01f;
if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEASTMASTER || tab == HUNTER_TAB_SURVIVAL))
{
@ -531,13 +529,13 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
// enhancement, rogue, ice dk, unholy dk, shield tank, fury warrior without titan's grip but with duel wield
if (((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && player_->CanDualWield()) ||
(cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() &&
player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() && player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) ||
(cls == CLASS_PALADIN && tab == PALADIN_TAB_PROTECTION)))
{
weight_ *= 0.1;
}
}
// spec with double hand
// fury without duel wield, arms, bear, retribution, blood dk
@ -553,11 +551,15 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
weight_ *= 0.1;
}
// caster's main hand (cannot duel weapon but can equip two-hands stuff)
if (cls == CLASS_MAGE || cls == CLASS_PRIEST || cls == CLASS_WARLOCK || cls == CLASS_DRUID ||
if (cls == CLASS_MAGE ||
cls == CLASS_PRIEST ||
cls == CLASS_WARLOCK ||
cls == CLASS_DRUID ||
(cls == CLASS_SHAMAN && !player_->CanDualWield()))
{
weight_ *= 0.65;
}
}
// fury with titan's grip
if ((!isDoubleHand || proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM ||
@ -566,16 +568,16 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
{
weight_ *= 0.1;
}
if (cls == CLASS_HUNTER && proto->SubClass == ITEM_SUBCLASS_WEAPON_THROWN)
{
weight_ *= 0.1;
}
if (lvl >= 10 && cls == CLASS_ROGUE && (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY) &&
proto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER)
if (cls == CLASS_ROGUE && (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY) &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER)
{
weight_ *= 1.5;
weight_ *= 0.5;
}
if (cls == CLASS_ROGUE && player_->HasAura(13964) &&
@ -658,7 +660,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
else
validPoints = 0;
}
collector_->stats[STATS_TYPE_HIT] = std::min(collector_->stats[STATS_TYPE_HIT], validPoints);
collector_->stats[STATS_TYPE_HIT] = std::min(collector_->stats[STATS_TYPE_HIT], (int)validPoints);
}
{
@ -675,7 +677,8 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
else
validPoints = 0;
collector_->stats[STATS_TYPE_EXPERTISE] = std::min(collector_->stats[STATS_TYPE_EXPERTISE], validPoints);
collector_->stats[STATS_TYPE_EXPERTISE] =
std::min(collector_->stats[STATS_TYPE_EXPERTISE], (int)validPoints);
}
}
@ -692,7 +695,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
else
validPoints = 0;
collector_->stats[STATS_TYPE_DEFENSE] = std::min(collector_->stats[STATS_TYPE_DEFENSE], validPoints);
collector_->stats[STATS_TYPE_DEFENSE] = std::min(collector_->stats[STATS_TYPE_DEFENSE], (int)validPoints);
}
}
@ -711,7 +714,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
validPoints = 0;
collector_->stats[STATS_TYPE_ARMOR_PENETRATION] =
std::min(collector_->stats[STATS_TYPE_ARMOR_PENETRATION], validPoints);
std::min(collector_->stats[STATS_TYPE_ARMOR_PENETRATION], (int)validPoints);
}
}
}

View File

@ -57,7 +57,6 @@ private:
CollectorType hitOverflowType_;
std::unique_ptr<StatsCollector> collector_;
uint8 cls;
uint8 lvl;
int tab;
bool enable_overflow_penalty_;
bool enable_item_set_bonus_;

View File

@ -163,8 +163,7 @@ void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
PlayerbotFactory factory(bot, bot->GetLevel());
if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
{
if (sPlayerbotAIConfig->incrementalGearInit)
factory.InitEquipment(true);
factory.InitEquipment(true);
}
factory.InitAmmo();
return;

View File

@ -187,8 +187,7 @@ void EquipAction::EquipItem(Item* item)
// Priority 1: Replace main hand if the new weapon is strictly better
// and if conditions allow (e.g. no conflicting 2H logic)
bool betterThanMH = (newItemScore > mainHandScore);
// If a one-handed weapon is better, we can still use it instead of a two-handed weapon
bool mhConditionOK = (invType != INVTYPE_2HWEAPON ||
bool mhConditionOK = ((invType != INVTYPE_2HWEAPON && !have2HWeaponEquipped) ||
(isTwoHander && !canTitanGrip) ||
(canTitanGrip && isValidTGWeapon));

View File

@ -1801,6 +1801,7 @@ const Movement::PointsArray MovementAction::SearchForBestPath(float x, float y,
bool FleeAction::Execute(Event event)
{
// return Flee(AI_VALUE(Unit*, "current target"));
return MoveAway(AI_VALUE(Unit*, "current target"), sPlayerbotAIConfig->fleeDistance, true);
}
@ -1810,10 +1811,6 @@ bool FleeAction::isUseful()
{
return false;
}
Unit* target = AI_VALUE(Unit*, "current target");
if (target && target->IsInWorld() && !bot->IsWithinMeleeRange(target))
return false;
return true;
}

View File

@ -73,7 +73,7 @@ bool TaxiAction::Execute(Event event)
{
if (Creature* npcPtr = ObjectAccessor::GetCreature(*bot, npcGuid))
if (!movement.taxiNodes.empty())
bot->ActivateTaxiPathTo(movement.taxiNodes, npcPtr, 0);
bot->ActivateTaxiPathTo(movement.taxiNodes, npcPtr);
},
delay);
botAI->SetNextCheckDelay(delay + 50);
@ -114,7 +114,7 @@ bool TaxiAction::Execute(Event event)
return bot->ActivateTaxiPathTo({entry->from, entry->to}, npc, 0);
}
if (!movement.taxiNodes.empty() && !bot->ActivateTaxiPathTo(movement.taxiNodes, npc, 0))
if (!movement.taxiNodes.empty() && !bot->ActivateTaxiPathTo(movement.taxiNodes, npc))
{
movement.taxiNodes.clear();
movement.Set(nullptr);

View File

@ -60,7 +60,7 @@ ArcaneMageStrategy::ArcaneMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy
NextAction** ArcaneMageStrategy::getDefaultActions()
{
return NextAction::array(0, new NextAction("arcane blast", ACTION_DEFAULT + 0.3f),
new NextAction("frostbolt", ACTION_DEFAULT + 0.2f), // arcane immune target
// new NextAction("arcane barrage", ACTION_DEFAULT + 0.2f), // cast during movement
new NextAction("fire blast", ACTION_DEFAULT + 0.1f), // cast during movement
new NextAction("shoot", ACTION_DEFAULT), nullptr);
}

View File

@ -10,8 +10,7 @@
NextAction** FireMageStrategy::getDefaultActions()
{
return NextAction::array(0, new NextAction("fireball", ACTION_DEFAULT + 0.3f),
new NextAction("frostbolt", ACTION_DEFAULT + 0.2f), // fire immune target
return NextAction::array(0, new NextAction("fireball", ACTION_DEFAULT + 0.2f),
new NextAction("fire blast", ACTION_DEFAULT + 0.1f), // cast during movement
new NextAction("shoot", ACTION_DEFAULT), NULL);
}

View File

@ -37,6 +37,13 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
botAI->rpgInfo.SetMoveFarTo(dest);
}
float dis = bot->GetExactDist(dest);
if (dis < pathFinderDis)
{
return MoveTo(dest.getMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), false, false,
false, true);
}
// performance optimization
if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL))
{
@ -63,13 +70,6 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), dest.getMapId(), bot->GetZoneId(), zone_name);
return bot->TeleportTo(dest);
}
float dis = bot->GetExactDist(dest);
if (dis < pathFinderDis)
{
return MoveTo(dest.getMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), false, false,
false, true);
}
float minDelta = M_PI;
const float x = bot->GetPositionX();
@ -852,18 +852,10 @@ WorldPosition NewRpgBaseAction::SelectRandomGrindPos(Player* bot)
float loRange = 2500.0f;
if (bot->GetLevel() < 5)
{
hiRange /= 3;
loRange /= 3;
hiRange /= 10;
loRange /= 10;
}
std::vector<WorldLocation> lo_prepared_locs, hi_prepared_locs;
bool inCity = false;
if (AreaTableEntry const* zone = sAreaTableStore.LookupEntry(bot->GetZoneId()))
{
if (zone->flags & AREA_FLAG_CAPITAL)
inCity = true;
}
for (auto& loc : locs)
{
if (bot->GetMapId() != loc.GetMapId())
@ -871,17 +863,17 @@ WorldPosition NewRpgBaseAction::SelectRandomGrindPos(Player* bot)
if (bot->GetExactDist(loc) > 2500.0f)
continue;
if (!inCity && bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) !=
if (bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) !=
bot->GetZoneId())
continue;
if (bot->GetExactDist(loc) < hiRange)
if (bot->GetExactDist(loc) < 500.0f)
{
hi_prepared_locs.push_back(loc);
}
if (bot->GetExactDist(loc) < loRange)
if (bot->GetExactDist(loc) < 2500.0f)
{
lo_prepared_locs.push_back(loc);
}
@ -908,15 +900,6 @@ WorldPosition NewRpgBaseAction::SelectRandomInnKeeperPos(Player* bot)
const std::vector<WorldLocation>& locs = IsAlliance(bot->getRace())
? sRandomPlayerbotMgr->allianceStarterPerLevelCache[bot->GetLevel()]
: sRandomPlayerbotMgr->hordeStarterPerLevelCache[bot->GetLevel()];
bool inCity = false;
if (AreaTableEntry const* zone = sAreaTableStore.LookupEntry(bot->GetZoneId()))
{
if (zone->flags & AREA_FLAG_CAPITAL)
inCity = true;
}
std::vector<WorldLocation> prepared_locs;
for (auto& loc : locs)
{
@ -927,7 +910,7 @@ WorldPosition NewRpgBaseAction::SelectRandomInnKeeperPos(Player* bot)
if (bot->GetExactDist(loc) > range)
continue;
if (!inCity && bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) !=
if (bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) !=
bot->GetZoneId())
continue;

View File

@ -116,11 +116,12 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
botAI->rpgInfo.status == RPG_GO_INNKEEPER ||
botAI->rpgInfo.status == RPG_DO_QUEST;
bool notHostile = !bot->IsHostileTo(unit); /*|| (unit->ToCreature() && unit->ToCreature()->IsCivilian());*/
float aggroRange = 30.0f;
if (unit->ToCreature())
aggroRange = std::min(30.0f, unit->ToCreature()->GetAggroRange(bot) + 10.0f);
bool outOfAggro = unit->ToCreature() && bot->GetDistance(unit) > aggroRange;
if (inactiveGrindStatus && outOfAggro)
if (inactiveGrindStatus && (outOfAggro || notHostile))
{
if (needForQuestMap.find(unit->GetEntry()) == needForQuestMap.end())
needForQuestMap[unit->GetEntry()] = needForQuest(unit);

View File

@ -84,8 +84,6 @@ void AfflictionWarlockStrategy::InitTriggers(std::vector<TriggerNode*>& triggers
// Life Tap glyph buff, and Life Tap as filler
triggers.push_back(new TriggerNode("life tap glyph buff", NextAction::array(0, new NextAction("life tap", 29.0f), nullptr)));
triggers.push_back(new TriggerNode("life tap", NextAction::array(0, new NextAction("life tap", 5.1f), nullptr)));
triggers.push_back(new TriggerNode("enemy too close for spell", NextAction::array(0, new NextAction("flee", 39.0f), nullptr)));
}
// ===== AoE Strategy, 3+ enemies =====

View File

@ -30,6 +30,7 @@ public:
creators["seed of corruption"] = &seed_of_corruption;
creators["rain of fire"] = &rain_of_fire;
creators["demon charge"] = &demon_charge;
creators["shadow cleave"] = &shadow_cleave;
}
private:
@ -51,6 +52,7 @@ private:
static ActionNode* seed_of_corruption(PlayerbotAI*) { return new ActionNode("seed of corruption", nullptr, nullptr, nullptr); }
static ActionNode* rain_of_fire(PlayerbotAI*) { return new ActionNode("rain of fire", nullptr, nullptr, nullptr); }
static ActionNode* demon_charge(PlayerbotAI*) { return new ActionNode("demon charge", nullptr, nullptr, nullptr); }
static ActionNode* shadow_cleave(PlayerbotAI*) { return new ActionNode("shadow cleave", nullptr, nullptr, nullptr); }
};
// ===== Single Target Strategy =====
@ -95,8 +97,6 @@ void DemonologyWarlockStrategy::InitTriggers(std::vector<TriggerNode*>& triggers
// Life Tap glyph buff, and Life Tap as filler
triggers.push_back(new TriggerNode("life tap glyph buff", NextAction::array(0, new NextAction("life tap", 29.0f), nullptr)));
triggers.push_back(new TriggerNode("life tap", NextAction::array(0, new NextAction("life tap", 5.1f), nullptr)));
triggers.push_back(new TriggerNode("meta melee flee check", NextAction::array(0, new NextAction("flee", 39.0f), nullptr)));
}
// ===== AoE Strategy, 3+ enemies =====
@ -122,5 +122,6 @@ void MetaMeleeAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("immolation aura active", NextAction::array(0,
new NextAction("reach melee", 25.5f),
new NextAction("demon charge", 25.0f), nullptr)));
new NextAction("demon charge", 25.0f),
new NextAction("shadow cleave", 24.5f), nullptr)));
}

View File

@ -91,8 +91,6 @@ void DestructionWarlockStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
// Life Tap glyph buff, and Life Tap as filler
triggers.push_back(new TriggerNode("life tap glyph buff", NextAction::array(0, new NextAction("life tap", 29.0f), nullptr)));
triggers.push_back(new TriggerNode("life tap", NextAction::array(0, new NextAction("life tap", 5.1f), nullptr)));
triggers.push_back(new TriggerNode("enemy too close for spell", NextAction::array(0, new NextAction("flee", 39.0f), nullptr)));
}
// ===== AoE Strategy, 3+ enemies =====

View File

@ -0,0 +1,88 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "DpsWarlockStrategy.h"
#include "Playerbots.h"
// This strategy is designed for low-level Warlocks without talents.
// All of the important spells/cooldowns have been migrated to
// their respective specs.
// ===== Action Node Factory =====
class DpsWarlockStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{
public:
DpsWarlockStrategyActionNodeFactory()
{
creators["corruption"] = &corruption;
creators["curse of agony"] = &curse_of_agony;
creators["immolate"] = &immolate;
creators["shadow bolt"] = &shadow_bolt;
creators["life tap"] = &life_tap;
creators["shadowflame"] = &shadowflame;
creators["seed of corruption"] = &seed_of_corruption;
creators["rain of fire"] = &rain_of_fire;
creators["drain soul"] = &drain_soul;
}
private:
static ActionNode* corruption(PlayerbotAI*) { return new ActionNode("corruption", nullptr, nullptr, nullptr); }
static ActionNode* curse_of_agony(PlayerbotAI*){return new ActionNode("curse of agony", nullptr, nullptr, nullptr);}
static ActionNode* immolate(PlayerbotAI*) { return new ActionNode("immolate", nullptr, nullptr, nullptr); }
static ActionNode* shadow_bolt(PlayerbotAI*) { return new ActionNode("shadow bolt", nullptr, nullptr, nullptr); }
static ActionNode* life_tap(PlayerbotAI*) { return new ActionNode("life tap", nullptr, nullptr, nullptr); }
static ActionNode* shadowflame(PlayerbotAI*) { return new ActionNode("shadowflame", nullptr, nullptr, nullptr); }
static ActionNode* seed_of_corruption(PlayerbotAI*){return new ActionNode("seed of corruption", nullptr, nullptr, nullptr);}
static ActionNode* rain_of_fire(PlayerbotAI*) { return new ActionNode("rain of fire", nullptr, nullptr, nullptr); }
static ActionNode* drain_soul(PlayerbotAI*) { return new ActionNode("drain soul", nullptr, nullptr, nullptr); }
};
// ===== Single Target Strategy =====
DpsWarlockStrategy::DpsWarlockStrategy(PlayerbotAI* botAI) : GenericWarlockStrategy(botAI)
{
actionNodeFactories.Add(new DpsWarlockStrategyActionNodeFactory());
}
// ===== Default Actions =====
NextAction** DpsWarlockStrategy::getDefaultActions()
{
return NextAction::array(0,
new NextAction("immolate", 5.5f),
new NextAction("corruption", 5.4f),
new NextAction("curse of agony", 5.3f),
new NextAction("shadow bolt", 5.2f),
new NextAction("shoot", 5.0f), nullptr);
}
// ===== Trigger Initialization ===
void DpsWarlockStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
GenericWarlockStrategy::InitTriggers(triggers);
// Main DoT triggers for high uptime
triggers.push_back(new TriggerNode("corruption on attacker", NextAction::array(0, new NextAction("corruption on attacker", 20.0f), nullptr)));
triggers.push_back(new TriggerNode("curse of agony on attacker", NextAction::array(0, new NextAction("curse of agony on attacker", 19.5f), nullptr)));
triggers.push_back(new TriggerNode("immolate on attacker", NextAction::array(0, new NextAction("immolate on attacker", 19.0f), nullptr)));
triggers.push_back(new TriggerNode("corruption", NextAction::array(0, new NextAction("corruption", 18.5f), nullptr)));
triggers.push_back(new TriggerNode("curse of agony", NextAction::array(0, new NextAction("curse of agony", 18.0f), nullptr)));
triggers.push_back(new TriggerNode("immolate", NextAction::array(0, new NextAction("immolate", 17.5f), nullptr)));
// Drain Soul as execute if target is low HP
triggers.push_back(new TriggerNode("target critical health", NextAction::array(0, new NextAction("drain soul", 17.0f), nullptr)));
// Cast during movement or to activate glyph buff
triggers.push_back(new TriggerNode("life tap", NextAction::array(0, new NextAction("life tap", ACTION_DEFAULT + 0.1f), nullptr)));
triggers.push_back(new TriggerNode("life tap glyph buff", NextAction::array(0, new NextAction("life tap", 28.0f), NULL)));
}
// ===== AoE Strategy, 3+ enemies =====
void DpsAoeWarlockStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("medium aoe", NextAction::array(0,
new NextAction("shadowflame", 22.5f),
new NextAction("seed of corruption on attacker", 22.0f),
new NextAction("seed of corruption", 21.5f),
new NextAction("rain of fire", 21.0f), nullptr)));
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_DPSWARLOCKSTRATEGY_H
#define _PLAYERBOT_DPSWARLOCKSTRATEGY_H
#include "GenericWarlockStrategy.h"
#include "Strategy.h"
class PlayerbotAI;
class DpsWarlockStrategy : public GenericWarlockStrategy
{
public:
DpsWarlockStrategy(PlayerbotAI* botAI);
std::string const getName() override { return "dps"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
NextAction** getDefaultActions() override;
uint32 GetType() const override { return GenericWarlockStrategy::GetType() | STRATEGY_TYPE_DPS; }
};
class DpsAoeWarlockStrategy : public CombatStrategy
{
public:
DpsAoeWarlockStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {}
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "aoe"; }
};
#endif

View File

@ -89,18 +89,20 @@ void GenericWarlockNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& tr
Player* bot = botAI->GetBot();
int tab = AiFactory::GetPlayerSpecTab(bot);
// Firestone/Spellstone triggers
if (tab == 2) // Destruction uses Firestone
{
triggers.push_back(new TriggerNode("no firestone", NextAction::array(0, new NextAction("create firestone", 24.0f), nullptr)));
triggers.push_back(new TriggerNode("firestone", NextAction::array(0, new NextAction("firestone", 24.0f), nullptr)));
triggers.push_back(
new TriggerNode("no firestone", NextAction::array(0, new NextAction("create firestone", 24.0f), nullptr)));
triggers.push_back(
new TriggerNode("firestone", NextAction::array(0, new NextAction("firestone", 24.0f), nullptr)));
}
else // Affliction and Demonology use Spellstone
{
triggers.push_back(new TriggerNode("no spellstone", NextAction::array(0, new NextAction("create spellstone", 24.0f), nullptr)));
triggers.push_back(new TriggerNode("spellstone", NextAction::array(0, new NextAction("spellstone", 24.0f), nullptr)));
triggers.push_back(new TriggerNode("no spellstone",
NextAction::array(0, new NextAction("create spellstone", 24.0f), nullptr)));
triggers.push_back(
new TriggerNode("spellstone", NextAction::array(0, new NextAction("spellstone", 24.0f), nullptr)));
}
}
// Non-combat strategy for summoning a Imp
@ -122,7 +124,8 @@ SummonVoidwalkerStrategy::SummonVoidwalkerStrategy(PlayerbotAI* ai) : NonCombatS
void SummonVoidwalkerStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon voidwalker", 29.0f), NULL)));
triggers.push_back(
new TriggerNode("no pet", NextAction::array(0, new NextAction("summon voidwalker", 29.0f), NULL)));
}
// Non-combat strategy for summoning a Succubus
@ -144,7 +147,8 @@ SummonFelhunterStrategy::SummonFelhunterStrategy(PlayerbotAI* ai) : NonCombatStr
void SummonFelhunterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon felhunter", 29.0f), NULL)));
triggers.push_back(
new TriggerNode("no pet", NextAction::array(0, new NextAction("summon felhunter", 29.0f), NULL)));
}
// Non-combat strategy for summoning a Felguard

View File

@ -18,7 +18,6 @@ public:
std::string const getName() override { return "warlock"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
NextAction** getDefaultActions() override;
uint32 GetType() const override { return CombatStrategy::GetType() | STRATEGY_TYPE_RANGED | STRATEGY_TYPE_DPS; }
};
class WarlockBoostStrategy : public Strategy

View File

@ -15,9 +15,6 @@
#include "Playerbots.h"
#include "ServerFacade.h"
#include "Unit.h"
#include "Timer.h"
#include <unordered_map>
#include <mutex>
// Checks if the bot has less than 32 soul shards, and if so, allows casting Drain Soul
bool CastDrainSoulAction::isUseful() { return AI_VALUE2(uint32, "item count", "soul shard") < 32; }
@ -137,29 +134,9 @@ bool UseSoulstoneSelfAction::Execute(Event event)
return UseItem(items[0], ObjectGuid::Empty, nullptr, bot);
}
// Reservation map for soulstone targets (GUID -> reservation expiry in ms)
static std::unordered_map<ObjectGuid, uint32> soulstoneReservations;
static std::mutex soulstoneReservationsMutex;
// Helper to clean up expired reservations
void CleanupSoulstoneReservations()
{
uint32 now = getMSTime();
std::lock_guard<std::mutex> lock(soulstoneReservationsMutex);
for (auto it = soulstoneReservations.begin(); it != soulstoneReservations.end();)
{
if (it->second <= now)
it = soulstoneReservations.erase(it);
else
++it;
}
}
// Use the soulstone item on the bot's master with nc strategy "ss master"
bool UseSoulstoneMasterAction::Execute(Event event)
{
CleanupSoulstoneReservations();
std::vector<Item*> items = AI_VALUE2(std::vector<Item*>, "inventory items", "soulstone");
if (items.empty())
return false;
@ -168,23 +145,6 @@ bool UseSoulstoneMasterAction::Execute(Event event)
if (!master || HasSoulstoneAura(master))
return false;
uint32 now = getMSTime();
{
std::lock_guard<std::mutex> lock(soulstoneReservationsMutex);
if (soulstoneReservations.count(master->GetGUID()) && soulstoneReservations[master->GetGUID()] > now)
return false; // Already being soulstoned
soulstoneReservations[master->GetGUID()] = now + 2500; // Reserve for 2.5 seconds
}
float distance = sServerFacade->GetDistance2d(bot, master);
if (distance >= 30.0f)
return false;
if (!bot->IsWithinLOSInMap(master))
return false;
bot->SetSelection(master->GetGUID());
return UseItem(items[0], ObjectGuid::Empty, nullptr, master);
}
@ -192,83 +152,41 @@ bool UseSoulstoneMasterAction::Execute(Event event)
// Use the soulstone item on a tank in the group with nc strategy "ss tank"
bool UseSoulstoneTankAction::Execute(Event event)
{
CleanupSoulstoneReservations();
std::vector<Item*> items = AI_VALUE2(std::vector<Item*>, "inventory items", "soulstone");
if (items.empty())
return false;
Player* chosenTank = nullptr;
Player* tank = nullptr;
Group* group = bot->GetGroup();
uint32 now = getMSTime();
// First: Try to soulstone the main tank
if (group)
{
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (member && member->IsAlive() && botAI->IsTank(member) && botAI->IsMainTank(member) &&
!HasSoulstoneAura(member))
if (member && member->IsAlive() && botAI->IsTank(member) && !HasSoulstoneAura(member))
{
std::lock_guard<std::mutex> lock(soulstoneReservationsMutex);
if (soulstoneReservations.count(member->GetGUID()) && soulstoneReservations[member->GetGUID()] > now)
continue; // Already being soulstoned
float distance = sServerFacade->GetDistance2d(bot, member);
if (distance < 30.0f && bot->IsWithinLOSInMap(member))
{
chosenTank = member;
soulstoneReservations[chosenTank->GetGUID()] = now + 2500; // Reserve for 2.5 seconds
break;
}
}
}
// If no main tank found, soulstone another tank
if (!chosenTank)
{
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (member && member->IsAlive() && botAI->IsTank(member) && !HasSoulstoneAura(member))
{
std::lock_guard<std::mutex> lock(soulstoneReservationsMutex);
if (soulstoneReservations.count(member->GetGUID()) &&
soulstoneReservations[member->GetGUID()] > now)
continue; // Already being soulstoned
float distance = sServerFacade->GetDistance2d(bot, member);
if (distance < 30.0f && bot->IsWithinLOSInMap(member))
{
chosenTank = member;
soulstoneReservations[chosenTank->GetGUID()] = now + 2500; // Reserve for 2.5 seconds
break;
}
}
tank = member;
break;
}
}
}
if (!chosenTank)
if (!tank)
return false;
bot->SetSelection(chosenTank->GetGUID());
return UseItem(items[0], ObjectGuid::Empty, nullptr, chosenTank);
bot->SetSelection(tank->GetGUID());
return UseItem(items[0], ObjectGuid::Empty, nullptr, tank);
}
// Use the soulstone item on a healer in the group with nc strategy "ss healer"
bool UseSoulstoneHealerAction::Execute(Event event)
{
CleanupSoulstoneReservations();
std::vector<Item*> items = AI_VALUE2(std::vector<Item*>, "inventory items", "soulstone");
if (items.empty())
return false;
Player* healer = nullptr;
Group* group = bot->GetGroup();
uint32 now = getMSTime();
if (group)
{
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
@ -276,20 +194,8 @@ bool UseSoulstoneHealerAction::Execute(Event event)
Player* member = gref->GetSource();
if (member && member->IsAlive() && botAI->IsHeal(member) && !HasSoulstoneAura(member))
{
{
std::lock_guard<std::mutex> lock(soulstoneReservationsMutex);
if (soulstoneReservations.count(member->GetGUID()) &&
soulstoneReservations[member->GetGUID()] > now)
continue; // Already being soulstoned
float distance = sServerFacade->GetDistance2d(bot, member);
if (distance < 30.0f && bot->IsWithinLOSInMap(member))
{
healer = member;
soulstoneReservations[healer->GetGUID()] = now + 2500; // Reserve for 2.5 seconds
break;
}
}
healer = member;
break;
}
}
}

View File

@ -8,6 +8,7 @@
#include "DemonologyWarlockStrategy.h"
#include "DestructionWarlockStrategy.h"
#include "TankWarlockStrategy.h"
#include "DpsWarlockStrategy.h"
#include "GenericTriggers.h"
#include "GenericWarlockNonCombatStrategy.h"
#include "NamedObjectContext.h"
@ -28,16 +29,16 @@ public:
creators["boost"] = &WarlockStrategyFactoryInternal::boost;
creators["cc"] = &WarlockStrategyFactoryInternal::cc;
creators["pet"] = &WarlockStrategyFactoryInternal::pet;
creators["affli"] = &WarlockStrategyFactoryInternal::affliction;
creators["affli aoe"] = &WarlockStrategyFactoryInternal::affliction_aoe;
creators["demo"] = &WarlockStrategyFactoryInternal::demonology;
creators["demo aoe"] = &WarlockStrategyFactoryInternal::demonology_aoe;
creators["destro"] = &WarlockStrategyFactoryInternal::destruction;
creators["destro aoe"] = &WarlockStrategyFactoryInternal::destruction_aoe;
creators["meta melee"] = &WarlockStrategyFactoryInternal::meta_melee_aoe;
creators["dps"] = &WarlockStrategyFactoryInternal::dps;
creators["aoe"] = &WarlockStrategyFactoryInternal::aoe;
creators["curse of elements"] = &WarlockStrategyFactoryInternal::curse_of_elements;
creators["imp"] = &WarlockStrategyFactoryInternal::imp;
creators["voidwalker"] = &WarlockStrategyFactoryInternal::voidwalker;
creators["succubus"] = &WarlockStrategyFactoryInternal::succubus;
creators["felhunter"] = &WarlockStrategyFactoryInternal::felhunter;
creators["felguard"] = &WarlockStrategyFactoryInternal::felguard;
}
private:
@ -46,16 +47,16 @@ private:
static Strategy* pull(PlayerbotAI* botAI) { return new PullStrategy(botAI, "shoot"); }
static Strategy* boost(PlayerbotAI* botAI) { return new WarlockBoostStrategy(botAI); }
static Strategy* cc(PlayerbotAI* botAI) { return new WarlockCcStrategy(botAI); }
static Strategy* affliction(PlayerbotAI* botAI) { return new AfflictionWarlockStrategy(botAI); }
static Strategy* affliction_aoe(PlayerbotAI* botAI) { return new AfflictionWarlockAoeStrategy(botAI); }
static Strategy* demonology(PlayerbotAI* botAI) { return new DemonologyWarlockStrategy(botAI); }
static Strategy* demonology_aoe(PlayerbotAI* botAI) { return new DemonologyWarlockAoeStrategy(botAI); }
static Strategy* destruction(PlayerbotAI* botAI) { return new DestructionWarlockStrategy(botAI); }
static Strategy* destruction_aoe(PlayerbotAI* botAI) { return new DestructionWarlockAoeStrategy(botAI); }
static Strategy* meta_melee_aoe(PlayerbotAI* botAI) { return new MetaMeleeAoeStrategy(botAI); }
static Strategy* dps(PlayerbotAI* botAI) { return new DpsWarlockStrategy(botAI); }
static Strategy* aoe(PlayerbotAI* botAI) { return new DpsAoeWarlockStrategy(botAI); }
static Strategy* curse_of_elements(PlayerbotAI* botAI) { return new WarlockCurseOfTheElementsStrategy(botAI); }
static Strategy* imp(PlayerbotAI* ai) { return new SummonImpStrategy(ai); }
static Strategy* voidwalker(PlayerbotAI* ai) { return new SummonVoidwalkerStrategy(ai); }
static Strategy* succubus(PlayerbotAI* ai) { return new SummonSuccubusStrategy(ai); }
static Strategy* felhunter(PlayerbotAI* ai) { return new SummonFelhunterStrategy(ai); }
static Strategy* felguard(PlayerbotAI* ai) { return new SummonFelguardStrategy(ai); }
};
class WarlockCombatStrategyFactoryInternal : public NamedObjectContext<Strategy>
@ -64,16 +65,10 @@ public:
WarlockCombatStrategyFactoryInternal() : NamedObjectContext<Strategy>(false, true)
{
creators["tank"] = &WarlockCombatStrategyFactoryInternal::tank;
creators["affli"] = &WarlockCombatStrategyFactoryInternal::affliction;
creators["demo"] = &WarlockCombatStrategyFactoryInternal::demonology;
creators["destro"] = &WarlockCombatStrategyFactoryInternal::destruction;
}
private:
static Strategy* tank(PlayerbotAI* botAI) { return new TankWarlockStrategy(botAI); }
static Strategy* affliction(PlayerbotAI* botAI) { return new AfflictionWarlockStrategy(botAI); }
static Strategy* demonology(PlayerbotAI* botAI) { return new DemonologyWarlockStrategy(botAI); }
static Strategy* destruction(PlayerbotAI* botAI) { return new DestructionWarlockStrategy(botAI); }
};
class NonCombatBuffStrategyFactoryInternal : public NamedObjectContext<Strategy>
@ -81,6 +76,11 @@ class NonCombatBuffStrategyFactoryInternal : public NamedObjectContext<Strategy>
public:
NonCombatBuffStrategyFactoryInternal() : NamedObjectContext<Strategy>(false, true)
{
creators["imp"] = &NonCombatBuffStrategyFactoryInternal::imp;
creators["voidwalker"] = &NonCombatBuffStrategyFactoryInternal::voidwalker;
creators["succubus"] = &NonCombatBuffStrategyFactoryInternal::succubus;
creators["felhunter"] = &NonCombatBuffStrategyFactoryInternal::felhunter;
creators["felguard"] = &NonCombatBuffStrategyFactoryInternal::felguard;
creators["ss self"] = &NonCombatBuffStrategyFactoryInternal::soulstone_self;
creators["ss master"] = &NonCombatBuffStrategyFactoryInternal::soulstone_master;
creators["ss tank"] = &NonCombatBuffStrategyFactoryInternal::soulstone_tank;
@ -88,6 +88,11 @@ public:
}
private:
static Strategy* imp(PlayerbotAI* ai) { return new SummonImpStrategy(ai); }
static Strategy* voidwalker(PlayerbotAI* ai) { return new SummonVoidwalkerStrategy(ai); }
static Strategy* succubus(PlayerbotAI* ai) { return new SummonSuccubusStrategy(ai); }
static Strategy* felhunter(PlayerbotAI* ai) { return new SummonFelhunterStrategy(ai); }
static Strategy* felguard(PlayerbotAI* ai) { return new SummonFelguardStrategy(ai); }
static Strategy* soulstone_self(PlayerbotAI* ai) { return new SoulstoneSelfStrategy(ai); }
static Strategy* soulstone_master(PlayerbotAI* ai) { return new SoulstoneMasterStrategy(ai); }
static Strategy* soulstone_tank(PlayerbotAI* ai) { return new SoulstoneTankStrategy(ai); }
@ -132,8 +137,6 @@ public:
creators["metamorphosis"] = &WarlockTriggerFactoryInternal::metamorphosis;
creators["demonic empowerment"] = &WarlockTriggerFactoryInternal::demonic_empowerment;
creators["immolation aura active"] = &WarlockTriggerFactoryInternal::immolation_aura_active;
creators["metamorphosis not active"] = &WarlockTriggerFactoryInternal::metamorphosis_not_active;
creators["meta melee flee check"] = &WarlockTriggerFactoryInternal::meta_melee_flee_check;
}
private:
@ -170,8 +173,6 @@ private:
static Trigger* metamorphosis(PlayerbotAI* ai) { return new MetamorphosisTrigger(ai); }
static Trigger* demonic_empowerment(PlayerbotAI* ai) { return new DemonicEmpowermentTrigger(ai); }
static Trigger* immolation_aura_active(PlayerbotAI* ai) { return new ImmolationAuraActiveTrigger(ai); }
static Trigger* metamorphosis_not_active(PlayerbotAI* ai) { return new MetamorphosisNotActiveTrigger(ai); }
static Trigger* meta_melee_flee_check(PlayerbotAI* ai) { return new MetaMeleeEnemyTooCloseForSpellTrigger(ai); }
};
class WarlockAiObjectContextInternal : public NamedObjectContext<Action>

View File

@ -271,18 +271,4 @@ class MoltenCoreTrigger : public HasAuraTrigger
public:
MoltenCoreTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "molten core") {}
};
class MetamorphosisNotActiveTrigger : public HasNoAuraTrigger
{
public:
MetamorphosisNotActiveTrigger(PlayerbotAI* ai) : HasNoAuraTrigger(ai, "metamorphosis") {}
};
class MetaMeleeEnemyTooCloseForSpellTrigger : public TwoTriggers
{
public:
MetaMeleeEnemyTooCloseForSpellTrigger(PlayerbotAI* ai)
: TwoTriggers(ai, "enemy too close for spell", "metamorphosis not active") {}
};
#endif