diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index c2e558f8..71c3a051 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -596,6 +596,11 @@ AiPlayerbot.RandomBotGroupNearby = 0 # Default: 1 (enabled) AiPlayerbot.AutoDoQuests = 1 +# Random Bots will behave more like real players (exprimental) +# This option will override AutoDoQuests +# Default: 0 (disabled) +AiPlayerbot.EnableNewRpgStrategy = 0 + # Quest items to leave (do not destroy) AiPlayerbot.RandomBotQuestItems = "6948,5175,5176,5177,5178,16309,12382,13704,11000" @@ -717,10 +722,10 @@ AiPlayerbot.RandomBotArenaTeamMinRating = 1000 AiPlayerbot.DeleteRandomBotArenaTeams = 0 # PvP Restricted Zones (bots don't pvp) -AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298" +AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,139" # PvP Restricted Areas (bots don't pvp) -AiPlayerbot.PvpProhibitedAreaIds = "976,35,392" +AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268" # Improve react speed in battleground and arena (may cause lag) AiPlayerbot.FastReactInBG = 1 diff --git a/src/AiFactory.cpp b/src/AiFactory.cpp index d3cd1ef3..8af2e0dd 100644 --- a/src/AiFactory.cpp +++ b/src/AiFactory.cpp @@ -631,7 +631,11 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const // nonCombatEngine->addStrategy("group"); // nonCombatEngine->addStrategy("guild"); - if (sPlayerbotAIConfig->autoDoQuests) + if (sPlayerbotAIConfig->enableNewRpgStrategy) + { + nonCombatEngine->addStrategy("new rpg", false); + } + else if (sPlayerbotAIConfig->autoDoQuests) { // nonCombatEngine->addStrategy("travel"); nonCombatEngine->addStrategy("rpg", false); diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 1acbd662..3c3ec2b4 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -29,6 +29,7 @@ #include "MotionMaster.h" #include "MoveSpline.h" #include "MoveSplineInit.h" +#include "NewRpgStrategy.h" #include "ObjectGuid.h" #include "PerformanceMonitor.h" #include "Player.h" @@ -38,6 +39,7 @@ #include "Playerbots.h" #include "PointMovementGenerator.h" #include "PositionValue.h" +#include "RandomPlayerbotMgr.h" #include "SayAction.h" #include "ScriptMgr.h" #include "ServerFacade.h" @@ -718,7 +720,7 @@ void PlayerbotAI::HandleTeleportAck() // SetNextCheckDelay(urand(2000, 5000)); if (sPlayerbotAIConfig->applyInstanceStrategies) ApplyInstanceStrategies(bot->GetMapId(), true); - Reset(); + Reset(true); } SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown); @@ -767,14 +769,15 @@ void PlayerbotAI::Reset(bool full) ->setTarget(sTravelMgr->nullTravelDestination, sTravelMgr->nullWorldPosition, true); aiObjectContext->GetValue("travel target")->Get()->setStatus(TRAVEL_STATUS_EXPIRED); aiObjectContext->GetValue("travel target")->Get()->setExpireIn(1000); + rpgInfo = NewRpgInfo(); } aiObjectContext->GetValue("ignore rpg target")->Get().clear(); bot->GetMotionMaster()->Clear(); - // bot->CleanupAfterTaxiFlight(); - InterruptSpell(); + InterruptSpell(); + if (full) { for (uint8 i = 0; i < BOT_STATE_MAX; i++) @@ -4111,20 +4114,11 @@ inline bool ZoneHasRealPlayers(Player* bot) { return false; } - - Map::PlayerList const& players = bot->GetMap()->GetPlayers(); - if (players.IsEmpty()) + + for (auto& player : sRandomPlayerbotMgr->GetPlayers()) { - return false; - } - - for (auto const& itr : players) - { - Player* player = itr.GetSource(); - if (!player || !player->IsVisible()) - { + if (player->GetMapId() != bot->GetMapId()) continue; - } if (player->GetZoneId() == bot->GetZoneId()) { @@ -4143,16 +4137,16 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) { // only keep updating till initializing time has completed, // which prevents unneeded expensive GameTime calls. - if (_isBotInitializing) - { - _isBotInitializing = GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * 0.12; + // if (_isBotInitializing) + // { + // _isBotInitializing = GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * 0.12; - // no activity allowed during bot initialization - if (_isBotInitializing) - { - return false; - } - } + // // no activity allowed during bot initialization + // if (_isBotInitializing) + // { + // return false; + // } + // } // General exceptions if (activityType == PACKET_ACTIVITY) diff --git a/src/PlayerbotAI.h b/src/PlayerbotAI.h index 5477685e..7c61d48b 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -21,6 +21,7 @@ #include "PlayerbotTextMgr.h" #include "SpellAuras.h" #include "WorldPacket.h" +#include "NewRpgStrategy.h" class AiObjectContext; class Creature; @@ -432,7 +433,7 @@ public: std::vector GetPlayersInGroup(); const AreaTableEntry* GetCurrentArea(); const AreaTableEntry* GetCurrentZone(); - std::string GetLocalizedAreaName(const AreaTableEntry* entry); + static std::string GetLocalizedAreaName(const AreaTableEntry* entry); bool TellMaster(std::ostringstream& stream, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL); bool TellMaster(std::string const text, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL); @@ -572,6 +573,7 @@ public: std::set GetCurrentIncompleteQuestIds(); void PetFollow(); static float GetItemScoreMultiplier(ItemQualities quality); + NewRpgInfo rpgInfo; private: static void _fillGearScoreData(Player* player, Item* item, std::vector* gearScore, uint32& twoHandScore, @@ -580,7 +582,7 @@ private: void HandleCommands(); void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL); - bool _isBotInitializing = true; + bool _isBotInitializing = false; protected: Player* bot; diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 3443dbe9..3c17af18 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -399,6 +399,7 @@ bool PlayerbotAIConfig::Initialize() worldBuffs.clear(); + LOG_INFO("playerbots", "Loading Worldbuff..."); for (uint32 factionId = 0; factionId < 3; factionId++) { for (uint32 classId = 0; classId < MAX_CLASSES; classId++) @@ -507,7 +508,8 @@ bool PlayerbotAIConfig::Initialize() autoLearnTrainerSpells = sConfigMgr->GetOption("AiPlayerbot.AutoLearnTrainerSpells", true); autoLearnQuestSpells = sConfigMgr->GetOption("AiPlayerbot.AutoLearnQuestSpells", false); autoTeleportForLevel = sConfigMgr->GetOption("AiPlayerbot.AutoTeleportForLevel", false); - autoDoQuests = sConfigMgr->GetOption("AiPlayerbot.AutoDoQuests", false); + autoDoQuests = sConfigMgr->GetOption("AiPlayerbot.AutoDoQuests", true); + enableNewRpgStrategy = sConfigMgr->GetOption("AiPlayerbot.EnableNewRpgStrategy", false); syncLevelWithPlayers = sConfigMgr->GetOption("AiPlayerbot.SyncLevelWithPlayers", false); freeFood = sConfigMgr->GetOption("AiPlayerbot.FreeFood", true); randomBotGroupNearby = sConfigMgr->GetOption("AiPlayerbot.RandomBotGroupNearby", true); @@ -569,7 +571,7 @@ bool PlayerbotAIConfig::IsInRandomQuestItemList(uint32 id) bool PlayerbotAIConfig::IsPvpProhibited(uint32 zoneId, uint32 areaId) { - return IsInPvpProhibitedZone(zoneId) || IsInPvpProhibitedArea(areaId); + return IsInPvpProhibitedZone(zoneId) || IsInPvpProhibitedArea(areaId) || IsInPvpProhibitedZone(areaId); } bool PlayerbotAIConfig::IsInPvpProhibitedZone(uint32 id) diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index becb673a..15a3025c 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -287,6 +287,7 @@ public: bool autoUpgradeEquip; bool autoLearnTrainerSpells; bool autoDoQuests; + bool enableNewRpgStrategy; bool syncLevelWithPlayers; bool freeFood; bool autoLearnQuestSpells; diff --git a/src/RandomPlayerbotFactory.cpp b/src/RandomPlayerbotFactory.cpp index 57b42bda..3241978e 100644 --- a/src/RandomPlayerbotFactory.cpp +++ b/src/RandomPlayerbotFactory.cpp @@ -556,7 +556,7 @@ void RandomPlayerbotFactory::CreateRandomBots() totalRandomBotChars += AccountMgr::GetCharactersCount(accountId); } - LOG_INFO("server.loading", "{} random bot accounts with {} characters available", + LOG_INFO("server.loading", ">> {} random bot accounts with {} characters available", sPlayerbotAIConfig->randomBotAccounts.size(), totalRandomBotChars); } diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 0d583332..18b6ab3a 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -18,6 +19,8 @@ #include "BattlegroundMgr.h" #include "CellImpl.h" #include "ChannelMgr.h" +#include "DBCStores.h" +#include "DBCStructure.h" #include "DatabaseEnv.h" #include "Define.h" #include "FleeManager.h" @@ -28,15 +31,19 @@ #include "GuildTaskMgr.h" #include "LFGMgr.h" #include "MapMgr.h" +#include "NewRpgStrategy.h" #include "PerformanceMonitor.h" +#include "Player.h" #include "PlayerbotAI.h" #include "PlayerbotAIConfig.h" #include "PlayerbotCommandServer.h" #include "PlayerbotFactory.h" #include "Playerbots.h" +#include "Position.h" #include "Random.h" #include "ServerFacade.h" #include "SharedDefines.h" +#include "TravelMgr.h" #include "Unit.h" #include "UpdateTime.h" #include "World.h" @@ -363,6 +370,17 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) sRandomPlayerbotMgr->CheckLfgQueue(); } + if (time(nullptr) > (printStatsTimer + 300)) + { + if (!printStatsTimer) + { + printStatsTimer = time(nullptr); + } + else + { + activatePrintStatsThread(); + } + } uint32 updateBots = sPlayerbotAIConfig->randomBotsPerInterval * onlineBotFocus / 100; uint32 maxNewBots = onlineBotCount < maxAllowedBotCount ? maxAllowedBotCount - onlineBotCount : 0; uint32 loginBots = std::min(sPlayerbotAIConfig->randomBotsPerInterval - updateBots, maxNewBots); @@ -417,25 +435,25 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) } } -//void RandomPlayerbotMgr::ScaleBotActivity() +// void RandomPlayerbotMgr::ScaleBotActivity() //{ -// float activityPercentage = getActivityPercentage(); +// float activityPercentage = getActivityPercentage(); // -// // if (activityPercentage >= 100.0f || activityPercentage <= 0.0f) pid.reset(); //Stop integer buildup during -// // max/min activity +// // if (activityPercentage >= 100.0f || activityPercentage <= 0.0f) pid.reset(); //Stop integer buildup during +// // max/min activity // -// // % increase/decrease wanted diff , avg diff -// float activityPercentageMod = pid.calculate( -// sRandomPlayerbotMgr->GetPlayers().empty() ? sPlayerbotAIConfig->diffEmpty : sPlayerbotAIConfig->diffWithPlayer, -// sWorldUpdateTime.GetAverageUpdateTime()); +// // % increase/decrease wanted diff , avg diff +// float activityPercentageMod = pid.calculate( +// sRandomPlayerbotMgr->GetPlayers().empty() ? sPlayerbotAIConfig->diffEmpty : +// sPlayerbotAIConfig->diffWithPlayer, sWorldUpdateTime.GetAverageUpdateTime()); // -// activityPercentage = activityPercentageMod + 50; +// activityPercentage = activityPercentageMod + 50; // -// // Cap the percentage between 0 and 100. -// activityPercentage = std::max(0.0f, std::min(100.0f, activityPercentage)); +// // Cap the percentage between 0 and 100. +// activityPercentage = std::max(0.0f, std::min(100.0f, activityPercentage)); // -// setActivityPercentage(activityPercentage); -//} +// setActivityPercentage(activityPercentage); +// } uint32 RandomPlayerbotMgr::AddRandomBots() { @@ -521,7 +539,9 @@ uint32 RandomPlayerbotMgr::AddRandomBots() } if (maxAllowedBotCount) - LOG_ERROR("playerbots", "Not enough random bot accounts available. Need {} more!!", + LOG_ERROR("playerbots", + "Not enough random bot accounts available. Need {} more, try to increase RandomBotAccountCount " + "in your conf file", ceil(maxAllowedBotCount / 10)); } @@ -597,7 +617,7 @@ void RandomPlayerbotMgr::CheckBgQueue() BgCheckTimer = time(nullptr); - LOG_INFO("playerbots", "Checking BG Queue..."); + LOG_DEBUG("playerbots", "Checking BG Queue..."); BattlegroundData.clear(); @@ -915,7 +935,7 @@ void RandomPlayerbotMgr::LogBattlegroundInfo() bgInfo.bgHordePlayerCount + bgInfo.bgHordeBotCount, bgInfo.bgInstanceCount); } } - LOG_INFO("playerbots", "BG Queue check finished"); + LOG_DEBUG("playerbots", "BG Queue check finished"); } void RandomPlayerbotMgr::CheckLfgQueue() @@ -923,7 +943,7 @@ void RandomPlayerbotMgr::CheckLfgQueue() if (!LfgCheckTimer || time(nullptr) > (LfgCheckTimer + 30)) LfgCheckTimer = time(nullptr); - LOG_INFO("playerbots", "Checking LFG Queue..."); + LOG_DEBUG("playerbots", "Checking LFG Queue..."); // Clear LFG list LfgDungeons[TEAM_ALLIANCE].clear(); @@ -953,7 +973,7 @@ void RandomPlayerbotMgr::CheckLfgQueue() } } - LOG_INFO("playerbots", "LFG Queue check finished"); + LOG_DEBUG("playerbots", "LFG Queue check finished"); } void RandomPlayerbotMgr::CheckPlayers() @@ -983,10 +1003,7 @@ void RandomPlayerbotMgr::CheckPlayers() LOG_INFO("playerbots", "Max player level is {}, max bot level set to {}", playersLevel - 3, playersLevel); } -void RandomPlayerbotMgr::ScheduleRandomize(uint32 bot, uint32 time) -{ - SetEventValue(bot, "randomize", 1, time); -} +void RandomPlayerbotMgr::ScheduleRandomize(uint32 bot, uint32 time) { SetEventValue(bot, "randomize", 1, time); } void RandomPlayerbotMgr::ScheduleTeleport(uint32 bot, uint32 time) { @@ -1102,9 +1119,7 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot) if (update) ProcessBot(player); - randomTime = urand( - sPlayerbotAIConfig->minRandomBotReviveTime, - sPlayerbotAIConfig->maxRandomBotReviveTime); + randomTime = urand(sPlayerbotAIConfig->minRandomBotReviveTime, sPlayerbotAIConfig->maxRandomBotReviveTime); SetEventValue(bot, "update", 1, randomTime); return true; @@ -1117,9 +1132,8 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot) player->GetLevel(), player->GetName().c_str()); LogoutPlayerBot(botGUID); currentBots.remove(bot); - SetEventValue(bot, "logout", 1, urand( - sPlayerbotAIConfig->minRandomBotInWorldTime, - sPlayerbotAIConfig->maxRandomBotInWorldTime)); + SetEventValue(bot, "logout", 1, + urand(sPlayerbotAIConfig->minRandomBotInWorldTime, sPlayerbotAIConfig->maxRandomBotInWorldTime)); return true; } @@ -1141,11 +1155,10 @@ bool RandomPlayerbotMgr::ProcessBot(Player* player) { if (!GetEventValue(bot, "dead")) { - uint32 randomTime = urand( - sPlayerbotAIConfig->minRandomBotReviveTime, - sPlayerbotAIConfig->maxRandomBotReviveTime); - LOG_INFO("playerbots", "Mark bot {} as dead, will be revived in {}s.", - player->GetName().c_str(), randomTime); + uint32 randomTime = + urand(sPlayerbotAIConfig->minRandomBotReviveTime, sPlayerbotAIConfig->maxRandomBotReviveTime); + LOG_DEBUG("playerbots", "Mark bot {} as dead, will be revived in {}s.", player->GetName().c_str(), + randomTime); SetEventValue(bot, "dead", 1, sPlayerbotAIConfig->maxRandomBotInWorldTime); SetEventValue(bot, "revive", 1, randomTime); return false; @@ -1168,11 +1181,10 @@ bool RandomPlayerbotMgr::ProcessBot(Player* player) LOG_INFO("playerbots", "Bot {} remove from group since leader is random bot.", player->GetName().c_str()); } - // only randomize and teleport idle bots bool idleBot = false; PlayerbotAI* botAI = GET_PLAYERBOT_AI(player); - if (botAI) + if (botAI) { if (TravelTarget* target = botAI->GetAiObjectContext()->GetValue("travel target")->Get()) { @@ -1181,10 +1193,10 @@ bool RandomPlayerbotMgr::ProcessBot(Player* player) idleBot = true; } } - else + else { idleBot = true; - } + } } if (idleBot) { @@ -1192,7 +1204,7 @@ bool RandomPlayerbotMgr::ProcessBot(Player* player) uint32 randomize = GetEventValue(bot, "randomize"); if (!randomize) { - // bool randomiser = true; + // bool randomiser = true; // if (player->GetGuildId()) // { // if (Guild* guild = sGuildMgr->GetGuildById(player->GetGuildId())) @@ -1214,11 +1226,10 @@ bool RandomPlayerbotMgr::ProcessBot(Player* player) // if (randomiser) // { Randomize(player); - LOG_INFO("playerbots", "Bot #{} {}:{} <{}>: randomized", - bot, player->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", player->GetLevel(), player->GetName()); - uint32 randomTime = urand( - sPlayerbotAIConfig->minRandomBotRandomizeTime, - sPlayerbotAIConfig->maxRandomBotRandomizeTime); + LOG_DEBUG("playerbots", "Bot #{} {}:{} <{}>: randomized", bot, + player->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", player->GetLevel(), player->GetName()); + uint32 randomTime = + urand(sPlayerbotAIConfig->minRandomBotRandomizeTime, sPlayerbotAIConfig->maxRandomBotRandomizeTime); ScheduleRandomize(bot, randomTime); return true; } @@ -1234,12 +1245,11 @@ bool RandomPlayerbotMgr::ProcessBot(Player* player) uint32 teleport = GetEventValue(bot, "teleport"); if (!teleport) { - LOG_INFO("playerbots", "Bot #{} <{}>: teleport for level and refresh", bot, player->GetName()); + LOG_DEBUG("playerbots", "Bot #{} <{}>: teleport for level and refresh", bot, player->GetName()); Refresh(player); RandomTeleportForLevel(player); - uint32 time = urand( - sPlayerbotAIConfig->minRandomBotTeleportInterval, - sPlayerbotAIConfig->maxRandomBotTeleportInterval); + uint32 time = urand(sPlayerbotAIConfig->minRandomBotTeleportInterval, + sPlayerbotAIConfig->maxRandomBotTeleportInterval); ScheduleTeleport(bot, time); return true; } @@ -1264,7 +1274,7 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& { // ignore when alrdy teleported or not in the world yet. if (bot->IsBeingTeleported() || !bot->IsInWorld()) - return; + return; // ignore when in queue for battle grounds. if (bot->InBattlegroundQueue()) @@ -1280,11 +1290,10 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); if (botAI) - { + { // ignore when in when taxi with boat/zeppelin and has players nearby - if (bot->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && - bot->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING) && - botAI->HasPlayerNearby()) + if (bot->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && bot->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING) && + botAI->HasPlayerNearby()) return; } @@ -1373,17 +1382,19 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& { continue; } - if (bot->GetLevel() <= 18 && (loc.GetMapId() != pInfo->mapId || dis > 10000.0f)) + if (bot->GetLevel() <= 16 && (loc.GetMapId() != pInfo->mapId || dis > 10000.0f)) { continue; } const LocaleConstant& locale = sWorld->GetDefaultDbcLocale(); - LOG_INFO("playerbots", - "Random teleporting bot {} (level {}) to Map: {} ({}) Zone: {} ({}) Area: {} ({}) {},{},{} ({}/{} " - "locations)", - bot->GetName().c_str(), bot->GetLevel(), map->GetId(), map->GetMapName(), zone->ID, - zone->area_name[locale], area->ID, area->area_name[locale], x, y, z, i + 1, tlocs.size()); + LOG_DEBUG("playerbots", + "Random teleporting bot {} (level {}) to Map: {} ({}) Zone: {} ({}) Area: {} ({}) ZoneLevel: {} " + "AreaLevel: {} {},{},{} ({}/{} " + "locations)", + bot->GetName().c_str(), bot->GetLevel(), map->GetId(), map->GetMapName(), zone->ID, + zone->area_name[locale], area->ID, area->area_name[locale], zone->area_level, area->area_level, x, y, + z, i + 1, tlocs.size()); if (hearth) { @@ -1422,32 +1433,35 @@ void RandomPlayerbotMgr::PrepareTeleportCache() "position_x, " "position_y, " "position_z, " - "t.minlevel " + "t.minlevel, " + "t.maxlevel " "FROM " "(SELECT " "map, " - "MIN( c.guid ) guid, " - "t.entry " + "MIN( c.guid ) guid " "FROM " "creature c " "INNER JOIN creature_template t ON c.id1 = t.entry " "WHERE " "t.npcflag = 0 " "AND t.lootid != 0 " - "AND t.unit_flags != 768 " "AND t.maxlevel - t.minlevel < 3 " "AND map IN ({}) " - "AND c.id1 != 32820 " + "AND t.entry not in (32820, 24196, 30627, 30617) " "AND c.spawntimesecs < 1000 " - "AND t.faction != 188 " + "AND t.faction not in (11, 71, 79, 85, 188, 1575) " + "AND (t.unit_flags & 256) = 0 " + "AND (t.unit_flags & 4096) = 0 " + "AND t.rank = 0 " + // "AND (t.flags_extra & 32768) = 0 " "GROUP BY " "map, " - "ROUND( position_x / 500 ), " - "ROUND( position_y / 500 ), " - "ROUND( position_z / 50), " - "t.entry " + "ROUND(position_x / 50), " + "ROUND(position_y / 50), " + "ROUND(position_z / 50) " "HAVING " - "count(*) > 7) AS g " + "count(*) >= 2) " + "AS g " "INNER JOIN creature c ON g.guid = c.guid " "INNER JOIN creature_template t on c.id1 = t.entry " "ORDER BY " @@ -1463,7 +1477,9 @@ void RandomPlayerbotMgr::PrepareTeleportCache() float x = fields[1].Get(); float y = fields[2].Get(); float z = fields[3].Get(); - uint32 level = fields[4].Get(); + uint32 min_level = fields[4].Get(); + uint32 max_level = fields[5].Get(); + uint32 level = (min_level + max_level + 1) / 2; WorldLocation loc(mapId, x, y, z, 0); collected_locs++; for (int32 l = (int32)level - (int32)sPlayerbotAIConfig->randomBotTeleHigherLevel; @@ -1477,8 +1493,117 @@ void RandomPlayerbotMgr::PrepareTeleportCache() } } while (results->NextRow()); } - LOG_INFO("playerbots", "{} locations for level collected.", collected_locs); + LOG_INFO("playerbots", ">> {} locations for level collected.", collected_locs); + LOG_INFO("playerbots", "Preparing innkeepers locations for level collected..."); + if (sPlayerbotAIConfig->enableNewRpgStrategy) + { + results = WorldDatabase.Query( + "SELECT " + "map, " + "position_x, " + "position_y, " + "position_z, " + "orientation, " + "t.faction, " + "t.entry " + "FROM " + "creature c " + "INNER JOIN creature_template t on c.id1 = t.entry " + "WHERE " + "t.npcflag & 73728 " + "AND map IN ({}) " + "ORDER BY " + "t.minlevel;", + sPlayerbotAIConfig->randomBotMapsAsString.c_str()); + collected_locs = 0; + if (results) + { + do + { + Field* fields = results->Fetch(); + uint16 mapId = fields[0].Get(); + float x = fields[1].Get(); + float y = fields[2].Get(); + float z = fields[3].Get(); + float orient = fields[4].Get(); + uint32 faction = fields[5].Get(); + uint32 c_entry = fields[6].Get(); + 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); + collected_locs++; + Map* map = sMapMgr->FindMap(loc.GetMapId(), 0); + if (!map) + continue; + const AreaTableEntry* area = sAreaTableStore.LookupEntry(map->GetAreaId(1, x, y, z)); + uint32 zoneId = area->zone ? area->zone : area->ID; + uint32 level = area->area_level; + for (int i = 5; i <= maxLevel; i++) + { + std::vector& locs = locsPerLevelCache[i]; + int counter = 0; + WorldLocation levelLoc; + for (auto& checkLoc : locs) + { + if (loc.GetMapId() != checkLoc.GetMapId()) + continue; + + if (loc.GetExactDist(checkLoc) > 1500.0f) + continue; + + if (zoneId != + map->GetZoneId(1, checkLoc.GetPositionX(), checkLoc.GetPositionY(), checkLoc.GetPositionZ())) + continue; + + counter++; + levelLoc = checkLoc; + if (counter >= 15) + break; + } + + if (counter < 15) + continue; + + if (!(entry->hostileMask & 4)) + { + hordeStarterPerLevelCache[i].push_back(loc); + } + if (!(entry->hostileMask & 2)) + { + allianceStarterPerLevelCache[i].push_back(loc); + } + LOG_DEBUG("playerbots", "Area: {} Level: {} creature_entry: {} add to: {} {}({},{},{},{})", area->ID, + level, c_entry, i, counter, levelLoc.GetPositionX(), levelLoc.GetPositionY(), + levelLoc.GetPositionZ(), levelLoc.GetMapId()); + } + } while (results->NextRow()); + } + // add all initial position + for (uint32 i = 1; i < MAX_RACES; i++) + { + for (uint32 j = 1; j < MAX_CLASSES; j++) + { + PlayerInfo const* info = sObjectMgr->GetPlayerInfo(i, j); + + if (!info) + continue; + + WorldPosition pos(info->mapId, info->positionX, info->positionY, info->positionZ, info->orientation); + + for (int32 l = 1; l <= 5; l++) + { + if ((1 << (i - 1)) & RACEMASK_ALLIANCE) + allianceStarterPerLevelCache[(uint8)l].push_back(pos); + else + hordeStarterPerLevelCache[(uint8)l].push_back(pos); + } + break; + } + } + LOG_INFO("playerbots", ">> {} innkeepers locations for level collected.", collected_locs); + } + results = WorldDatabase.Query( "SELECT " "map, " @@ -1500,6 +1625,7 @@ void RandomPlayerbotMgr::PrepareTeleportCache() "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 " @@ -1541,15 +1667,15 @@ void RandomPlayerbotMgr::PrepareTeleportCache() } } while (results->NextRow()); } - LOG_INFO("playerbots", "{} banker locations for level collected.", collected_locs); + LOG_INFO("playerbots", ">> {} banker locations for level collected.", collected_locs); } void RandomPlayerbotMgr::PrepareAddclassCache() { int32 maxAccountId = sPlayerbotAIConfig->randomBotAccounts.back(); - int32 minIdx = - sPlayerbotAIConfig->randomBotAccounts.size() - 1 >= sPlayerbotAIConfig->addClassAccountPoolSize - ? sPlayerbotAIConfig->randomBotAccounts.size() - sPlayerbotAIConfig->addClassAccountPoolSize : 0; + int32 minIdx = sPlayerbotAIConfig->randomBotAccounts.size() - 1 >= sPlayerbotAIConfig->addClassAccountPoolSize + ? sPlayerbotAIConfig->randomBotAccounts.size() - sPlayerbotAIConfig->addClassAccountPoolSize + : 0; int32 minAccountId = sPlayerbotAIConfig->randomBotAccounts[minIdx]; if (minAccountId < 0) { @@ -1579,7 +1705,7 @@ void RandomPlayerbotMgr::PrepareAddclassCache() } while (results->NextRow()); } } - LOG_INFO("playerbots", "{} characters collected for addclass command.", collected); + LOG_INFO("playerbots", ">> {} characters collected for addclass command.", collected); } void RandomPlayerbotMgr::RandomTeleportForLevel(Player* bot) @@ -1589,15 +1715,20 @@ void RandomPlayerbotMgr::RandomTeleportForLevel(Player* bot) uint32 level = bot->GetLevel(); uint8 race = bot->getRace(); + std::vector* locs = nullptr; + if (sPlayerbotAIConfig->enableNewRpgStrategy) + locs = IsAlliance(race) ? &allianceStarterPerLevelCache[level] : &hordeStarterPerLevelCache[level]; + else + locs = &locsPerLevelCache[level]; LOG_DEBUG("playerbots", "Random teleporting bot {} for level {} ({} locations available)", bot->GetName().c_str(), - bot->GetLevel(), locsPerLevelCache[level].size()); - if (level > 10 && urand(0, 100) < sPlayerbotAIConfig->probTeleToBankers * 100) + bot->GetLevel(), locs->size()); + if (level >= 10 && urand(0, 100) < sPlayerbotAIConfig->probTeleToBankers * 100) { RandomTeleport(bot, bankerLocsPerLevelCache[level], true); } else { - RandomTeleport(bot, locsPerLevelCache[level]); + RandomTeleport(bot, *locs); } } @@ -1608,10 +1739,15 @@ void RandomPlayerbotMgr::RandomTeleportGrindForLevel(Player* bot) uint32 level = bot->GetLevel(); uint8 race = bot->getRace(); + std::vector* locs = nullptr; + if (sPlayerbotAIConfig->enableNewRpgStrategy) + locs = IsAlliance(race) ? &allianceStarterPerLevelCache[level] : &hordeStarterPerLevelCache[level]; + else + locs = &locsPerLevelCache[level]; LOG_DEBUG("playerbots", "Random teleporting bot {} for level {} ({} locations available)", bot->GetName().c_str(), - bot->GetLevel(), locsPerLevelCache[level].size()); + bot->GetLevel(), locs->size()); - RandomTeleport(bot, locsPerLevelCache[level]); + RandomTeleport(bot, *locs); } void RandomPlayerbotMgr::RandomTeleport(Player* bot) @@ -2261,7 +2397,7 @@ void RandomPlayerbotMgr::OnBotLoginInternal(Player* const bot) { LOG_INFO("playerbots", "{}/{} Bot {} logged in", playerBots.size(), sRandomPlayerbotMgr->GetMaxAllowedBotCount(), bot->GetName().c_str()); - + if (sPlayerbotAIConfig->randomBotFixedLevel) { bot->SetPlayerFlag(PLAYER_FLAGS_NO_XP_GAIN); @@ -2379,7 +2515,8 @@ Player* RandomPlayerbotMgr::GetRandomPlayer() void RandomPlayerbotMgr::PrintStats() { - LOG_INFO("playerbots", "{} Random Bots online", playerBots.size()); + printStatsTimer = time(nullptr); + LOG_INFO("playerbots", "Random Bots Stats: {} online", playerBots.size()); std::map alliance, horde; for (uint32 i = 0; i < 10; ++i) @@ -2421,13 +2558,17 @@ void RandomPlayerbotMgr::PrintStats() uint32 engine_dead = 0; uint32 stateCount[MAX_TRAVEL_STATE + 1] = {0}; std::vector> questCount; + std::unordered_map rpgStatusCount; + std::unordered_map zoneCount; + uint8 maxBotLevel = 0; for (PlayerBotMap::iterator i = playerBots.begin(); i != playerBots.end(); ++i) { Player* bot = i->second; if (IsAlliance(bot->getRace())) - ++alliance[bot->GetLevel() / 10]; + ++alliance[bot->GetLevel()]; else - ++horde[bot->GetLevel() / 10]; + ++horde[bot->GetLevel()]; + maxBotLevel = std::max(maxBotLevel, bot->GetLevel()); ++perRace[bot->getRace()]; ++perClass[bot->getClass()]; @@ -2481,7 +2622,7 @@ void RandomPlayerbotMgr::PrintStats() ++engine_combat; else ++engine_dead; - + if (botAI->IsHeal(bot, true)) ++heal; else if (botAI->IsTank(bot, true)) @@ -2489,6 +2630,11 @@ void RandomPlayerbotMgr::PrintStats() else ++dps; + zoneCount[bot->GetZoneId()]++; + + if (sPlayerbotAIConfig->enableNewRpgStrategy) + rpgStatusCount[botAI->rpgInfo.status]++; + if (TravelTarget* target = botAI->GetAiObjectContext()->GetValue("travel target")->Get()) { TravelState state = target->getTravelState(); @@ -2518,18 +2664,22 @@ void RandomPlayerbotMgr::PrintStats() } LOG_INFO("playerbots", "Bots level:"); - uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); - for (uint8 i = 0; i < 10; ++i) + // uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); + uint32 currentAlliance = 0, currentHorde = 0; + uint32 step = std::max(1, (maxBotLevel + 4) / 8); + uint32 from = 1; + for (uint8 i = 1; i <= maxBotLevel; ++i) { - if (!alliance[i] && !horde[i]) - continue; + currentAlliance += alliance[i]; + currentHorde += horde[i]; - uint32 from = i * 10; - uint32 to = std::min(from + 9, maxLevel); - if (!from) - from = 1; - - LOG_INFO("playerbots", " {}..{}: {} alliance, {} horde", from, to, alliance[i], horde[i]); + if (((i + 1) % step == 0) || i == maxBotLevel) + { + LOG_INFO("playerbots", " {}..{}: {} alliance, {} horde", from, i, currentAlliance, currentHorde); + currentAlliance = 0; + currentHorde = 0; + from = i + 1; + } } LOG_INFO("playerbots", "Bots race:"); @@ -2566,20 +2716,39 @@ 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) + // { + // const AreaTableEntry* entry = sAreaTableStore.LookupEntry(zond_id); + // std::string name = PlayerbotAI::GetLocalizedAreaName(entry); + // LOG_INFO("playerbots", " {}: {}", name, counter); + // } + + if (sPlayerbotAIConfig->enableNewRpgStrategy) + { + LOG_INFO("playerbots", "Bots rpg status:", dead); + LOG_INFO("playerbots", " IDLE: {}", rpgStatusCount[NewRpgStatus::IDLE]); + LOG_INFO("playerbots", " REST: {}", rpgStatusCount[NewRpgStatus::REST]); + LOG_INFO("playerbots", " GO_GRIND: {}", rpgStatusCount[NewRpgStatus::GO_GRIND]); + LOG_INFO("playerbots", " GO_INNKEEPER: {}", rpgStatusCount[NewRpgStatus::GO_INNKEEPER]); + LOG_INFO("playerbots", " NEAR_RANDOM: {}", rpgStatusCount[NewRpgStatus::NEAR_RANDOM]); + LOG_INFO("playerbots", " NEAR_NPC: {}", rpgStatusCount[NewRpgStatus::NEAR_NPC]); + } LOG_INFO("playerbots", "Bots engine:", dead); LOG_INFO("playerbots", " Non-combat: {}", engine_noncombat); LOG_INFO("playerbots", " Combat: {}", engine_combat); LOG_INFO("playerbots", " Dead: {}", engine_dead); - LOG_INFO("playerbots", "Bots questing:"); - LOG_INFO("playerbots", " Picking quests: {}", - stateCount[TRAVEL_STATE_TRAVEL_PICK_UP_QUEST] + stateCount[TRAVEL_STATE_WORK_PICK_UP_QUEST]); - LOG_INFO("playerbots", " Doing quests: {}", - stateCount[TRAVEL_STATE_TRAVEL_DO_QUEST] + stateCount[TRAVEL_STATE_WORK_DO_QUEST]); - LOG_INFO("playerbots", " Completing quests: {}", - stateCount[TRAVEL_STATE_TRAVEL_HAND_IN_QUEST] + stateCount[TRAVEL_STATE_WORK_HAND_IN_QUEST]); - LOG_INFO("playerbots", " Idling: {}", stateCount[TRAVEL_STATE_IDLE]); + // LOG_INFO("playerbots", "Bots questing:"); + // LOG_INFO("playerbots", " Picking quests: {}", + // stateCount[TRAVEL_STATE_TRAVEL_PICK_UP_QUEST] + stateCount[TRAVEL_STATE_WORK_PICK_UP_QUEST]); + // LOG_INFO("playerbots", " Doing quests: {}", + // stateCount[TRAVEL_STATE_TRAVEL_DO_QUEST] + stateCount[TRAVEL_STATE_WORK_DO_QUEST]); + // LOG_INFO("playerbots", " Completing quests: {}", + // stateCount[TRAVEL_STATE_TRAVEL_HAND_IN_QUEST] + stateCount[TRAVEL_STATE_WORK_HAND_IN_QUEST]); + // LOG_INFO("playerbots", " Idling: {}", stateCount[TRAVEL_STATE_IDLE]); /*sort(questCount.begin(), questCount.end(), [](std::pair i, std::pair j) {return i.second > j.second; }); diff --git a/src/RandomPlayerbotMgr.h b/src/RandomPlayerbotMgr.h index f7ebf9aa..8d8b9f56 100644 --- a/src/RandomPlayerbotMgr.h +++ b/src/RandomPlayerbotMgr.h @@ -171,6 +171,10 @@ public: void PrepareAddclassCache(); std::map> addclassCache; + std::map> locsPerLevelCache; + std::map> allianceStarterPerLevelCache; + std::map> hordeStarterPerLevelCache; + std::map> bankerLocsPerLevelCache; protected: void OnBotLoginInternal(Player* const bot) override; @@ -188,6 +192,7 @@ private: time_t BgCheckTimer; time_t LfgCheckTimer; time_t PlayersCheckTimer; + time_t printStatsTimer; uint32 AddRandomBots(); bool ProcessBot(uint32 bot); void ScheduleRandomize(uint32 bot, uint32 time); @@ -199,8 +204,7 @@ private: std::vector players; uint32 processTicks; - std::map> locsPerLevelCache; - std::map> bankerLocsPerLevelCache; + // std::map> rpgLocsCache; std::map>> rpgLocsCacheLevel; diff --git a/src/factory/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp index 076500f6..89de2570 100644 --- a/src/factory/PlayerbotFactory.cpp +++ b/src/factory/PlayerbotFactory.cpp @@ -424,7 +424,7 @@ void PlayerbotFactory::Randomize(bool incremental) bot->SetHealth(bot->GetMaxHealth()); bot->SetPower(POWER_MANA, bot->GetMaxPower(POWER_MANA)); bot->SaveToDB(false, false); - LOG_INFO("playerbots", "Initialization Done."); + // LOG_INFO("playerbots", "Initialization Done."); if (pmo) pmo->finish(); } diff --git a/src/strategy/StrategyContext.h b/src/strategy/StrategyContext.h index da3d8e42..dad46bd8 100644 --- a/src/strategy/StrategyContext.h +++ b/src/strategy/StrategyContext.h @@ -31,6 +31,7 @@ #include "MeleeCombatStrategy.h" #include "MoveFromGroupStrategy.h" #include "NamedObjectContext.h" +#include "NewRpgStrategy.h" #include "NonCombatStrategy.h" #include "PassiveStrategy.h" #include "PullStrategy.h" @@ -82,6 +83,7 @@ public: creators["reveal"] = &StrategyContext::reveal; creators["collision"] = &StrategyContext::collision; creators["rpg"] = &StrategyContext::rpg; + creators["new rpg"] = &StrategyContext::new_rpg; creators["travel"] = &StrategyContext::travel; creators["explore"] = &StrategyContext::explore; creators["map"] = &StrategyContext::map; @@ -152,6 +154,7 @@ private: static Strategy* reveal(PlayerbotAI* botAI) { return new RevealStrategy(botAI); } static Strategy* collision(PlayerbotAI* botAI) { return new CollisionStrategy(botAI); } static Strategy* rpg(PlayerbotAI* botAI) { return new RpgStrategy(botAI); } + static Strategy* new_rpg(PlayerbotAI* botAI) { return new NewRpgStrategy(botAI); } static Strategy* travel(PlayerbotAI* botAI) { return new TravelStrategy(botAI); } static Strategy* explore(PlayerbotAI* botAI) { return new ExploreStrategy(botAI); } static Strategy* map(PlayerbotAI* botAI) { return new MapStrategy(botAI); } diff --git a/src/strategy/actions/ActionContext.h b/src/strategy/actions/ActionContext.h index c13f7162..0ffe08eb 100644 --- a/src/strategy/actions/ActionContext.h +++ b/src/strategy/actions/ActionContext.h @@ -62,6 +62,7 @@ #include "VehicleActions.h" #include "WorldBuffAction.h" #include "XpGainAction.h" +#include "NewRpgAction.h" class PlayerbotAI; @@ -240,6 +241,12 @@ public: creators["toggle pet spell"] = &ActionContext::toggle_pet_spell; creators["pet attack"] = &ActionContext::pet_attack; + + creators["new rpg status update"] = &ActionContext::new_rpg_status_update; + creators["new rpg go grind"] = &ActionContext::new_rpg_go_grind; + creators["new rpg go innkeeper"] = &ActionContext::new_rpg_go_innkeeper; + creators["new rpg move random"] = &ActionContext::new_rpg_move_random; + creators["new rpg move npc"] = &ActionContext::new_rpg_move_npc; } private: @@ -415,6 +422,12 @@ private: static Action* toggle_pet_spell(PlayerbotAI* ai) { return new TogglePetSpellAutoCastAction(ai); } static Action* pet_attack(PlayerbotAI* ai) { return new PetAttackAction(ai); } + + static Action* new_rpg_status_update(PlayerbotAI* ai) { return new NewRpgStatusUpdateAction(ai); } + static Action* new_rpg_go_grind(PlayerbotAI* ai) { return new NewRpgGoGrindAction(ai); } + static Action* new_rpg_go_innkeeper(PlayerbotAI* ai) { return new NewRpgGoInnKeeperAction(ai); } + static Action* new_rpg_move_random(PlayerbotAI* ai) { return new NewRpgMoveRandomAction(ai); } + static Action* new_rpg_move_npc(PlayerbotAI* ai) { return new NewRpgMoveNpcAction(ai); } }; #endif diff --git a/src/strategy/actions/ChatActionContext.h b/src/strategy/actions/ChatActionContext.h index 254dc919..f4cd9cba 100644 --- a/src/strategy/actions/ChatActionContext.h +++ b/src/strategy/actions/ChatActionContext.h @@ -38,6 +38,7 @@ #include "LootStrategyAction.h" #include "MailAction.h" #include "NamedObjectContext.h" +#include "NewRpgAction.h" #include "PassLeadershipToMasterAction.h" #include "PositionAction.h" #include "QueryItemUsageAction.h" @@ -88,6 +89,7 @@ public: creators["reputation"] = &ChatActionContext::reputation; creators["log"] = &ChatActionContext::log; creators["los"] = &ChatActionContext::los; + creators["rpg status"] = &ChatActionContext::rpg_status; creators["aura"] = &ChatActionContext::aura; creators["drop"] = &ChatActionContext::drop; creators["clean quest log"] = &ChatActionContext::clean_quest_log; @@ -258,6 +260,7 @@ private: static Action* reputation(PlayerbotAI* botAI) { return new TellReputationAction(botAI); } static Action* log(PlayerbotAI* botAI) { return new LogLevelAction(botAI); } static Action* los(PlayerbotAI* botAI) { return new TellLosAction(botAI); } + static Action* rpg_status(PlayerbotAI* botAI) { return new TellRpgStatusAction(botAI); } static Action* aura(PlayerbotAI* ai) { return new TellAuraAction(ai); } static Action* ll(PlayerbotAI* botAI) { return new LootStrategyAction(botAI); } static Action* ss(PlayerbotAI* botAI) { return new SkipSpellsListAction(botAI); } diff --git a/src/strategy/actions/MovementActions.cpp b/src/strategy/actions/MovementActions.cpp index fa92d877..086afea3 100644 --- a/src/strategy/actions/MovementActions.cpp +++ b/src/strategy/actions/MovementActions.cpp @@ -177,7 +177,7 @@ bool MovementAction::MoveToLOS(WorldObject* target, bool ranged) } bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, bool react, bool normal_only, - bool exact_waypoint, MovementPriority priority) + bool exact_waypoint, MovementPriority priority, bool lessDelay) { UpdateMovementState(); if (!IsMovingAllowed(mapId, x, y, z)) @@ -210,6 +210,10 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, mm.Clear(); mm.MovePoint(0, x, y, z, generatePath); float delay = 1000.0f * (distance / vehicleBase->GetSpeed(MOVE_RUN)); + if (lessDelay) + { + delay -= botAI->GetReactDelay(); + } delay = std::max(.0f, delay); delay = std::min((float)sPlayerbotAIConfig->maxWaitForMove, delay); AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), delay, priority); @@ -233,6 +237,10 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, mm.Clear(); mm.MovePoint(0, x, y, z, generatePath); float delay = 1000.0f * MoveDelay(distance); + if (lessDelay) + { + delay -= botAI->GetReactDelay(); + } delay = std::max(.0f, delay); delay = std::min((float)sPlayerbotAIConfig->maxWaitForMove, delay); AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), delay, priority); @@ -264,6 +272,10 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, mm.Clear(); mm.MovePoint(0, endP.x, endP.y, endP.z, generatePath); float delay = 1000.0f * MoveDelay(distance); + if (lessDelay) + { + delay -= botAI->GetReactDelay(); + } delay = std::max(.0f, delay); delay = std::min((float)sPlayerbotAIConfig->maxWaitForMove, delay); AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), delay, priority); @@ -822,7 +834,7 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance) PathGenerator path(bot); path.CalculatePath(tx, ty, tz, false); PathType type = path.GetPathType(); - int typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE; + int typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_SHORTCUT; if (!(type & typeOk)) return false; float shortenTo = distance; @@ -838,7 +850,7 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance) path.ShortenPathUntilDist(G3D::Vector3(tx, ty, tz), shortenTo); G3D::Vector3 endPos = path.GetPath().back(); return MoveTo(target->GetMapId(), endPos.x, endPos.y, endPos.z, false, false, false, false, - MovementPriority::MOVEMENT_COMBAT); + MovementPriority::MOVEMENT_COMBAT, true); } float MovementAction::GetFollowAngle() @@ -2013,8 +2025,15 @@ Position MovementAction::BestPositionForMeleeToFlee(Position pos, float radius) } bool strict = checkAngle.strict; float fleeDis = std::min(radius + 1.0f, sPlayerbotAIConfig->fleeDistance); - Position fleePos{bot->GetPositionX() + cos(angle) * fleeDis, bot->GetPositionY() + sin(angle) * fleeDis, - bot->GetPositionZ()}; + float dx = bot->GetPositionX() + cos(angle) * fleeDis; + float dy = bot->GetPositionY() + sin(angle) * fleeDis; + float dz = bot->GetPositionZ(); + if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), + bot->GetPositionZ(), dx, dy, dz)) + { + continue; + } + Position fleePos{dx, dy, dz}; if (strict && currentTarget && fleePos.GetExactDist(currentTarget) - currentTarget->GetCombatReach() > sPlayerbotAIConfig->tooCloseDistance && @@ -2069,8 +2088,15 @@ Position MovementAction::BestPositionForRangedToFlee(Position pos, float radius) } bool strict = checkAngle.strict; float fleeDis = std::min(radius + 1.0f, sPlayerbotAIConfig->fleeDistance); - Position fleePos{bot->GetPositionX() + cos(angle) * fleeDis, bot->GetPositionY() + sin(angle) * fleeDis, - bot->GetPositionZ()}; + float dx = bot->GetPositionX() + cos(angle) * fleeDis; + float dy = bot->GetPositionY() + sin(angle) * fleeDis; + float dz = bot->GetPositionZ(); + if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), + bot->GetPositionZ(), dx, dy, dz)) + { + continue; + } + Position fleePos{dx, dy, dz}; if (strict && currentTarget && fleePos.GetExactDist(currentTarget) - currentTarget->GetCombatReach() > sPlayerbotAIConfig->spellDistance) { @@ -2082,6 +2108,7 @@ Position MovementAction::BestPositionForRangedToFlee(Position pos, float radius) { continue; } + if (pos.GetExactDist(fleePos) > farestDis) { farestDis = pos.GetExactDist(fleePos); diff --git a/src/strategy/actions/MovementActions.h b/src/strategy/actions/MovementActions.h index 76929427..ce6c64b2 100644 --- a/src/strategy/actions/MovementActions.h +++ b/src/strategy/actions/MovementActions.h @@ -32,7 +32,7 @@ protected: bool MoveNear(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->contactDistance, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); bool MoveToLOS(WorldObject* target, bool ranged = false); bool MoveTo(uint32 mapId, float x, float y, float z, bool idle = false, bool react = false, - bool normal_only = false, bool exact_waypoint = false, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); + bool normal_only = false, bool exact_waypoint = false, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, bool lessDelay = false); bool MoveTo(WorldObject* target, float distance = 0.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig->contactDistance, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); float GetFollowAngle(); diff --git a/src/strategy/actions/ReviveFromCorpseAction.cpp b/src/strategy/actions/ReviveFromCorpseAction.cpp index 7845a073..960f438f 100644 --- a/src/strategy/actions/ReviveFromCorpseAction.cpp +++ b/src/strategy/actions/ReviveFromCorpseAction.cpp @@ -188,7 +188,7 @@ bool FindCorpseAction::Execute(Event event) if (!moved) { - moved = botAI->DoSpecificAction("spirit healer"); + moved = botAI->DoSpecificAction("spirit healer", Event(), true); } } } @@ -347,16 +347,16 @@ bool SpiritHealerAction::Execute(Event event) if (moved) return true; - if (!botAI->HasActivePlayerMaster()) - { - context->GetValue("death count")->Set(dCount + 1); - return bot->TeleportTo(ClosestGrave->Map, ClosestGrave->x, ClosestGrave->y, ClosestGrave->z, 0.f); - } + // if (!botAI->HasActivePlayerMaster()) + // { + context->GetValue("death count")->Set(dCount + 1); + return bot->TeleportTo(ClosestGrave->Map, ClosestGrave->x, ClosestGrave->y, ClosestGrave->z, 0.f); + // } - LOG_INFO("playerbots", "Bot {} {}:{} <{}> can't find a spirit healer", bot->GetGUID().ToString().c_str(), - bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", bot->GetLevel(), bot->GetName().c_str()); + // LOG_INFO("playerbots", "Bot {} {}:{} <{}> can't find a spirit healer", bot->GetGUID().ToString().c_str(), + // bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", bot->GetLevel(), bot->GetName().c_str()); - botAI->TellError("Cannot find any spirit healer nearby"); + // botAI->TellError("Cannot find any spirit healer nearby"); return false; } diff --git a/src/strategy/druid/FeralDruidStrategy.cpp b/src/strategy/druid/FeralDruidStrategy.cpp index 91e315b2..5fe2f63b 100644 --- a/src/strategy/druid/FeralDruidStrategy.cpp +++ b/src/strategy/druid/FeralDruidStrategy.cpp @@ -101,7 +101,7 @@ void FeralDruidStrategy::InitTriggers(std::vector& triggers) // triggers.push_back(new TriggerNode("not facing target", NextAction::array(0, new NextAction("set facing", // ACTION_NORMAL + 7), nullptr))); triggers.push_back(new TriggerNode( - "enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_NORMAL + 8), nullptr))); + "enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_HIGH + 1), nullptr))); // triggers.push_back(new TriggerNode("enemy too close for melee", NextAction::array(0, new NextAction("move out of // enemy contact", ACTION_NORMAL + 8), nullptr))); triggers.push_back(new TriggerNode( diff --git a/src/strategy/generic/ChatCommandHandlerStrategy.cpp b/src/strategy/generic/ChatCommandHandlerStrategy.cpp index e88ac5bb..132f2423 100644 --- a/src/strategy/generic/ChatCommandHandlerStrategy.cpp +++ b/src/strategy/generic/ChatCommandHandlerStrategy.cpp @@ -106,6 +106,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas supported.push_back("reputation"); supported.push_back("log"); supported.push_back("los"); + supported.push_back("rpg status"); supported.push_back("aura"); supported.push_back("drop"); supported.push_back("share"); diff --git a/src/strategy/generic/CombatStrategy.cpp b/src/strategy/generic/CombatStrategy.cpp index bd1e3838..29c525fb 100644 --- a/src/strategy/generic/CombatStrategy.cpp +++ b/src/strategy/generic/CombatStrategy.cpp @@ -22,7 +22,7 @@ void CombatStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("not facing target", NextAction::array(0, new NextAction("set facing", ACTION_MOVE + 7), nullptr))); triggers.push_back( - new TriggerNode("pet attack", NextAction::array(0, new NextAction("pet attack", ACTION_NORMAL), nullptr))); + new TriggerNode("pet attack", NextAction::array(0, new NextAction("pet attack", 40.0f), nullptr))); // triggers.push_back(new TriggerNode("combat long stuck", NextAction::array(0, new NextAction("hearthstone", 0.9f), // new NextAction("repop", 0.8f), nullptr))); } diff --git a/src/strategy/generic/GrindingStrategy.cpp b/src/strategy/generic/GrindingStrategy.cpp index c8638e45..87970637 100644 --- a/src/strategy/generic/GrindingStrategy.cpp +++ b/src/strategy/generic/GrindingStrategy.cpp @@ -11,10 +11,10 @@ NextAction** GrindingStrategy::getDefaultActions() { return nullptr; } void GrindingStrategy::InitTriggers(std::vector& triggers) { - triggers.push_back(new TriggerNode("timer", NextAction::array(0, new NextAction("drink", 4.2f), nullptr))); - triggers.push_back(new TriggerNode("timer", NextAction::array(0, new NextAction("food", 4.1f), nullptr))); + triggers.push_back(new TriggerNode("timer", NextAction::array(0, new NextAction("drink", 10.2f), nullptr))); + triggers.push_back(new TriggerNode("timer", NextAction::array(0, new NextAction("food", 10.1f), nullptr))); triggers.push_back( - new TriggerNode("no target", NextAction::array(0, new NextAction("attack anything", 4.0f), nullptr))); + new TriggerNode("no target", NextAction::array(0, new NextAction("attack anything", 10.0f), nullptr))); } void MoveRandomStrategy::InitTriggers(std::vector& triggers) diff --git a/src/strategy/generic/MeleeCombatStrategy.cpp b/src/strategy/generic/MeleeCombatStrategy.cpp index 178bd418..67a5f1db 100644 --- a/src/strategy/generic/MeleeCombatStrategy.cpp +++ b/src/strategy/generic/MeleeCombatStrategy.cpp @@ -14,7 +14,7 @@ void MeleeCombatStrategy::InitTriggers(std::vector& triggers) // triggers.push_back(new TriggerNode("not facing target", NextAction::array(0, new NextAction("set facing", // ACTION_MOVE + 7), nullptr))); triggers.push_back(new TriggerNode( - "enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_NORMAL + 8), nullptr))); + "enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_HIGH + 1), nullptr))); // triggers.push_back(new TriggerNode("enemy too close for melee", NextAction::array(0, new NextAction("move out of // enemy contact", ACTION_NORMAL + 8), nullptr))); } diff --git a/src/strategy/mage/FrostMageStrategy.cpp b/src/strategy/mage/FrostMageStrategy.cpp index f4b02dc7..6dd46152 100644 --- a/src/strategy/mage/FrostMageStrategy.cpp +++ b/src/strategy/mage/FrostMageStrategy.cpp @@ -59,8 +59,9 @@ FrostMageStrategy::FrostMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(b NextAction** FrostMageStrategy::getDefaultActions() { - return NextAction::array(0, new NextAction("frostbolt", ACTION_DEFAULT + 0.1f), - new NextAction("shoot", ACTION_DEFAULT), nullptr); + return NextAction::array(0, new NextAction("frostbolt", ACTION_DEFAULT + 0.2f), + new NextAction("shoot", ACTION_DEFAULT + 0.1f), + new NextAction("fireball", ACTION_DEFAULT), nullptr); } void FrostMageStrategy::InitTriggers(std::vector& triggers) diff --git a/src/strategy/paladin/DpsPaladinStrategy.cpp b/src/strategy/paladin/DpsPaladinStrategy.cpp index efd55a6a..4b6bf78c 100644 --- a/src/strategy/paladin/DpsPaladinStrategy.cpp +++ b/src/strategy/paladin/DpsPaladinStrategy.cpp @@ -131,5 +131,5 @@ void DpsPaladinStrategy::InitTriggers(std::vector& triggers) // NextAction::array(0, new NextAction("set facing", ACTION_NORMAL + 7), NULL))); triggers.push_back(new TriggerNode("enemy out of melee", - NextAction::array(0, new NextAction("reach melee", ACTION_NORMAL + 8), NULL))); + NextAction::array(0, new NextAction("reach melee", ACTION_HIGH + 1), NULL))); } diff --git a/src/strategy/paladin/TankPaladinStrategy.cpp b/src/strategy/paladin/TankPaladinStrategy.cpp index 25e761b7..aa017d0d 100644 --- a/src/strategy/paladin/TankPaladinStrategy.cpp +++ b/src/strategy/paladin/TankPaladinStrategy.cpp @@ -113,5 +113,5 @@ void TankPaladinStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("not facing target", NextAction::array(0, new NextAction("set facing", ACTION_NORMAL + 7), nullptr))); triggers.push_back(new TriggerNode( - "enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_NORMAL + 8), nullptr))); + "enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_HIGH + 1), nullptr))); } diff --git a/src/strategy/rogue/AssassinationRogueStrategy.cpp b/src/strategy/rogue/AssassinationRogueStrategy.cpp index 5f3d2211..6069b383 100644 --- a/src/strategy/rogue/AssassinationRogueStrategy.cpp +++ b/src/strategy/rogue/AssassinationRogueStrategy.cpp @@ -88,6 +88,6 @@ void AssassinationRogueStrategy::InitTriggers(std::vector& trigger triggers.push_back(new TriggerNode( "enemy out of melee", - NextAction::array(0, new NextAction("stealth", ACTION_NORMAL + 9), new NextAction("sprint", ACTION_NORMAL + 8), - new NextAction("reach melee", ACTION_NORMAL + 7), NULL))); + NextAction::array(0, new NextAction("stealth", ACTION_HIGH + 3), new NextAction("sprint", ACTION_HIGH + 2), + new NextAction("reach melee", ACTION_HIGH + 1), NULL))); } diff --git a/src/strategy/rogue/DpsRogueStrategy.cpp b/src/strategy/rogue/DpsRogueStrategy.cpp index 0fd0d003..de4815df 100644 --- a/src/strategy/rogue/DpsRogueStrategy.cpp +++ b/src/strategy/rogue/DpsRogueStrategy.cpp @@ -134,8 +134,8 @@ void DpsRogueStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode( "enemy out of melee", - NextAction::array(0, new NextAction("stealth", ACTION_NORMAL + 9), new NextAction("sprint", ACTION_NORMAL + 8), - new NextAction("reach melee", ACTION_NORMAL + 7), nullptr))); + NextAction::array(0, new NextAction("stealth", ACTION_HIGH + 3), new NextAction("sprint", ACTION_HIGH + 2), + new NextAction("reach melee", ACTION_HIGH + 1), nullptr))); triggers.push_back(new TriggerNode("expose armor", NextAction::array(0, new NextAction("expose armor", ACTION_HIGH + 3), nullptr))); diff --git a/src/strategy/rpg/NewRpgAction.cpp b/src/strategy/rpg/NewRpgAction.cpp new file mode 100644 index 00000000..925585ba --- /dev/null +++ b/src/strategy/rpg/NewRpgAction.cpp @@ -0,0 +1,380 @@ +#include "NewRpgAction.h" + +#include +#include + +#include "NewRpgStrategy.h" +#include "ObjectDefines.h" +#include "ObjectGuid.h" +#include "PathGenerator.h" +#include "Player.h" +#include "PlayerbotAI.h" +#include "Playerbots.h" +#include "Random.h" +#include "RandomPlayerbotMgr.h" +#include "Timer.h" +#include "TravelMgr.h" +#include "World.h" + +bool TellRpgStatusAction::Execute(Event event) +{ + std::string out = botAI->rpgInfo.ToString(); + botAI->TellMasterNoFacing(out); + return true; +} + +bool NewRpgStatusUpdateAction::Execute(Event event) +{ + NewRpgInfo& info = botAI->rpgInfo; + switch (info.status) + { + case NewRpgStatus::IDLE: + { + uint32 roll = urand(1, 100); + // IDLE -> NEAR_NPC + // if ((!info.lastNearNpc || info.lastNearNpc + setNpcInterval < getMSTime()) && roll <= 30) + if (roll <= 30) + { + info.lastNearNpc = getMSTime(); + GuidVector possibleTargets = AI_VALUE(GuidVector, "possible rpg targets"); + if (!possibleTargets.empty()) + { + info.status = NewRpgStatus::NEAR_NPC; + return true; + } + } + // IDLE -> GO_INNKEEPER + else if (bot->GetLevel() >= 6 && roll <= 40) + { + WorldPosition pos = SelectRandomInnKeeperPos(); + if (pos != WorldPosition() && bot->GetExactDist(pos) > 50.0f) + { + info.lastGoInnKeeper = getMSTime(); + info.status = NewRpgStatus::GO_INNKEEPER; + info.innKeeperPos = pos; + return true; + } + } + // IDLE -> GO_GRIND + else if (roll <= 90) + { + WorldPosition pos = SelectRandomGrindPos(); + if (pos != WorldPosition()) + { + info.lastGoGrind = getMSTime(); + info.status = NewRpgStatus::GO_GRIND; + info.grindPos = pos; + return true; + } + } + // IDLE -> REST + info.status = NewRpgStatus::REST; + info.lastRest = getMSTime(); + bot->SetStandState(UNIT_STAND_STATE_SIT); + return true; + } + case NewRpgStatus::GO_GRIND: + { + WorldPosition& originalPos = info.grindPos; + assert(info.grindPos != WorldPosition()); + // GO_GRIND -> NEAR_RANDOM + if (bot->GetExactDist(originalPos) < 10.0f) + { + info.status = NewRpgStatus::NEAR_RANDOM; + info.lastNearRandom = getMSTime(); + info.grindPos = WorldPosition(); + return true; + } + // // just choose another grindPos + // if (!info.lastGoGrind || info.lastGoGrind + setGrindInterval < getMSTime()) + // { + // WorldPosition pos = SelectRandomGrindPos(); + // if (pos == WorldPosition()) + // break; + // info.status = NewRpgStatus::GO_GRIND; + // info.lastGoGrind = getMSTime(); + // info.grindPos = pos; + // return true; + // } + break; + } + case NewRpgStatus::GO_INNKEEPER: + { + WorldPosition& originalPos = info.innKeeperPos; + assert(info.innKeeperPos != WorldPosition()); + // GO_INNKEEPER -> NEAR_NPC + if (bot->GetExactDist(originalPos) < 10.0f) + { + info.lastNearNpc = getMSTime(); + info.status = NewRpgStatus::NEAR_NPC; + info.innKeeperPos = WorldPosition(); + return true; + } + break; + } + case NewRpgStatus::NEAR_RANDOM: + { + // NEAR_RANDOM -> IDLE + if (info.lastNearRandom + statusNearRandomDuration < getMSTime()) + { + info.status = NewRpgStatus::IDLE; + return true; + } + break; + } + case NewRpgStatus::NEAR_NPC: + { + if (info.lastNearNpc + statusNearNpcDuration < getMSTime()) + { + info.status = NewRpgStatus::IDLE; + return true; + } + break; + } + case NewRpgStatus::REST: + { + // REST -> IDLE + if (info.lastRest + statusRestDuration < getMSTime()) + { + info.status = NewRpgStatus::IDLE; + return true; + } + break; + } + default: + break; + } + return false; +} + +WorldPosition NewRpgStatusUpdateAction::SelectRandomGrindPos() +{ + const std::vector& locs = sRandomPlayerbotMgr->locsPerLevelCache[bot->GetLevel()]; + std::vector lo_prepared_locs, hi_prepared_locs; + for (auto& loc : locs) + { + if (bot->GetMapId() != loc.GetMapId()) + continue; + + if (bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) != + bot->GetZoneId()) + continue; + + if (bot->GetExactDist(loc) < 500.0f) + { + hi_prepared_locs.push_back(loc); + } + + if (bot->GetExactDist(loc) < 2500.0f) + { + lo_prepared_locs.push_back(loc); + } + } + WorldPosition dest; + if (urand(1, 100) <= 50 && !hi_prepared_locs.empty()) + { + uint32 idx = urand(0, hi_prepared_locs.size() - 1); + dest = hi_prepared_locs[idx]; + } + else if (!lo_prepared_locs.empty()) + { + uint32 idx = urand(0, lo_prepared_locs.size() - 1); + dest = lo_prepared_locs[idx]; + } + LOG_DEBUG("playerbots", "[New Rpg] Bot {} select random grind pos Map:{} X:{} Y:{} Z:{} ({}+{} available in {})", + bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), + hi_prepared_locs.size(), lo_prepared_locs.size() - hi_prepared_locs.size(), locs.size()); + return dest; +} + +WorldPosition NewRpgStatusUpdateAction::SelectRandomInnKeeperPos() +{ + const std::vector& locs = IsAlliance(bot->getRace()) + ? sRandomPlayerbotMgr->allianceStarterPerLevelCache[bot->GetLevel()] + : sRandomPlayerbotMgr->hordeStarterPerLevelCache[bot->GetLevel()]; + std::vector prepared_locs; + for (auto& loc : locs) + { + if (bot->GetMapId() != loc.GetMapId()) + continue; + + float range = bot->GetLevel() <= 5 ? 500.0f : 2500.0f; + if (bot->GetExactDist(loc) < range) + { + prepared_locs.push_back(loc); + } + } + WorldPosition dest; + if (!prepared_locs.empty()) + { + uint32 idx = urand(0, prepared_locs.size() - 1); + dest = prepared_locs[idx]; + } + LOG_DEBUG("playerbots", "[New Rpg] Bot {} select random inn keeper pos Map:{} X:{} Y:{} Z:{} ({} available in {})", + bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), + prepared_locs.size(), locs.size()); + return dest; +} + +bool NewRpgGoFarAwayPosAction::MoveFarTo(WorldPosition 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)) + { + return false; + } + + float minDelta = M_PI; + const float x = bot->GetPositionX(); + const float y = bot->GetPositionY(); + const float z = bot->GetPositionZ(); + float rx, ry, rz; + bool found = false; + int attempt = 10; + while (--attempt) + { + float angle = bot->GetAngle(&dest); + float delta = (rand_norm() - 0.5) * M_PI * 2; + angle += delta; + float dis = rand_norm() * pathFinderDis; + float dx = x + cos(angle) * dis; + float dy = y + sin(angle) * dis; + float dz = z + 5.0f; + bot->UpdateAllowedPositionZ(dx, dy, dz); + PathGenerator path(bot); + path.CalculatePath(dx, dy, dz); + PathType type = path.GetPathType(); + + bool canReach = type == PATHFIND_INCOMPLETE || type == PATHFIND_NORMAL; + + if (canReach && fabs(delta) <= minDelta) + { + found = true; + const G3D::Vector3& endPos = path.GetActualEndPosition(); + rx = endPos.x; + ry = endPos.y; + rz = endPos.z; + minDelta = fabs(delta); + } + } + if (found) + { + return MoveTo(bot->GetMapId(), rx, ry, rz, false, false, false, true); + } + // don't fallback to direct move + // float angle = bot->GetAngle(&dest); + // return MoveTo(bot->GetMapId(), x + cos(angle) * pathFinderDis, y + sin(angle) * pathFinderDis, z); + return false; +} + +bool NewRpgGoGrindAction::Execute(Event event) { return MoveFarTo(botAI->rpgInfo.grindPos); } + +bool NewRpgGoInnKeeperAction::Execute(Event event) { return MoveFarTo(botAI->rpgInfo.innKeeperPos); } + +bool NewRpgMoveRandomAction::Execute(Event event) +{ + float distance = rand_norm() * moveStep; + Map* map = bot->GetMap(); + const float x = bot->GetPositionX(); + const float y = bot->GetPositionY(); + const float z = bot->GetPositionZ(); + int attempts = 5; + while (--attempts) + { + float angle = (float)rand_norm() * 2 * static_cast(M_PI); + float dx = x + distance * cos(angle); + float dy = y + distance * sin(angle); + float dz = z; + if (!map->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), + dx, dy, dz)) + continue; + + if (map->IsInWater(bot->GetPhaseMask(), dx, dy, dz, bot->GetCollisionHeight())) + continue; + + bool moved = MoveTo(bot->GetMapId(), dx, dy, dz, false, false, false, true); + if (moved) + return true; + } + + return false; +} + +bool NewRpgMoveNpcAction::Execute(Event event) +{ + NewRpgInfo& info = botAI->rpgInfo; + if (!info.npcPos) + { + GuidVector possibleTargets = AI_VALUE(GuidVector, "possible rpg targets"); + if (possibleTargets.empty()) + return false; + int idx = urand(0, possibleTargets.size() - 1); + ObjectGuid guid = possibleTargets[idx]; + Unit* unit = botAI->GetUnit(guid); + if (unit) + { + info.npcPos = GuidPosition(unit); + info.lastReachNpc = 0; + } + else + return false; + } + + if (bot->GetDistance(info.npcPos) <= INTERACTION_DISTANCE) + { + if (!info.lastReachNpc) + { + info.lastReachNpc = getMSTime(); + return true; + } + + if (info.lastReachNpc && info.lastReachNpc + stayTime > getMSTime()) + return false; + + info.npcPos = GuidPosition(); + info.lastReachNpc = 0; + } + else + { + assert(info.npcPos); + Unit* unit = botAI->GetUnit(info.npcPos); + if (!unit) + return false; + float x = unit->GetPositionX(); + float y = unit->GetPositionY(); + float z = unit->GetPositionZ(); + float mapId = unit->GetMapId(); + float angle = 0.f; + if (bot->IsWithinLOS(x, y, z)) + { + if (!unit->isMoving()) + angle = unit->GetAngle(bot) + (M_PI * irand(-25, 25) / 100.0); // Closest 45 degrees towards the target + else + angle = unit->GetOrientation() + + (M_PI * irand(-25, 25) / 100.0); // 45 degrees infront of target (leading it's movement) + } + else + angle = 2 * M_PI * rand_norm(); // A circle around the target. + float rnd = rand_norm(); + x += cos(angle) * INTERACTION_DISTANCE * rnd; + y += sin(angle) * INTERACTION_DISTANCE * rnd; + // bool exact = true; + if (!unit->GetMap()->CheckCollisionAndGetValidCoords(unit, unit->GetPositionX(), unit->GetPositionY(), + unit->GetPositionZ(), x, y, z)) + { + x = unit->GetPositionX(); + y = unit->GetPositionY(); + z = unit->GetPositionZ(); + // exact = false; + } + return MoveTo(mapId, x, y, z, false, false, false, true); + } + return true; +} \ No newline at end of file diff --git a/src/strategy/rpg/NewRpgAction.h b/src/strategy/rpg/NewRpgAction.h new file mode 100644 index 00000000..fb293b8e --- /dev/null +++ b/src/strategy/rpg/NewRpgAction.h @@ -0,0 +1,78 @@ +#ifndef _PLAYERBOT_NEWRPGACTION_H +#define _PLAYERBOT_NEWRPGACTION_H + +#include "Duration.h" +#include "MovementActions.h" +#include "NewRpgStrategy.h" +#include "TravelMgr.h" +#include "PlayerbotAI.h" + +class TellRpgStatusAction : public Action +{ +public: + TellRpgStatusAction(PlayerbotAI* botAI) : Action(botAI, "rpg status") {} + + bool Execute(Event event) override; +}; + +class NewRpgStatusUpdateAction : public Action +{ +public: + NewRpgStatusUpdateAction(PlayerbotAI* botAI) : Action(botAI, "new rpg status update") {} + bool Execute(Event event) override; +protected: + // const int32 setGrindInterval = 5 * 60 * 1000; + // const int32 setNpcInterval = 1 * 60 * 1000; + const int32 statusNearNpcDuration = 2 * 60 * 1000; + const int32 statusNearRandomDuration = 2 * 60 * 1000; + const int32 statusRestDuration = 30 * 1000; + WorldPosition SelectRandomGrindPos(); + WorldPosition SelectRandomInnKeeperPos(); +}; + +class NewRpgGoFarAwayPosAction : public MovementAction +{ +public: + NewRpgGoFarAwayPosAction(PlayerbotAI* botAI, std::string name) : MovementAction(botAI, name) {} + // bool Execute(Event event) override; + bool MoveFarTo(WorldPosition dest); + +protected: + // WorldPosition dest; + float pathFinderDis = 70.0f; // path finder +}; + +class NewRpgGoGrindAction : public NewRpgGoFarAwayPosAction +{ +public: + NewRpgGoGrindAction(PlayerbotAI* botAI) : NewRpgGoFarAwayPosAction(botAI, "new rpg go grind") {} + bool Execute(Event event) override; +}; + +class NewRpgGoInnKeeperAction : public NewRpgGoFarAwayPosAction +{ +public: + NewRpgGoInnKeeperAction(PlayerbotAI* botAI) : NewRpgGoFarAwayPosAction(botAI, "new rpg go innkeeper") {} + bool Execute(Event event) override; +}; + + +class NewRpgMoveRandomAction : public MovementAction +{ +public: + NewRpgMoveRandomAction(PlayerbotAI* botAI) : MovementAction(botAI, "new rpg move random") {} + bool Execute(Event event) override; +protected: + const float moveStep = 50.0f; +}; + +class NewRpgMoveNpcAction : public MovementAction +{ +public: + NewRpgMoveNpcAction(PlayerbotAI* botAI) : MovementAction(botAI, "new rpg move npcs") {} + bool Execute(Event event) override; +protected: + const uint32 stayTime = 8 * 1000; +}; + +#endif \ No newline at end of file diff --git a/src/strategy/rpg/NewRpgStrategy.cpp b/src/strategy/rpg/NewRpgStrategy.cpp new file mode 100644 index 00000000..82bd03ba --- /dev/null +++ b/src/strategy/rpg/NewRpgStrategy.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016+ AzerothCore , 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 "NewRpgStrategy.h" + +#include "Playerbots.h" + +NewRpgStrategy::NewRpgStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} + +NextAction** NewRpgStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("new rpg status update", 5.0f), nullptr); +} + +void NewRpgStrategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back( + new TriggerNode("go grind status", NextAction::array(0, new NextAction("new rpg go grind", 1.0f), nullptr))); + + triggers.push_back( + new TriggerNode("go innkeeper status", NextAction::array(0, new NextAction("new rpg go innkeeper", 1.0f), nullptr))); + + triggers.push_back( + new TriggerNode("near random status", NextAction::array(0, new NextAction("new rpg move random", 1.0f), nullptr))); + + triggers.push_back( + new TriggerNode("near npc status", NextAction::array(0, new NextAction("new rpg move npc", 1.0f), nullptr))); +} + +void NewRpgStrategy::InitMultipliers(std::vector& multipliers) +{ + // multipliers.push_back(new RpgActionMultiplier(botAI)); +} diff --git a/src/strategy/rpg/NewRpgStrategy.h b/src/strategy/rpg/NewRpgStrategy.h new file mode 100644 index 00000000..36ddb67d --- /dev/null +++ b/src/strategy/rpg/NewRpgStrategy.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2016+ AzerothCore , 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_NEWRPGSTRATEGY_H +#define _PLAYERBOT_NEWRPGSTRATEGY_H + +#include +#include "Strategy.h" +#include "TravelMgr.h" + +class PlayerbotAI; + +enum class NewRpgStatus +{ + // Going to far away place + GO_GRIND, + GO_INNKEEPER, + // Exploring nearby + NEAR_RANDOM, + NEAR_NPC, + // Taking a break + REST, + // Initial status + IDLE +}; + +struct NewRpgInfo +{ + NewRpgStatus status{NewRpgStatus::IDLE}; + // NewRpgStatus::GO_GRIND + WorldPosition grindPos{}; + uint32 lastGoGrind{0}; + // NewRpgStatus::GO_INNKEEPER + WorldPosition innKeeperPos{}; + uint32 lastGoInnKeeper{0}; + // NewRpgStatus::NEAR_NPC + GuidPosition npcPos{}; + uint32 lastNearNpc{0}; + uint32 lastReachNpc{0}; + // NewRpgStatus::NEAR_RANDOM + uint32 lastNearRandom{0}; + // NewRpgStatus::REST + uint32 lastRest{0}; + + std::string ToString() + { + std::stringstream out; + out << "Status: "; + switch (status) + { + case NewRpgStatus::GO_GRIND: + out << "GO_GRIND"; + out << "\nGrindPos: " << grindPos.GetMapId() << " " << grindPos.GetPositionX() << " " << grindPos.GetPositionY() << " " << grindPos.GetPositionZ(); + out << "\nlastGoGrind: " << lastGoGrind; + break; + case NewRpgStatus::GO_INNKEEPER: + out << "GO_INNKEEPER"; + out << "\nInnKeeperPos: " << innKeeperPos.GetMapId() << " " << innKeeperPos.GetPositionX() << " " << innKeeperPos.GetPositionY() << " " << innKeeperPos.GetPositionZ(); + out << "\nlastGoInnKeeper: " << lastGoInnKeeper; + break; + case NewRpgStatus::NEAR_NPC: + out << "NEAR_NPC"; + out << "\nNpcPos: " << npcPos.GetMapId() << " " << npcPos.GetPositionX() << " " << npcPos.GetPositionY() << " " << npcPos.GetPositionZ(); + out << "\nlastNearNpc: " << lastNearNpc; + out << "\nlastReachNpc: " << lastReachNpc; + break; + case NewRpgStatus::NEAR_RANDOM: + out << "NEAR_RANDOM"; + out << "\nlastNearRandom: " << lastNearRandom; + break; + case NewRpgStatus::IDLE: + out << "IDLE"; + break; + case NewRpgStatus::REST: + out << "REST"; + out << "\nlastRest: " << lastRest; + break; + default: + out << "UNKNOWN"; + } + return out.str(); + } +}; + +class NewRpgStrategy : public Strategy +{ +public: + NewRpgStrategy(PlayerbotAI* botAI); + + std::string const getName() override { return "new rpg"; } + NextAction** getDefaultActions() override; + void InitTriggers(std::vector& triggers) override; + void InitMultipliers(std::vector& multipliers) override; +}; + +#endif diff --git a/src/strategy/rpg/NewRpgTrigger.cpp b/src/strategy/rpg/NewRpgTrigger.cpp new file mode 100644 index 00000000..66354092 --- /dev/null +++ b/src/strategy/rpg/NewRpgTrigger.cpp @@ -0,0 +1,4 @@ +#include "NewRpgTriggers.h" +#include "PlayerbotAI.h" + +bool NewRpgStatusTrigger::IsActive() { return status == botAI->rpgInfo.status; } \ No newline at end of file diff --git a/src/strategy/rpg/NewRpgTriggers.h b/src/strategy/rpg/NewRpgTriggers.h new file mode 100644 index 00000000..9827d57e --- /dev/null +++ b/src/strategy/rpg/NewRpgTriggers.h @@ -0,0 +1,20 @@ +#ifndef _PLAYERBOT_NEWRPGTRIGGERS_H +#define _PLAYERBOT_NEWRPGTRIGGERS_H + +#include "NewRpgStrategy.h" +#include "Trigger.h" + +class NewRpgStatusTrigger : public Trigger +{ +public: + NewRpgStatusTrigger(PlayerbotAI* botAI, NewRpgStatus status = NewRpgStatus::IDLE) + : Trigger(botAI, "new rpg status"), status(status) + { + } + bool IsActive() override; + +protected: + NewRpgStatus status; +}; + +#endif diff --git a/src/strategy/shaman/HealShamanStrategy.cpp b/src/strategy/shaman/HealShamanStrategy.cpp index 17d4ff44..c71f5f90 100644 --- a/src/strategy/shaman/HealShamanStrategy.cpp +++ b/src/strategy/shaman/HealShamanStrategy.cpp @@ -50,7 +50,7 @@ void HealShamanStrategy::InitTriggers(std::vector& triggers) NextAction::array(0, new NextAction("earthliving weapon", 22.0f), nullptr))); triggers.push_back(new TriggerNode( "group heal setting", - NextAction::array(0, new NextAction("riptide on party", 23.0f), new NextAction("chain heal on party", 22.0f), NULL))); + NextAction::array(0, new NextAction("riptide on party", 27.0f), new NextAction("chain heal on party", 26.0f), NULL))); triggers.push_back(new TriggerNode( "party member critical health", diff --git a/src/strategy/shaman/MeleeShamanStrategy.cpp b/src/strategy/shaman/MeleeShamanStrategy.cpp index d38c163a..96cea7b7 100644 --- a/src/strategy/shaman/MeleeShamanStrategy.cpp +++ b/src/strategy/shaman/MeleeShamanStrategy.cpp @@ -77,7 +77,7 @@ void MeleeShamanStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode( "medium aoe", NextAction::array(0, new NextAction("strength of earth totem", ACTION_LIGHT_HEAL), nullptr))); triggers.push_back(new TriggerNode( - "enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_NORMAL + 8), nullptr))); + "enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_HIGH + 1), nullptr))); triggers.push_back(new TriggerNode( "no fire totem", diff --git a/src/strategy/triggers/ChatTriggerContext.h b/src/strategy/triggers/ChatTriggerContext.h index af20f3a9..950cacb3 100644 --- a/src/strategy/triggers/ChatTriggerContext.h +++ b/src/strategy/triggers/ChatTriggerContext.h @@ -24,6 +24,7 @@ public: creators["reputation"] = &ChatTriggerContext::reputation; creators["log"] = &ChatTriggerContext::log; creators["los"] = &ChatTriggerContext::los; + creators["rpg status"] = &ChatTriggerContext::rpg_status; creators["aura"] = &ChatTriggerContext::aura; creators["drop"] = &ChatTriggerContext::drop; creators["share"] = &ChatTriggerContext::share; @@ -211,6 +212,7 @@ private: static Trigger* reputation(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "reputation"); } static Trigger* log(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "log"); } static Trigger* los(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "los"); } + static Trigger* rpg_status(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "rpg status"); } static Trigger* aura(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "aura"); } static Trigger* loot_all(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "add all loot"); } static Trigger* release(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "release"); } diff --git a/src/strategy/triggers/TriggerContext.h b/src/strategy/triggers/TriggerContext.h index cbfeec6f..2a18c0a1 100644 --- a/src/strategy/triggers/TriggerContext.h +++ b/src/strategy/triggers/TriggerContext.h @@ -12,6 +12,8 @@ #include "LfgTriggers.h" #include "LootTriggers.h" #include "NamedObjectContext.h" +#include "NewRpgStrategy.h" +#include "NewRpgTriggers.h" #include "PvpTriggers.h" #include "RaidNaxxTriggers.h" #include "RpgTriggers.h" @@ -213,6 +215,10 @@ public: creators["rpg craft"] = &TriggerContext::rpg_craft; creators["rpg trade useful"] = &TriggerContext::rpg_trade_useful; creators["rpg duel"] = &TriggerContext::rpg_duel; + creators["go grind status"] = &TriggerContext::go_grind_status; + creators["go innkeeper status"] = &TriggerContext::go_innkeeper_status; + creators["near random status"] = &TriggerContext::near_random_status; + creators["near npc status"] = &TriggerContext::near_npc_status; } private: @@ -402,6 +408,10 @@ private: static Trigger* rpg_craft(PlayerbotAI* botAI) { return new RpgCraftTrigger(botAI); } static Trigger* rpg_trade_useful(PlayerbotAI* botAI) { return new RpgTradeUsefulTrigger(botAI); } static Trigger* rpg_duel(PlayerbotAI* botAI) { return new RpgDuelTrigger(botAI); } + static Trigger* go_grind_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, NewRpgStatus::GO_GRIND); } + static Trigger* go_innkeeper_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, NewRpgStatus::GO_INNKEEPER); } + static Trigger* near_random_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, NewRpgStatus::NEAR_RANDOM); } + static Trigger* near_npc_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, NewRpgStatus::NEAR_NPC); } }; #endif diff --git a/src/strategy/values/DpsTargetValue.cpp b/src/strategy/values/DpsTargetValue.cpp index 88c52ccf..526f2e42 100644 --- a/src/strategy/values/DpsTargetValue.cpp +++ b/src/strategy/values/DpsTargetValue.cpp @@ -298,8 +298,9 @@ Unit* DpsTargetValue::Calculate() return rti; // FindLeastHpTargetStrategy strategy(botAI); + Group* group = bot->GetGroup(); float dps = AI_VALUE(float, "estimated group dps"); - if (botAI->IsCaster(bot)) + if (group && botAI->IsCaster(bot)) { CasterFindTargetSmartStrategy strategy(botAI, dps); return TargetValue::FindTarget(&strategy); diff --git a/src/strategy/values/GrindTargetValue.cpp b/src/strategy/values/GrindTargetValue.cpp index e6109463..b21f62a9 100644 --- a/src/strategy/values/GrindTargetValue.cpp +++ b/src/strategy/values/GrindTargetValue.cpp @@ -52,7 +52,7 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount) float distance = 0; Unit* result = nullptr; - std::unordered_map needForQuestMap; + // std::unordered_map needForQuestMap; for (ObjectGuid const guid : targets) { @@ -99,18 +99,18 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount) if (!bot->InBattleground() && (int)unit->GetLevel() - (int)bot->GetLevel() > 4 && !unit->GetGUID().IsPlayer()) continue; - if (needForQuestMap.find(unit->GetEntry()) == needForQuestMap.end()) - needForQuestMap[unit->GetEntry()] = needForQuest(unit); + // if (needForQuestMap.find(unit->GetEntry()) == needForQuestMap.end()) + // needForQuestMap[unit->GetEntry()] = needForQuest(unit); - if (!needForQuestMap[unit->GetEntry()]) - { - Creature* creature = dynamic_cast(unit); - if ((urand(0, 100) < 60 || (context->GetValue("travel target")->Get()->isWorking() && - context->GetValue("travel target")->Get()->getDestination()->getName() != "GrindTravelDestination"))) - { - continue; - } - } + // if (!needForQuestMap[unit->GetEntry()]) + // { + // Creature* creature = dynamic_cast(unit); + // if ((urand(0, 100) < 60 || (context->GetValue("travel target")->Get()->isWorking() && + // context->GetValue("travel target")->Get()->getDestination()->getName() != "GrindTravelDestination"))) + // { + // continue; + // } + // } if (Creature* creature = unit->ToCreature()) if (CreatureTemplate const* CreatureTemplate = creature->GetCreatureTemplate()) @@ -142,8 +142,7 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount) else { float newdistance = bot->GetDistance(unit); - if (!result || (newdistance < distance && - urand(0, abs(distance - newdistance)) > sPlayerbotAIConfig->sightDistance * 0.1)) + if (!result || (newdistance < distance)) { distance = newdistance; result = unit; diff --git a/src/strategy/values/PossibleRpgTargetsValue.h b/src/strategy/values/PossibleRpgTargetsValue.h index 02df8701..287f5db3 100644 --- a/src/strategy/values/PossibleRpgTargetsValue.h +++ b/src/strategy/values/PossibleRpgTargetsValue.h @@ -14,7 +14,7 @@ class PlayerbotAI; class PossibleRpgTargetsValue : public NearestUnitsValue { public: - PossibleRpgTargetsValue(PlayerbotAI* botAI, float range = sPlayerbotAIConfig->rpgDistance); + PossibleRpgTargetsValue(PlayerbotAI* botAI, float range = 70.0f); static std::vector allowedNpcFlags; diff --git a/src/strategy/warlock/GenericWarlockNonCombatStrategy.cpp b/src/strategy/warlock/GenericWarlockNonCombatStrategy.cpp index a46346fe..4de01421 100644 --- a/src/strategy/warlock/GenericWarlockNonCombatStrategy.cpp +++ b/src/strategy/warlock/GenericWarlockNonCombatStrategy.cpp @@ -48,7 +48,7 @@ private: { return new ActionNode("summon succubus", /*P*/ nullptr, - /*A*/ NextAction::array(0, new NextAction("summon imp"), nullptr), + /*A*/ NextAction::array(0, new NextAction("summon voidwalker"), nullptr), /*C*/ nullptr); } static ActionNode* summon_felhunter([[maybe_unused]] PlayerbotAI* botAI) diff --git a/src/strategy/warrior/GenericWarriorStrategy.cpp b/src/strategy/warrior/GenericWarriorStrategy.cpp index 99b107fc..3b5588ea 100644 --- a/src/strategy/warrior/GenericWarriorStrategy.cpp +++ b/src/strategy/warrior/GenericWarriorStrategy.cpp @@ -16,7 +16,7 @@ void GenericWarriorStrategy::InitTriggers(std::vector& triggers) { CombatStrategy::InitTriggers(triggers); triggers.push_back(new TriggerNode( - "enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_NORMAL + 8), nullptr))); + "enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_HIGH + 1), nullptr))); /*triggers.push_back(new TriggerNode("bloodrage", NextAction::array(0, new NextAction("bloodrage", ACTION_HIGH + 1), nullptr))); triggers.push_back(new TriggerNode("shield bash", NextAction::array(0, new NextAction("shield bash", ACTION_INTERRUPT + 4), nullptr))); triggers.push_back(new TriggerNode("shield bash on enemy healer",