diff --git a/data/sql/playerbots/updates/2025_11_25_00_ai_playerbot_pet_action_texts.sql b/data/sql/playerbots/updates/2025_11_25_00_ai_playerbot_pet_action_texts.sql new file mode 100644 index 0000000000..b7c6c96826 --- /dev/null +++ b/data/sql/playerbots/updates/2025_11_25_00_ai_playerbot_pet_action_texts.sql @@ -0,0 +1,255 @@ +DELETE FROM ai_playerbot_texts WHERE name IN ( + 'pet_usage_error', + 'pet_no_pet_error', + 'pet_stance_report', + 'pet_no_target_error', + 'pet_target_dead_error', + 'pet_invalid_target_error', + 'pet_pvp_prohibited_error', + 'pet_attack_success', + 'pet_attack_failed', + 'pet_follow_success', + 'pet_stay_success', + 'pet_unknown_command_error', + 'pet_stance_set_success', + 'pet_type_pet', + 'pet_type_guardian', + 'pet_stance_aggressive', + 'pet_stance_defensive', + 'pet_stance_passive', + 'pet_stance_unknown' +); + +DELETE FROM ai_playerbot_texts_chance WHERE name IN ( + 'pet_usage_error', + 'pet_no_pet_error', + 'pet_stance_report', + 'pet_no_target_error', + 'pet_target_dead_error', + 'pet_invalid_target_error', + 'pet_pvp_prohibited_error', + 'pet_attack_success', + 'pet_attack_failed', + 'pet_follow_success', + 'pet_stay_success', + 'pet_unknown_command_error', + 'pet_stance_set_success', + 'pet_type_pet', + 'pet_type_guardian', + 'pet_stance_aggressive', + 'pet_stance_defensive', + 'pet_stance_passive', + 'pet_stance_unknown' +); + +INSERT INTO ai_playerbot_texts (id, name, text, say_type, reply_type, text_loc1, text_loc2, text_loc3, text_loc4, text_loc5, text_loc6, text_loc7, text_loc8) VALUES +(1717, 'pet_usage_error', "Usage: pet ", 0, 0, +"사용법: pet ", +"Utilisation: pet ", +"Verwendung: pet ", +"用法: pet ", +"用法: pet ", +"Uso: pet ", +"Uso: pet ", +"Использование: pet "), + +(1718, 'pet_no_pet_error', "You have no pet or guardian pet.", 0, 0, +"펫이나 수호자 펫이 없습니다.", +"Vous n'avez pas de familier ou gardien.", +"Du hast kein Tier oder Wächter.", +"你没有宠物或守护者宠物。", +"你沒有寵物或守護者寵物。", +"No tienes mascota o mascota guardián.", +"No tienes mascota o mascota guardián.", +"У вас нет питомца или защитника."), + +(1719, 'pet_stance_report', "Current stance of %type \"%name\": %stance.", 0, 0, +"%type \"%name\"의 현재 태세: %stance.", +"Position actuelle du %type \"%name\": %stance.", +"Aktuelle Haltung des %type \"%name\": %stance.", +"%type \"%name\" 的当前姿态: %stance。", +"%type \"%name\" 的當前姿態: %stance。", +"Postura actual del %type \"%name\": %stance.", +"Postura actual del %type \"%name\": %stance.", +"Текущая позиция %type \"%name\": %stance."), + +(1720, 'pet_no_target_error', "No valid target selected by master.", 0, 0, +"주인이 유효한 대상을 선택하지 않았습니다.", +"Aucune cible valide sélectionnée par le maître.", +"Kein gültiges Ziel vom Meister ausgewählt.", +"主人未选择有效目标。", +"主人未選擇有效目標。", +"No hay objetivo válido seleccionado por el maestro.", +"No hay objetivo válido seleccionado por el maestro.", +"Хозяин не выбрал действительную цель."), + +(1721, 'pet_target_dead_error', "Target is not alive.", 0, 0, +"대상이 살아있지 않습니다.", +"La cible n'est pas vivante.", +"Das Ziel ist nicht am Leben.", +"目标未存活。", +"目標未存活。", +"El objetivo no está vivo.", +"El objetivo no está vivo.", +"Цель не жива."), + +(1722, 'pet_invalid_target_error', "Target is not a valid attack target for the bot.", 0, 0, +"대상이 봇에게 유효한 공격 대상이 아닙니다.", +"La cible n'est pas une cible d'attaque valide pour le bot.", +"Das Ziel ist kein gültiges Angriffsziel für den Bot.", +"目标不是机器人的有效攻击目标。", +"目標不是機器人的有效攻擊目標。", +"El objetivo no es un objetivo de ataque válido para el bot.", +"El objetivo no es un objetivo de ataque válido para el bot.", +"Цель не является допустимой целью атаки для бота."), + +(1723, 'pet_pvp_prohibited_error', "I cannot command my pet to attack players in PvP prohibited areas.", 0, 0, +"PvP 금지 지역에서는 펫에게 플레이어 공격 명령을 내릴 수 없습니다.", +"Je ne peux pas commander à mon familier d'attaquer des joueurs dans les zones où le PvP est interdit.", +"Ich kann meinem Tier nicht befehlen, Spieler in PvP-verbotenen Gebieten anzugreifen.", +"我不能命令我的宠物在禁止PvP的区域攻击玩家。", +"我不能命令我的寵物在禁止PvP的區域攻擊玩家。", +"No puedo ordenar a mi mascota atacar jugadores en áreas donde el PvP está prohibido.", +"No puedo ordenar a mi mascota atacar jugadores en áreas donde el PvP está prohibido.", +"Я не могу приказать своему питомцу атаковать игроков в зонах, где PvP запрещено."), + +(1724, 'pet_attack_success', "Pet commanded to attack your target.", 0, 0, +"펫이 당신의 대상을 공격하도록 명령했습니다.", +"Le familier a reçu l'ordre d'attaquer votre cible.", +"Tier wurde befohlen, dein Ziel anzugreifen.", +"宠物已命令攻击你的目标。", +"寵物已命令攻擊你的目標。", +"Mascota ordenada a atacar tu objetivo.", +"Mascota ordenada a atacar tu objetivo.", +"Питомцу приказано атаковать вашу цель."), + +(1725, 'pet_attack_failed', "Pet did not attack. (Already attacking or unable to attack target)", 0, 0, +"펫이 공격하지 않았습니다. (이미 공격 중이거나 대상 공격 불가)", +"Le familier n'a pas attaqué. (Attaque déjà en cours ou impossible d'attaquer la cible)", +"Tier hat nicht angegriffen. (Greift bereits an oder kann Ziel nicht angreifen)", +"宠物未攻击。(已在攻击或无法攻击目标)", +"寵物未攻擊。(已在攻擊或無法攻擊目標)", +"La mascota no atacó. (Ya está atacando o no puede atacar al objetivo)", +"La mascota no atacó. (Ya está atacando o no puede atacar al objetivo)", +"Питомец не атаковал. (Уже атакует или не может атаковать цель)"), + +(1726, 'pet_follow_success', "Pet commanded to follow.", 0, 0, +"펫이 따라오도록 명령했습니다.", +"Le familier a reçu l'ordre de suivre.", +"Tier wurde befohlen zu folgen.", +"宠物已命令跟随。", +"寵物已命令跟隨。", +"Mascota ordenada a seguir.", +"Mascota ordenada a seguir.", +"Питомцу приказано следовать."), + +(1727, 'pet_stay_success', "Pet commanded to stay.", 0, 0, +"펫이 머물도록 명령했습니다.", +"Le familier a reçu l'ordre de rester.", +"Tier wurde befohlen zu bleiben.", +"宠物已命令停留。", +"寵物已命令停留。", +"Mascota ordenada a quedarse.", +"Mascota ordenada a quedarse.", +"Питомцу приказано остаться."), + +(1728, 'pet_unknown_command_error', "Unknown pet command: %param. Use: pet ", 0, 0, +"알 수 없는 펫 명령: %param. 사용법: pet ", +"Commande de familier inconnue: %param. Utilisation: pet ", +"Unbekannter Tierbefehl: %param. Verwendung: pet ", +"未知宠物命令: %param。用法: pet ", +"未知寵物命令: %param。用法: pet ", +"Comando de mascota desconocido: %param. Uso: pet ", +"Comando de mascota desconocido: %param. Uso: pet ", +"Неизвестная команда питомца: %param. Использование: pet "), + +(1729, 'pet_stance_set_success', "Pet stance set to %stance.", 0, 0, +"펫 태세가 %stance(으)로 설정되었습니다.", +"Position du familier définie sur %stance.", +"Tierhaltung auf %stance gesetzt.", +"宠物姿态设置为 %stance。", +"寵物姿態設置為 %stance。", +"Postura de mascota establecida en %stance.", +"Postura de mascota establecida en %stance.", +"Позиция питомца установлена на %stance."), + +(1730, 'pet_type_pet', "pet", 0, 0, +"펫", +"familier", +"Tier", +"宠物", +"寵物", +"mascota", +"mascota", +"питомец"), + +(1731, 'pet_type_guardian', "guardian", 0, 0, +"수호자", +"gardien", +"Wächter", +"守护者", +"守護者", +"guardián", +"guardián", +"защитник"), + +(1732, 'pet_stance_aggressive', "aggressive", 0, 0, +"공격적", +"agressif", +"aggressiv", +"进攻", +"進攻", +"agresivo", +"agresivo", +"агрессивная"), + +(1733, 'pet_stance_defensive', "defensive", 0, 0, +"방어적", +"défensif", +"defensiv", +"防御", +"防禦", +"defensivo", +"defensivo", +"защитная"), + +(1734, 'pet_stance_passive', "passive", 0, 0, +"수동적", +"passif", +"passiv", +"被动", +"被動", +"pasivo", +"pasivo", +"пассивная"), + +(1735, 'pet_stance_unknown', "unknown", 0, 0, +"알 수 없음", +"inconnu", +"unbekannt", +"未知", +"未知", +"desconocido", +"desconocido", +"неизвестная"); + +INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES +('pet_usage_error', 100), +('pet_no_pet_error', 100), +('pet_stance_report', 100), +('pet_no_target_error', 100), +('pet_target_dead_error', 100), +('pet_invalid_target_error', 100), +('pet_pvp_prohibited_error', 100), +('pet_attack_success', 100), +('pet_attack_failed', 100), +('pet_follow_success', 100), +('pet_stay_success', 100), +('pet_unknown_command_error', 100), +('pet_stance_set_success', 100), +('pet_type_pet', 100), +('pet_type_guardian', 100), +('pet_stance_aggressive', 100), +('pet_stance_defensive', 100), +('pet_stance_passive', 100), +('pet_stance_unknown', 100); \ No newline at end of file diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 3748b84e02..2ef6860a2a 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -365,7 +365,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal) } // Update the bot's group status (moved to helper function) - UpdateAIGroupAndMaster(); + UpdateAIGroupMaster(); // Update internal AI UpdateAIInternal(elapsed, minimal); @@ -373,7 +373,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal) } // Helper function for UpdateAI to check group membership and handle removal if necessary -void PlayerbotAI::UpdateAIGroupAndMaster() +void PlayerbotAI::UpdateAIGroupMaster() { if (!bot) return; @@ -420,7 +420,7 @@ void PlayerbotAI::UpdateAIGroupAndMaster() { botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT); - if (botAI->GetMaster() == botAI->GetGroupMaster()) + if (botAI->GetMaster() == botAI->GetGroupLeader()) botAI->TellMaster("Hello, I follow you!"); else botAI->TellMaster(!urand(0, 2) ? "Hello!" : "Hi!"); @@ -431,8 +431,6 @@ void PlayerbotAI::UpdateAIGroupAndMaster() botAI->ChangeStrategy("-follow", BOT_STATE_NON_COMBAT); } } - else if (!newMaster && !bot->InBattleground()) - LeaveOrDisbandGroup(); } } @@ -1461,7 +1459,7 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster) strategyName = "onyxia"; // Onyxia's Lair break; case 409: - strategyName = "mc"; // Molten Core + strategyName = "moltencore"; // Molten Core break; case 469: strategyName = "bwl"; // Blackwing Lair @@ -2249,7 +2247,7 @@ uint32 PlayerbotAI::GetGroupTankNum(Player* player) bool PlayerbotAI::IsAssistTank(Player* player) { return IsTank(player) && !IsMainTank(player); } -bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index) +bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index, bool ignoreDeadPlayers) { Group* group = player->GetGroup(); if (!group) @@ -2266,6 +2264,9 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index) continue; } + if (ignoreDeadPlayers && !member->IsAlive()) + continue; + if (group->IsAssistant(member->GetGUID()) && IsAssistTank(member)) { if (index == counter) @@ -2285,6 +2286,9 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index) continue; } + if (ignoreDeadPlayers && !member->IsAlive()) + continue; + if (!group->IsAssistant(member->GetGUID()) && IsAssistTank(member)) { if (index == counter) @@ -4093,7 +4097,7 @@ Player* PlayerbotAI::FindNewMaster() if (!group) return nullptr; - Player* groupLeader = GetGroupMaster(); + Player* groupLeader = GetGroupLeader(); PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(groupLeader); if (!leaderBotAI || leaderBotAI->IsRealPlayer()) return groupLeader; @@ -4144,7 +4148,7 @@ bool PlayerbotAI::HasActivePlayerMaster() { return master && !GET_PLAYERBOT_AI(m bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); } -Player* PlayerbotAI::GetGroupMaster() +Player* PlayerbotAI::GetGroupLeader() { if (!bot->InBattleground()) if (Group* group = bot->GetGroup()) diff --git a/src/PlayerbotAI.h b/src/PlayerbotAI.h index 8c8cff14f2..3b9eaab1ea 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -428,7 +428,7 @@ class PlayerbotAI : public PlayerbotAIBase static bool IsMainTank(Player* player); static uint32 GetGroupTankNum(Player* player); static bool IsAssistTank(Player* player); - static bool IsAssistTankOfIndex(Player* player, int index); + static bool IsAssistTankOfIndex(Player* player, int index, bool ignoreDeadPlayers = false); static bool IsHealAssistantOfIndex(Player* player, int index); static bool IsRangedDpsAssistantOfIndex(Player* player, int index); bool HasAggro(Unit* unit); @@ -540,7 +540,7 @@ class PlayerbotAI : public PlayerbotAIBase // Get the group leader or the master of the bot. // Checks if the bot is summoned as alt of a player bool IsAlt(); - Player* GetGroupMaster(); + Player* GetGroupLeader(); // Returns a semi-random (cycling) number that is fixed for each bot. uint32 GetFixedBotNumer(uint32 maxNum = 100, float cyclePerMin = 1); GrouperType GetGrouperType(); @@ -612,7 +612,7 @@ class PlayerbotAI : public PlayerbotAIBase static void _fillGearScoreData(Player* player, Item* item, std::vector* gearScore, uint32& twoHandScore, bool mixed = false); bool IsTellAllowed(PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL); - void UpdateAIGroupAndMaster(); + void UpdateAIGroupMaster(); Item* FindItemInInventory(std::function checkItem) const; void HandleCommands(); void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL); diff --git a/src/PlayerbotSecurity.cpp b/src/PlayerbotSecurity.cpp index 45e8598989..822f40e6d4 100644 --- a/src/PlayerbotSecurity.cpp +++ b/src/PlayerbotSecurity.cpp @@ -251,9 +251,9 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, out << "I am currently leading a group. I can invite you if you want."; break; case PLAYERBOT_DENY_NOT_LEADER: - if (botAI->GetGroupMaster()) + if (botAI->GetGroupLeader()) { - out << "I am in a group with " << botAI->GetGroupMaster()->GetName() + out << "I am in a group with " << botAI->GetGroupLeader()->GetName() << ". You can ask him for invite."; } else diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 6e3dfde902..222990ca20 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -1480,10 +1480,10 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot) if (!sRandomPlayerbotMgr->IsRandomBot(player)) update = false; - if (player->GetGroup() && botAI->GetGroupMaster()) + if (player->GetGroup() && botAI->GetGroupLeader()) { - PlayerbotAI* groupMasterBotAI = GET_PLAYERBOT_AI(botAI->GetGroupMaster()); - if (!groupMasterBotAI || groupMasterBotAI->IsRealPlayer()) + PlayerbotAI* groupLeaderBotAI = GET_PLAYERBOT_AI(botAI->GetGroupLeader()); + if (!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer()) { update = false; } diff --git a/src/factory/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp index 65ffc444ee..50a216c970 100644 --- a/src/factory/PlayerbotFactory.cpp +++ b/src/factory/PlayerbotFactory.cpp @@ -4099,6 +4099,7 @@ void PlayerbotFactory::InitImmersive() void PlayerbotFactory::InitArenaTeam() { + if (!sPlayerbotAIConfig->IsInRandomAccountList(bot->GetSession()->GetAccountId())) return; @@ -4185,10 +4186,34 @@ void PlayerbotFactory::InitArenaTeam() if (botcaptain && botcaptain->GetTeamId() == bot->GetTeamId()) // need? { + // Add bot to arena team arenateam->AddMember(bot->GetGUID()); - arenateam->SaveToDB(); + + // Only synchronize ratings once the team is full (avoid redundant work) + // The captain was added with incorrect ratings when the team was created, + // so we fix everyone's ratings once the roster is complete + if (arenateam->GetMembersSize() >= (uint32)arenateam->GetType()) + { + uint32 teamRating = arenateam->GetRating(); + + // Use SetRatingForAll to align all members with team rating + arenateam->SetRatingForAll(teamRating); + + // For bot-only teams, keep MMR synchronized with team rating + // This ensures matchmaking reflects the artificial team strength (1000-2000 range) + // instead of being influenced by the global CONFIG_ARENA_START_MATCHMAKER_RATING + for (auto& member : arenateam->GetMembers()) + { + // Set MMR to match personal rating (which already matches team rating) + member.MatchMakerRating = member.PersonalRating; + member.MaxMMR = std::max(member.MaxMMR, member.PersonalRating); + } + // Force save all member data to database + arenateam->SaveToDB(true); + } } } + arenateams.erase(arenateams.begin() + index); } diff --git a/src/strategy/actions/ActionContext.h b/src/strategy/actions/ActionContext.h index cebd7615a7..09c9145f93 100644 --- a/src/strategy/actions/ActionContext.h +++ b/src/strategy/actions/ActionContext.h @@ -121,7 +121,7 @@ class ActionContext : public NamedObjectContext creators["shoot"] = &ActionContext::shoot; creators["follow"] = &ActionContext::follow; creators["move from group"] = &ActionContext::move_from_group; - creators["flee to master"] = &ActionContext::flee_to_master; + creators["flee to group leader"] = &ActionContext::flee_to_group_leader; creators["runaway"] = &ActionContext::runaway; creators["stay"] = &ActionContext::stay; creators["sit"] = &ActionContext::sit; @@ -318,7 +318,7 @@ class ActionContext : public NamedObjectContext static Action* runaway(PlayerbotAI* botAI) { return new RunAwayAction(botAI); } static Action* follow(PlayerbotAI* botAI) { return new FollowAction(botAI); } static Action* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupAction(botAI); } - static Action* flee_to_master(PlayerbotAI* botAI) { return new FleeToMasterAction(botAI); } + static Action* flee_to_group_leader(PlayerbotAI* botAI) { return new FleeToGroupLeaderAction(botAI); } static Action* add_gathering_loot(PlayerbotAI* botAI) { return new AddGatheringLootAction(botAI); } static Action* add_loot(PlayerbotAI* botAI) { return new AddLootAction(botAI); } static Action* add_all_loot(PlayerbotAI* botAI) { return new AddAllLootAction(botAI); } diff --git a/src/strategy/actions/ChooseRpgTargetAction.cpp b/src/strategy/actions/ChooseRpgTargetAction.cpp index 20151658cc..0d441e9b67 100644 --- a/src/strategy/actions/ChooseRpgTargetAction.cpp +++ b/src/strategy/actions/ChooseRpgTargetAction.cpp @@ -311,7 +311,7 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldObject* target) bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos) { PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); - Player* gmaster = botAI->GetGroupMaster(); + Player* groupLeader = botAI->GetGroupLeader(); Player* realMaster = botAI->GetMaster(); AiObjectContext* context = botAI->GetAiObjectContext(); @@ -327,30 +327,30 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos) return false; } - if (!gmaster || bot == gmaster) + if (!groupLeader || bot == groupLeader) return true; if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT)) return true; - if (bot->GetDistance(gmaster) > sPlayerbotAIConfig->rpgDistance * 2) + if (bot->GetDistance(groupLeader) > sPlayerbotAIConfig->rpgDistance * 2) return false; Formation* formation = AI_VALUE(Formation*, "formation"); - float distance = gmaster->GetDistance2d(pos.getX(), pos.getY()); + float distance = groupLeader->GetDistance2d(pos.getX(), pos.getY()); if (!botAI->HasActivePlayerMaster() && distance < 50.0f) { - Player* player = gmaster; - if (gmaster && !gmaster->isMoving() || + Player* player = groupLeader; + if (groupLeader && !groupLeader->isMoving() || PAI_VALUE(WorldPosition, "last long move").distance(pos) < sPlayerbotAIConfig->reactDistance) return true; } - if ((inDungeon || !gmaster->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == gmaster && distance > 5.0f) + if ((inDungeon || !groupLeader->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == groupLeader && distance > 5.0f) return false; - if (!gmaster->isMoving() && distance < 25.0f) + if (!groupLeader->isMoving() && distance < 25.0f) return true; if (distance < formation->GetMaxDistance()) diff --git a/src/strategy/actions/ChooseTravelTargetAction.cpp b/src/strategy/actions/ChooseTravelTargetAction.cpp index 3843f35876..cf6dddd40c 100644 --- a/src/strategy/actions/ChooseTravelTargetAction.cpp +++ b/src/strategy/actions/ChooseTravelTargetAction.cpp @@ -180,7 +180,7 @@ void ChooseTravelTargetAction::getNewTarget(TravelTarget* newTarget, TravelTarge void ChooseTravelTargetAction::setNewTarget(TravelTarget* newTarget, TravelTarget* oldTarget) { // Tell the master where we are going. - if (!bot->GetGroup() || (botAI->GetGroupMaster() == bot)) + if (!bot->GetGroup() || (botAI->GetGroupLeader() == bot)) ReportTravelTarget(newTarget, oldTarget); // If we are heading to a creature/npc clear it from the ignore list. diff --git a/src/strategy/actions/FollowActions.cpp b/src/strategy/actions/FollowActions.cpp index f4a66bf9e7..d168e8cc93 100644 --- a/src/strategy/actions/FollowActions.cpp +++ b/src/strategy/actions/FollowActions.cpp @@ -70,7 +70,7 @@ bool FollowAction::isUseful() if (!target.empty()) fTarget = AI_VALUE(Unit*, target); else - fTarget = AI_VALUE(Unit*, "master target"); + fTarget = AI_VALUE(Unit*, "group leader"); if (fTarget) { @@ -114,9 +114,9 @@ bool FollowAction::CanDeadFollow(Unit* target) return true; } -bool FleeToMasterAction::Execute(Event event) +bool FleeToGroupLeaderAction::Execute(Event event) { - Unit* fTarget = AI_VALUE(Unit*, "master target"); + Unit* fTarget = AI_VALUE(Unit*, "group leader"); bool canFollow = Follow(fTarget); if (!canFollow) { @@ -146,22 +146,22 @@ bool FleeToMasterAction::Execute(Event event) return true; } -bool FleeToMasterAction::isUseful() +bool FleeToGroupLeaderAction::isUseful() { - if (!botAI->GetGroupMaster()) + if (!botAI->GetGroupLeader()) return false; - if (botAI->GetGroupMaster() == bot) + if (botAI->GetGroupLeader() == bot) return false; Unit* target = AI_VALUE(Unit*, "current target"); - if (target && botAI->GetGroupMaster()->GetTarget() == target->GetGUID()) + if (target && botAI->GetGroupLeader()->GetTarget() == target->GetGUID()) return false; if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT)) return false; - Unit* fTarget = AI_VALUE(Unit*, "master target"); + Unit* fTarget = AI_VALUE(Unit*, "group leader"); if (!CanDeadFollow(fTarget)) return false; diff --git a/src/strategy/actions/FollowActions.h b/src/strategy/actions/FollowActions.h index 5468e34131..9331bbf556 100644 --- a/src/strategy/actions/FollowActions.h +++ b/src/strategy/actions/FollowActions.h @@ -20,10 +20,10 @@ class FollowAction : public MovementAction bool CanDeadFollow(Unit* target); }; -class FleeToMasterAction : public FollowAction +class FleeToGroupLeaderAction : public FollowAction { public: - FleeToMasterAction(PlayerbotAI* botAI) : FollowAction(botAI, "flee to master") {} + FleeToGroupLeaderAction(PlayerbotAI* botAI) : FollowAction(botAI, "flee to group leader") {} bool Execute(Event event) override; bool isUseful() override; diff --git a/src/strategy/actions/InviteToGroupAction.cpp b/src/strategy/actions/InviteToGroupAction.cpp index 4d0b4df7b9..bec515fefb 100644 --- a/src/strategy/actions/InviteToGroupAction.cpp +++ b/src/strategy/actions/InviteToGroupAction.cpp @@ -141,7 +141,7 @@ bool InviteNearbyToGroupAction::isUseful() if (group->isRaidGroup() && group->IsFull()) return false; - if (botAI->GetGroupMaster() != bot) + if (botAI->GetGroupLeader() != bot) return false; uint32 memberCount = group->GetMembersCount(); diff --git a/src/strategy/actions/LeaveGroupAction.cpp b/src/strategy/actions/LeaveGroupAction.cpp index 039c476bf5..a279c9426c 100644 --- a/src/strategy/actions/LeaveGroupAction.cpp +++ b/src/strategy/actions/LeaveGroupAction.cpp @@ -109,22 +109,22 @@ bool LeaveFarAwayAction::isUseful() if (!bot->GetGroup()) return false; - Player* master = botAI->GetGroupMaster(); + Player* groupLeader = botAI->GetGroupLeader(); Player* trueMaster = botAI->GetMaster(); - if (!master || (bot == master && !botAI->IsRealPlayer())) + if (!groupLeader || (bot == groupLeader && !botAI->IsRealPlayer())) return false; - PlayerbotAI* masterBotAI = nullptr; - if (master) - masterBotAI = GET_PLAYERBOT_AI(master); - if (master && !masterBotAI) + PlayerbotAI* groupLeaderBotAI = nullptr; + if (groupLeader) + groupLeaderBotAI = GET_PLAYERBOT_AI(groupLeader); + if (groupLeader && !groupLeaderBotAI) return false; if (trueMaster && !GET_PLAYERBOT_AI(trueMaster)) return false; if (botAI->IsAlt() && - (!masterBotAI || masterBotAI->IsRealPlayer())) // Don't leave group when alt grouped with player master. + (!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer())) // Don't leave group when alt grouped with player groupLeader. return false; if (botAI->GetGrouperType() == GrouperType::SOLO) @@ -138,19 +138,19 @@ bool LeaveFarAwayAction::isUseful() if (dCount > 4 && !botAI->HasRealPlayerMaster()) return true; - if (bot->GetGuildId() == master->GetGuildId()) + if (bot->GetGuildId() == groupLeader->GetGuildId()) { - if (bot->GetLevel() > master->GetLevel() + 5) + if (bot->GetLevel() > groupLeader->GetLevel() + 5) { if (AI_VALUE(bool, "should get money")) return false; } } - if (abs(int32(master->GetLevel() - bot->GetLevel())) > 4) + if (abs(int32(groupLeader->GetLevel() - bot->GetLevel())) > 4) return true; - if (bot->GetMapId() != master->GetMapId() || bot->GetDistance2d(master) >= 2 * sPlayerbotAIConfig->rpgDistance) + if (bot->GetMapId() != groupLeader->GetMapId() || bot->GetDistance2d(groupLeader) >= 2 * sPlayerbotAIConfig->rpgDistance) { return true; } diff --git a/src/strategy/actions/MoveToTravelTargetAction.cpp b/src/strategy/actions/MoveToTravelTargetAction.cpp index a4dec2caac..ed60339b82 100644 --- a/src/strategy/actions/MoveToTravelTargetAction.cpp +++ b/src/strategy/actions/MoveToTravelTargetAction.cpp @@ -18,7 +18,7 @@ bool MoveToTravelTargetAction::Execute(Event event) WorldLocation location = *target->getPosition(); Group* group = bot->GetGroup(); - if (group && !urand(0, 1) && bot == botAI->GetGroupMaster() && !bot->IsInCombat()) + if (group && !urand(0, 1) && bot == botAI->GetGroupLeader() && !bot->IsInCombat()) { for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { diff --git a/src/strategy/actions/MovementActions.cpp b/src/strategy/actions/MovementActions.cpp index 7078557d1e..f66638a7a1 100644 --- a/src/strategy/actions/MovementActions.cpp +++ b/src/strategy/actions/MovementActions.cpp @@ -1102,7 +1102,7 @@ void MovementAction::UpdateMovementState() // { // if (Unit* pTarget = sServerFacade->GetChaseTarget(bot)) // { - // if (pTarget != botAI->GetGroupMaster()) + // if (pTarget != botAI->GetGroupLeader()) // return; // if (!bot->IsWithinMeleeRange(pTarget)) @@ -2663,7 +2663,7 @@ bool DisperseSetAction::Execute(Event event) return true; } -bool RunAwayAction::Execute(Event event) { return Flee(AI_VALUE(Unit*, "master target")); } +bool RunAwayAction::Execute(Event event) { return Flee(AI_VALUE(Unit*, "group leader")); } bool MoveToLootAction::Execute(Event event) { diff --git a/src/strategy/actions/PetsAction.cpp b/src/strategy/actions/PetsAction.cpp index 303ac63632..5887d0ee3d 100644 --- a/src/strategy/actions/PetsAction.cpp +++ b/src/strategy/actions/PetsAction.cpp @@ -25,7 +25,9 @@ bool PetsAction::Execute(Event event) if (param.empty()) { // If no parameter is provided, show usage instructions and return. - botAI->TellError("Usage: pet "); + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_usage_error", "Usage: pet ", {}); + botAI->TellError(text); return false; } @@ -52,7 +54,9 @@ bool PetsAction::Execute(Event event) // If no pets or guardians are found, notify and return. if (targets.empty()) { - botAI->TellError("You have no pet or guardian pet."); + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_no_pet_error", "You have no pet or guardian pet.", {}); + botAI->TellError(text); return false; } @@ -63,42 +67,54 @@ bool PetsAction::Execute(Event event) if (param == "aggressive") { react = REACT_AGGRESSIVE; - stanceText = "aggressive"; + stanceText = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_aggressive", "aggressive", {}); } else if (param == "defensive") { react = REACT_DEFENSIVE; - stanceText = "defensive"; + stanceText = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_defensive", "defensive", {}); } else if (param == "passive") { react = REACT_PASSIVE; - stanceText = "passive"; + stanceText = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_passive", "passive", {}); } // The "stance" command simply reports the current stance of each pet/guardian. else if (param == "stance") { for (Creature* target : targets) { - std::string type = target->IsPet() ? "pet" : "guardian"; + std::string type = target->IsPet() ? + sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_pet", "pet", {}) : + sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_guardian", "guardian", {}); std::string name = target->GetName(); std::string stance; switch (target->GetReactState()) { case REACT_AGGRESSIVE: - stance = "aggressive"; + stance = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_aggressive", "aggressive", {}); break; case REACT_DEFENSIVE: - stance = "defensive"; + stance = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_defensive", "defensive", {}); break; case REACT_PASSIVE: - stance = "passive"; + stance = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_passive", "passive", {}); break; default: - stance = "unknown"; + stance = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_unknown", "unknown", {}); break; } - botAI->TellMaster("Current stance of " + type + " \"" + name + "\": " + stance + "."); + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_report", "Current stance of %type \"%name\": %stance.", + {{"type", type}, {"name", name}, {"stance", stance}}); + botAI->TellMaster(text); } return true; } @@ -121,17 +137,31 @@ bool PetsAction::Execute(Event event) // If no valid target is selected, show an error and return. if (!targetUnit) { - botAI->TellError("No valid target selected by master."); + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_no_target_error", "No valid target selected by master.", {}); + botAI->TellError(text); return false; } if (!targetUnit->IsAlive()) { - botAI->TellError("Target is not alive."); + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_target_dead_error", "Target is not alive.", {}); + botAI->TellError(text); return false; } if (!bot->IsValidAttackTarget(targetUnit)) { - botAI->TellError("Target is not a valid attack target for the bot."); + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_invalid_target_error", "Target is not a valid attack target for the bot.", {}); + botAI->TellError(text); + return false; + } + if (sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) + && (targetUnit->IsPlayer() || targetUnit->IsPet())) + { + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_pvp_prohibited_error", "I cannot command my pet to attack players in PvP prohibited areas.", {}); + botAI->TellError(text); return false; } if (sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId(), bot) @@ -189,9 +219,17 @@ bool PetsAction::Execute(Event event) } // Inform the master if the command succeeded or failed. if (didAttack && sPlayerbotAIConfig->petChatCommandDebug == 1) - botAI->TellMaster("Pet commanded to attack your target."); + { + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_attack_success", "Pet commanded to attack your target.", {}); + botAI->TellMaster(text); + } else if (!didAttack) - botAI->TellError("Pet did not attack. (Already attacking or unable to attack target)"); + { + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_attack_failed", "Pet did not attack. (Already attacking or unable to attack target)", {}); + botAI->TellError(text); + } return didAttack; } // The "follow" command makes all pets/guardians follow the bot. @@ -199,7 +237,11 @@ bool PetsAction::Execute(Event event) { botAI->PetFollow(); if (sPlayerbotAIConfig->petChatCommandDebug == 1) - botAI->TellMaster("Pet commanded to follow."); + { + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_follow_success", "Pet commanded to follow.", {}); + botAI->TellMaster(text); + } return true; } // The "stay" command causes all pets/guardians to stop and stay in place. @@ -236,14 +278,20 @@ bool PetsAction::Execute(Event event) } } if (sPlayerbotAIConfig->petChatCommandDebug == 1) - botAI->TellMaster("Pet commanded to stay."); + { + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stay_success", "Pet commanded to stay.", {}); + botAI->TellMaster(text); + } return true; } // Unknown command: show usage instructions and return. else { - botAI->TellError("Unknown pet command: " + param + - ". Use: pet "); + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_unknown_command_error", "Unknown pet command: %param. Use: pet ", + {{"param", param}}); + botAI->TellError(text); return false; } @@ -258,7 +306,12 @@ bool PetsAction::Execute(Event event) // Inform the master of the new stance if debug is enabled. if (sPlayerbotAIConfig->petChatCommandDebug == 1) - botAI->TellMaster("Pet stance set to " + stanceText + "."); + { + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_set_success", "Pet stance set to %stance.", + {{"stance", stanceText}}); + botAI->TellMaster(text); + } return true; -} +} \ No newline at end of file diff --git a/src/strategy/actions/RandomBotUpdateAction.cpp b/src/strategy/actions/RandomBotUpdateAction.cpp index 1536cd6b92..61a8360e59 100644 --- a/src/strategy/actions/RandomBotUpdateAction.cpp +++ b/src/strategy/actions/RandomBotUpdateAction.cpp @@ -13,10 +13,10 @@ bool RandomBotUpdateAction::Execute(Event event) if (!sRandomPlayerbotMgr->IsRandomBot(bot)) return false; - if (bot->GetGroup() && botAI->GetGroupMaster()) + if (bot->GetGroup() && botAI->GetGroupLeader()) { - PlayerbotAI* groupMasterBotAI = GET_PLAYERBOT_AI(botAI->GetGroupMaster()); - if (!groupMasterBotAI || groupMasterBotAI->IsRealPlayer()) + PlayerbotAI* groupLeaderBotAI = GET_PLAYERBOT_AI(botAI->GetGroupLeader()); + if (!groupLeaderBotAI || groupLeaderBotAI->IsRealPlayer()) return true; } diff --git a/src/strategy/actions/ReleaseSpiritAction.cpp b/src/strategy/actions/ReleaseSpiritAction.cpp index b06fc89263..17fcc42a0b 100644 --- a/src/strategy/actions/ReleaseSpiritAction.cpp +++ b/src/strategy/actions/ReleaseSpiritAction.cpp @@ -168,15 +168,15 @@ bool AutoReleaseSpiritAction::ShouldAutoRelease() const if (!bot->GetGroup()) return true; - Player* groupMaster = botAI->GetGroupMaster(); - if (!groupMaster || groupMaster == bot) + Player* groupLeader = botAI->GetGroupLeader(); + if (!groupLeader || groupLeader == bot) return true; if (!botAI->HasActivePlayerMaster()) return true; if (botAI->HasActivePlayerMaster() && - groupMaster->GetMapId() == bot->GetMapId() && + groupLeader->GetMapId() == bot->GetMapId() && bot->GetMap() && (bot->GetMap()->IsRaid() || bot->GetMap()->IsDungeon())) { @@ -184,7 +184,7 @@ bool AutoReleaseSpiritAction::ShouldAutoRelease() const } return sServerFacade->IsDistanceGreaterThan( - AI_VALUE2(float, "distance", "master target"), + AI_VALUE2(float, "distance", "group leader"), sPlayerbotAIConfig->sightDistance); } diff --git a/src/strategy/actions/ResetInstancesAction.cpp b/src/strategy/actions/ResetInstancesAction.cpp index 1c0a6f2480..cce5eef1e3 100644 --- a/src/strategy/actions/ResetInstancesAction.cpp +++ b/src/strategy/actions/ResetInstancesAction.cpp @@ -16,4 +16,4 @@ bool ResetInstancesAction::Execute(Event event) return true; } -bool ResetInstancesAction::isUseful() { return botAI->GetGroupMaster() == bot; }; +bool ResetInstancesAction::isUseful() { return botAI->GetGroupLeader() == bot; }; diff --git a/src/strategy/actions/ReviveFromCorpseAction.cpp b/src/strategy/actions/ReviveFromCorpseAction.cpp index 74237670df..ce0b1fa05d 100644 --- a/src/strategy/actions/ReviveFromCorpseAction.cpp +++ b/src/strategy/actions/ReviveFromCorpseAction.cpp @@ -17,14 +17,14 @@ bool ReviveFromCorpseAction::Execute(Event event) { - Player* master = botAI->GetGroupMaster(); + Player* groupLeader = botAI->GetGroupLeader(); Corpse* corpse = bot->GetCorpse(); - // follow master when master revives + // follow group Leader when group Leader revives WorldPacket& p = event.getPacket(); - if (!p.empty() && p.GetOpcode() == CMSG_RECLAIM_CORPSE && master && !corpse && bot->IsAlive()) + if (!p.empty() && p.GetOpcode() == CMSG_RECLAIM_CORPSE && groupLeader && !corpse && bot->IsAlive()) { - if (sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"), + if (sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"), sPlayerbotAIConfig->farDistance)) { if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT)) @@ -43,10 +43,10 @@ bool ReviveFromCorpseAction::Execute(Event event) // time(nullptr)) // return false; - if (master) + if (groupLeader) { - if (!GET_PLAYERBOT_AI(master) && master->isDead() && master->GetCorpse() && - sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"), + if (!GET_PLAYERBOT_AI(groupLeader) && groupLeader->isDead() && groupLeader->GetCorpse() && + sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"), sPlayerbotAIConfig->farDistance)) return false; } @@ -79,15 +79,15 @@ bool FindCorpseAction::Execute(Event event) if (bot->InBattleground()) return false; - Player* master = botAI->GetGroupMaster(); + Player* groupLeader = botAI->GetGroupLeader(); Corpse* corpse = bot->GetCorpse(); if (!corpse) return false; - // if (master) + // if (groupLeader) // { - // if (!GET_PLAYERBOT_AI(master) && - // sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"), + // if (!GET_PLAYERBOT_AI(groupLeader) && + // sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"), // sPlayerbotAIConfig->farDistance)) return false; // } @@ -110,20 +110,20 @@ bool FindCorpseAction::Execute(Event event) WorldPosition botPos(bot); WorldPosition corpsePos(corpse); WorldPosition moveToPos = corpsePos; - WorldPosition masterPos(master); + WorldPosition leaderPos(groupLeader); float reclaimDist = CORPSE_RECLAIM_RADIUS - 5.0f; float corpseDist = botPos.distance(corpsePos); int64 deadTime = time(nullptr) - corpse->GetGhostTime(); - bool moveToMaster = master && master != bot && masterPos.fDist(corpsePos) < reclaimDist; + bool moveToLeader = groupLeader && groupLeader != bot && leaderPos.fDist(corpsePos) < reclaimDist; // Should we ressurect? If so, return false. if (corpseDist < reclaimDist) { - if (moveToMaster) // We are near master. + if (moveToLeader) // We are near group leader. { - if (botPos.fDist(masterPos) < sPlayerbotAIConfig->spellDistance) + if (botPos.fDist(leaderPos) < sPlayerbotAIConfig->spellDistance) return false; } else if (deadTime > 8 * MINUTE) // We have walked too long already. @@ -140,8 +140,8 @@ bool FindCorpseAction::Execute(Event event) // If we are getting close move to a save ressurrection spot instead of just the corpse. if (corpseDist < sPlayerbotAIConfig->reactDistance) { - if (moveToMaster) - moveToPos = masterPos; + if (moveToLeader) + moveToPos = leaderPos; else { FleeManager manager(bot, reclaimDist, 0.0, urand(0, 1), moveToPos); @@ -215,12 +215,12 @@ GraveyardStruct const* SpiritHealerAction::GetGrave(bool startZone) if (!startZone && ClosestGrave) return ClosestGrave; - if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT) && botAI->GetGroupMaster() && botAI->GetGroupMaster() != bot) + if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT) && botAI->GetGroupLeader() && botAI->GetGroupLeader() != bot) { - Player* master = botAI->GetGroupMaster(); - if (master && master != bot) + Player* groupLeader = botAI->GetGroupLeader(); + if (groupLeader && groupLeader != bot) { - ClosestGrave = sGraveyard->GetClosestGraveyard(master, bot->GetTeamId()); + ClosestGrave = sGraveyard->GetClosestGraveyard(groupLeader, bot->GetTeamId()); if (ClosestGrave) return ClosestGrave; diff --git a/src/strategy/actions/RewardAction.cpp b/src/strategy/actions/RewardAction.cpp index 1022166d0f..8fe1c6ff72 100644 --- a/src/strategy/actions/RewardAction.cpp +++ b/src/strategy/actions/RewardAction.cpp @@ -35,8 +35,8 @@ bool RewardAction::Execute(Event event) return true; } - Unit* mtar = AI_VALUE(Unit*, "master target"); - if (mtar && Reward(itemId, mtar)) + Unit* groupLeaderUnit = AI_VALUE(Unit*, "group leader"); + if (groupLeaderUnit && Reward(itemId, groupLeaderUnit)) return true; botAI->TellError("Cannot talk to quest giver"); diff --git a/src/strategy/actions/RpgSubActions.cpp b/src/strategy/actions/RpgSubActions.cpp index 784f6bbb20..aa4269fa92 100644 --- a/src/strategy/actions/RpgSubActions.cpp +++ b/src/strategy/actions/RpgSubActions.cpp @@ -76,7 +76,7 @@ void RpgHelper::setFacing(GuidPosition guidPosition) void RpgHelper::setDelay(bool waitForGroup) { - if (!botAI->HasRealPlayerMaster() || (waitForGroup && botAI->GetGroupMaster() == bot && bot->GetGroup())) + if (!botAI->HasRealPlayerMaster() || (waitForGroup && botAI->GetGroupLeader() == bot && bot->GetGroup())) botAI->SetNextCheckDelay(sPlayerbotAIConfig->rpgDelay); else botAI->SetNextCheckDelay(sPlayerbotAIConfig->rpgDelay / 5); diff --git a/src/strategy/actions/SecurityCheckAction.cpp b/src/strategy/actions/SecurityCheckAction.cpp index 1320eb04a6..c47a6e52cb 100644 --- a/src/strategy/actions/SecurityCheckAction.cpp +++ b/src/strategy/actions/SecurityCheckAction.cpp @@ -22,8 +22,8 @@ bool SecurityCheckAction::Execute(Event event) ItemQualities threshold = group->GetLootThreshold(); if (method == MASTER_LOOT || method == FREE_FOR_ALL || threshold > ITEM_QUALITY_UNCOMMON) { - if ((botAI->GetGroupMaster()->GetSession()->GetSecurity() == SEC_PLAYER) && - (!bot->GetGuildId() || bot->GetGuildId() != botAI->GetGroupMaster()->GetGuildId())) + if ((botAI->GetGroupLeader()->GetSession()->GetSecurity() == SEC_PLAYER) && + (!bot->GetGuildId() || bot->GetGuildId() != botAI->GetGroupLeader()->GetGuildId())) { botAI->TellError("I will play with this loot type only if I'm in your guild :/"); botAI->ChangeStrategy("+passive,+stay", BOT_STATE_NON_COMBAT); diff --git a/src/strategy/actions/TellMasterAction.cpp b/src/strategy/actions/TellMasterAction.cpp index 701f93d773..4b8d96d561 100644 --- a/src/strategy/actions/TellMasterAction.cpp +++ b/src/strategy/actions/TellMasterAction.cpp @@ -22,7 +22,7 @@ bool OutOfReactRangeAction::Execute(Event event) bool OutOfReactRangeAction::isUseful() { - bool canFollow = Follow(AI_VALUE(Unit*, "master target")); + bool canFollow = Follow(AI_VALUE(Unit*, "group leader")); if (!canFollow) { return false; diff --git a/src/strategy/actions/TravelAction.cpp b/src/strategy/actions/TravelAction.cpp index 5804fd7d24..f99f8b29d3 100644 --- a/src/strategy/actions/TravelAction.cpp +++ b/src/strategy/actions/TravelAction.cpp @@ -64,7 +64,7 @@ bool MoveToDarkPortalAction::Execute(Event event) { if (bot->GetGroup()) if (bot->GetGroup()->GetLeaderGUID() != bot->GetGUID() && - !GET_PLAYERBOT_AI(GET_PLAYERBOT_AI(bot)->GetGroupMaster())) + !GET_PLAYERBOT_AI(GET_PLAYERBOT_AI(bot)->GetGroupLeader())) return false; if (bot->GetLevel() > 57) diff --git a/src/strategy/generic/GroupStrategy.cpp b/src/strategy/generic/GroupStrategy.cpp index 9406e8409d..9cfba6e2fc 100644 --- a/src/strategy/generic/GroupStrategy.cpp +++ b/src/strategy/generic/GroupStrategy.cpp @@ -11,7 +11,6 @@ void GroupStrategy::InitTriggers(std::vector& triggers) { triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("invite nearby", 4.0f), nullptr))); triggers.push_back(new TriggerNode("random", NextAction::array(0, new NextAction("invite guild", 4.0f), nullptr))); - triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("leave far away", 4.0f), nullptr))); - triggers.push_back( - new TriggerNode("seldom", NextAction::array(0, new NextAction("reset instances", 1.0f), nullptr))); + triggers.push_back(new TriggerNode("random", NextAction::array(0, new NextAction("leave far away", 4.0f), nullptr))); + triggers.push_back(new TriggerNode("seldom", NextAction::array(0, new NextAction("reset instances", 1.0f), nullptr))); } diff --git a/src/strategy/raids/RaidStrategyContext.h b/src/strategy/raids/RaidStrategyContext.h index f16fa72546..ee2bac3270 100644 --- a/src/strategy/raids/RaidStrategyContext.h +++ b/src/strategy/raids/RaidStrategyContext.h @@ -22,7 +22,7 @@ class RaidStrategyContext : public NamedObjectContext RaidStrategyContext() : NamedObjectContext(false, true) { creators["aq20"] = &RaidStrategyContext::aq20; - creators["mc"] = &RaidStrategyContext::mc; + creators["moltencore"] = &RaidStrategyContext::moltencore; creators["bwl"] = &RaidStrategyContext::bwl; creators["karazhan"] = &RaidStrategyContext::karazhan; creators["magtheridon"] = &RaidStrategyContext::magtheridon; @@ -38,7 +38,7 @@ class RaidStrategyContext : public NamedObjectContext private: static Strategy* aq20(PlayerbotAI* botAI) { return new RaidAq20Strategy(botAI); } - static Strategy* mc(PlayerbotAI* botAI) { return new RaidMcStrategy(botAI); } + static Strategy* moltencore(PlayerbotAI* botAI) { return new RaidMcStrategy(botAI); } static Strategy* bwl(PlayerbotAI* botAI) { return new RaidBwlStrategy(botAI); } static Strategy* karazhan(PlayerbotAI* botAI) { return new RaidKarazhanStrategy(botAI); } static Strategy* magtheridon(PlayerbotAI* botAI) { return new RaidMagtheridonStrategy(botAI); } diff --git a/src/strategy/raids/moltencore/RaidMcActionContext.h b/src/strategy/raids/moltencore/RaidMcActionContext.h index 45e6e056cc..79a4a95a85 100644 --- a/src/strategy/raids/moltencore/RaidMcActionContext.h +++ b/src/strategy/raids/moltencore/RaidMcActionContext.h @@ -10,13 +10,39 @@ class RaidMcActionContext : public NamedObjectContext public: RaidMcActionContext() { - creators["mc check should move from group"] = &RaidMcActionContext::check_should_move_from_group; + creators["mc lucifron shadow resistance"] = &RaidMcActionContext::lucifron_shadow_resistance; + creators["mc magmadar fire resistance"] = &RaidMcActionContext::magmadar_fire_resistance; + creators["mc gehennas shadow resistance"] = &RaidMcActionContext::gehennas_shadow_resistance; + creators["mc garr fire resistance"] = &RaidMcActionContext::garr_fire_resistance; + creators["mc baron geddon fire resistance"] = &RaidMcActionContext::baron_geddon_fire_resistance; + creators["mc move from group"] = &RaidMcActionContext::check_should_move_from_group; creators["mc move from baron geddon"] = &RaidMcActionContext::move_from_baron_geddon; + creators["mc shazzrah move away"] = &RaidMcActionContext::shazzrah_move_away; + creators["mc sulfuron harbinger fire resistance"] = &RaidMcActionContext::sulfuron_harbinger_fire_resistance; + creators["mc golemagg fire resistance"] = &RaidMcActionContext::golemagg_fire_resistance; + creators["mc golemagg mark boss"] = &RaidMcActionContext::golemagg_mark_boss; + creators["mc golemagg main tank attack golemagg"] = &RaidMcActionContext::golemagg_main_tank_attack_golemagg; + creators["mc golemagg assist tank attack core rager"] = &RaidMcActionContext::golemagg_assist_tank_attack_core_rager; + creators["mc majordomo shadow resistance"] = &RaidMcActionContext::majordomo_shadow_resistance; + creators["mc ragnaros fire resistance"] = &RaidMcActionContext::ragnaros_fire_resistance; } private: - static Action* check_should_move_from_group(PlayerbotAI* ai) { return new McCheckShouldMoveFromGroupAction(ai); } - static Action* move_from_baron_geddon(PlayerbotAI* ai) { return new McMoveFromBaronGeddonAction(ai); } + static Action* lucifron_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceAction(botAI, "lucifron"); } + static Action* magmadar_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "magmadar"); } + static Action* gehennas_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceAction(botAI, "gehennas"); } + static Action* garr_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "garr"); } + static Action* baron_geddon_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "baron geddon"); } + static Action* check_should_move_from_group(PlayerbotAI* botAI) { return new McMoveFromGroupAction(botAI); } + static Action* move_from_baron_geddon(PlayerbotAI* botAI) { return new McMoveFromBaronGeddonAction(botAI); } + static Action* shazzrah_move_away(PlayerbotAI* botAI) { return new McShazzrahMoveAwayAction(botAI); } + static Action* sulfuron_harbinger_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "sulfuron harbinger"); } + static Action* golemagg_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "golemagg the incinerator"); } + static Action* golemagg_mark_boss(PlayerbotAI* botAI) { return new McGolemaggMarkBossAction(botAI); } + static Action* golemagg_main_tank_attack_golemagg(PlayerbotAI* botAI) { return new McGolemaggMainTankAttackGolemaggAction(botAI); } + static Action* golemagg_assist_tank_attack_core_rager(PlayerbotAI* botAI) { return new McGolemaggAssistTankAttackCoreRagerAction(botAI); } + static Action* majordomo_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceAction(botAI, "majordomo executus"); } + static Action* ragnaros_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "ragnaros"); } }; #endif diff --git a/src/strategy/raids/moltencore/RaidMcActions.cpp b/src/strategy/raids/moltencore/RaidMcActions.cpp index b82a073c40..b18c8b8534 100644 --- a/src/strategy/raids/moltencore/RaidMcActions.cpp +++ b/src/strategy/raids/moltencore/RaidMcActions.cpp @@ -1,43 +1,215 @@ #include "RaidMcActions.h" #include "Playerbots.h" +#include "RtiTargetValue.h" +#include "RaidMcTriggers.h" +#include "RaidMcHelpers.h" -bool McCheckShouldMoveFromGroupAction::Execute(Event event) +static constexpr float LIVING_BOMB_DISTANCE = 20.0f; +static constexpr float INFERNO_DISTANCE = 20.0f; + +// don't get hit by Arcane Explosion but still be in casting range +static constexpr float ARCANE_EXPLOSION_DISTANCE = 26.0f; + +// dedicated tank positions; prevents assist tanks from positioning Core Ragers on steep walls on pull +static const Position GOLEMAGG_TANK_POSITION{795.7308, -994.8848, -207.18661}; +static const Position CORE_RAGER_TANK_POSITION{846.6453, -1019.0639, -198.9819}; + +static constexpr float GOLEMAGGS_TRUST_DISTANCE = 30.0f; +static constexpr float CORE_RAGER_STEP_DISTANCE = 5.0f; + +using namespace MoltenCoreHelpers; + +bool McMoveFromGroupAction::Execute(Event event) +{ + return MoveFromGroup(LIVING_BOMB_DISTANCE); +} + +bool McMoveFromBaronGeddonAction::Execute(Event event) +{ + if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon")) + { + float distToTravel = INFERNO_DISTANCE - bot->GetDistance2d(boss); + if (distToTravel > 0) + { + // Stop current spell first + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + + return MoveAway(boss, distToTravel); + } + } + return false; +} + +bool McShazzrahMoveAwayAction::Execute(Event event) +{ + if (Unit* boss = AI_VALUE2(Unit*, "find target", "shazzrah")) + { + float distToTravel = ARCANE_EXPLOSION_DISTANCE - bot->GetDistance2d(boss); + if (distToTravel > 0) + return MoveAway(boss, distToTravel); + } + return false; +} + +bool McGolemaggMarkBossAction::Execute(Event event) { - if (bot->HasAura(20475)) // barron geddon's living bomb + if (Unit* boss = AI_VALUE2(Unit*, "find target", "golemagg the incinerator")) { - if (!botAI->HasStrategy("move from group", BotState::BOT_STATE_COMBAT)) + if (Group* group = bot->GetGroup()) { - // add/remove from both for now as it will make it more obvious to - // player if this strat remains on after fight somehow - botAI->ChangeStrategy("+move from group", BOT_STATE_NON_COMBAT); - botAI->ChangeStrategy("+move from group", BOT_STATE_COMBAT); - return true; + ObjectGuid currentSkullGuid = group->GetTargetIcon(RtiTargetValue::skullIndex); + if (currentSkullGuid.IsEmpty() || currentSkullGuid != boss->GetGUID()) + { + group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), boss->GetGUID()); + return true; + } } } - else if (botAI->HasStrategy("move from group", BotState::BOT_STATE_COMBAT)) + return false; +} + +bool McGolemaggTankAction::MoveUnitToPosition(Unit* target, const Position& tankPosition, float maxDistance, + float stepDistance) +{ + if (bot->GetVictim() != target) + return Attack(target); + if (target->GetVictim() == bot) { - // add/remove from both for now as it will make it more obvious to - // player if this strat remains on after fight somehow - botAI->ChangeStrategy("-move from group", BOT_STATE_NON_COMBAT); - botAI->ChangeStrategy("-move from group", BOT_STATE_COMBAT); + float distanceToTankPosition = bot->GetExactDist2d(tankPosition.GetPositionX(), tankPosition.GetPositionY()); + if (distanceToTankPosition > maxDistance) + { + float dX = tankPosition.GetPositionX() - bot->GetPositionX(); + float dY = tankPosition.GetPositionY() - bot->GetPositionY(); + float dist = sqrt(dX * dX + dY * dY); + float moveX = bot->GetPositionX() + (dX / dist) * stepDistance; + float moveY = bot->GetPositionY() + (dY / dist) * stepDistance; + return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, + true); + } + } + else if (botAI->DoSpecificAction("taunt spell", Event(), true)) return true; + return false; +} + +bool McGolemaggTankAction::FindCoreRagers(Unit*& coreRager1, Unit*& coreRager2) const +{ + coreRager1 = coreRager2 = nullptr; + for (auto const& target : AI_VALUE(GuidVector, "possible targets no los")) + { + Unit* unit = botAI->GetUnit(target); + if (unit && unit->IsAlive() && unit->GetEntry() == NPC_CORE_RAGER) + { + if (coreRager1 == nullptr) + coreRager1 = unit; + else if (coreRager2 == nullptr) + { + coreRager2 = unit; + break; // There should be no third Core Rager. + } + } + } + return coreRager1 != nullptr && coreRager2 != nullptr; +} + +bool McGolemaggMainTankAttackGolemaggAction::Execute(Event event) +{ + // At this point, we know we are not the last living tank in the group. + if (Unit* boss = AI_VALUE2(Unit*, "find target", "golemagg the incinerator")) + { + Unit* coreRager1; + Unit* coreRager2; + if (!FindCoreRagers(coreRager1, coreRager2)) + return false; // safety check + + // We only need to move if the Core Ragers still have Golemagg's Trust + if (coreRager1->HasAura(SPELL_GOLEMAGGS_TRUST) || coreRager2->HasAura(SPELL_GOLEMAGGS_TRUST)) + return MoveUnitToPosition(boss, GOLEMAGG_TANK_POSITION, boss->GetCombatReach()); } return false; } -bool McMoveFromBaronGeddonAction::Execute(Event event) +bool McGolemaggAssistTankAttackCoreRagerAction::Execute(Event event) { - const float radius = 25.0f; // more than should be needed but bots keep trying to run back in - if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon")) + Unit* boss = AI_VALUE2(Unit*, "find target", "golemagg the incinerator"); + if (!boss) + return false; + + // Step 0: Filter additional assist tanks. We only need 2. + bool isFirstAssistTank = PlayerbotAI::IsAssistTankOfIndex(bot, 0, true); + bool isSecondAssistTank = PlayerbotAI::IsAssistTankOfIndex(bot, 1, true); + if (!isFirstAssistTank && !isSecondAssistTank) + return Attack(boss); + + // Step 1: Find both Core Ragers + Unit* coreRager1; + Unit* coreRager2; + if (!FindCoreRagers(coreRager1, coreRager2)) + return false; // safety check + + // Step 2: Assign Core Rager to bot + Unit* myCoreRager = nullptr; + Unit* otherCoreRager = nullptr; + if (isFirstAssistTank) { - long distToTravel = radius - bot->GetDistance(boss); - if (distToTravel > 0) + myCoreRager = coreRager1; + otherCoreRager = coreRager2; + } + else // isSecondAssistTank is always true here + { + myCoreRager = coreRager2; + otherCoreRager = coreRager1; + } + + // Step 3: Select the right target + if (myCoreRager->GetVictim() != bot) + { + // Step 3.1: My Core Rager isn't attacking me. Attack until it does. + if (bot->GetVictim() != myCoreRager) + return Attack(myCoreRager); + return botAI->DoSpecificAction("taunt spell", event, true); + } + + Unit* otherCoreRagerVictim = otherCoreRager->GetVictim(); + if (otherCoreRagerVictim) // Core Rager victim can be NULL + { + // Step 3.2: Check if the other Core Rager isn't attacking its assist tank. + Player* otherCoreRagerPlayerVictim = otherCoreRagerVictim->ToPlayer(); + if (otherCoreRagerPlayerVictim && + !PlayerbotAI::IsAssistTankOfIndex(otherCoreRagerPlayerVictim, 0, true) && + !PlayerbotAI::IsAssistTankOfIndex(otherCoreRagerPlayerVictim, 1, true)) { - // float angle = bot->GetAngle(boss) + M_PI; - // return Move(angle, distToTravel); - return MoveAway(boss, distToTravel); + // Assume we are the only assist tank or the other assist tank is dead => pick up other Core Rager! + if (bot->GetVictim() != otherCoreRager) + return Attack(otherCoreRager); + return botAI->DoSpecificAction("taunt spell", event, true); } } + + if (bot->GetVictim() != myCoreRager) + return Attack(myCoreRager); // Step 3.3: Attack our Core Rager in case we previously switched in 3.2. + + // Step 4: Prevent Golemagg's Trust on Core Ragers + if (myCoreRager->HasAura(SPELL_GOLEMAGGS_TRUST) || + (otherCoreRagerVictim == bot && otherCoreRager->HasAura(SPELL_GOLEMAGGS_TRUST))) + { + // Step 4.1: Move Core Ragers to dedicated tank position (only if Golemagg is far enough away from said position) + float bossDistanceToCoreRagerTankPosition = boss->GetExactDist2d( + CORE_RAGER_TANK_POSITION.GetPositionX(), CORE_RAGER_TANK_POSITION.GetPositionY()); + if (bossDistanceToCoreRagerTankPosition > GOLEMAGGS_TRUST_DISTANCE) + { + float distanceToTankPosition = bot->GetExactDist2d(CORE_RAGER_TANK_POSITION.GetPositionX(), + CORE_RAGER_TANK_POSITION.GetPositionY()); + if (distanceToTankPosition > CORE_RAGER_STEP_DISTANCE) + return MoveUnitToPosition(myCoreRager, CORE_RAGER_TANK_POSITION, CORE_RAGER_STEP_DISTANCE); + } + + // Step 4.2: if boss is too close to tank position, or we are already there, move away from Golemagg to try to out-range Golemagg's Trust + return MoveAway(boss, CORE_RAGER_STEP_DISTANCE, true); + } + return false; } diff --git a/src/strategy/raids/moltencore/RaidMcActions.h b/src/strategy/raids/moltencore/RaidMcActions.h index 6ff18513ff..680b311d3c 100644 --- a/src/strategy/raids/moltencore/RaidMcActions.h +++ b/src/strategy/raids/moltencore/RaidMcActions.h @@ -1,15 +1,16 @@ #ifndef _PLAYERBOT_RAIDMCACTIONS_H #define _PLAYERBOT_RAIDMCACTIONS_H +#include "AttackAction.h" #include "MovementActions.h" #include "PlayerbotAI.h" #include "Playerbots.h" -class McCheckShouldMoveFromGroupAction : public Action +class McMoveFromGroupAction : public MovementAction { public: - McCheckShouldMoveFromGroupAction(PlayerbotAI* botAI, std::string const name = "mc check should move from group") - : Action(botAI, name) {} + McMoveFromGroupAction(PlayerbotAI* botAI, std::string const name = "mc move from group") + : MovementAction(botAI, name) {} bool Execute(Event event) override; }; @@ -21,4 +22,46 @@ class McMoveFromBaronGeddonAction : public MovementAction bool Execute(Event event) override; }; +class McShazzrahMoveAwayAction : public MovementAction +{ +public: + McShazzrahMoveAwayAction(PlayerbotAI* botAI, std::string const name = "mc shazzrah move away") + : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class McGolemaggMarkBossAction : public Action +{ +public: + McGolemaggMarkBossAction(PlayerbotAI* botAI, std::string const name = "mc golemagg mark boss") + : Action(botAI, name) {}; + bool Execute(Event event) override; +}; + +class McGolemaggTankAction : public AttackAction +{ +public: + McGolemaggTankAction(PlayerbotAI* botAI, std::string const name) + : AttackAction(botAI, name) {} +protected: + bool MoveUnitToPosition(Unit* target, const Position& tankPosition, float maxDistance, float stepDistance = 3.0f); + bool FindCoreRagers(Unit*& coreRager1, Unit*& coreRager2) const; +}; + +class McGolemaggMainTankAttackGolemaggAction : public McGolemaggTankAction +{ +public: + McGolemaggMainTankAttackGolemaggAction(PlayerbotAI* botAI, std::string const name = "mc golemagg main tank attack golemagg") + : McGolemaggTankAction(botAI, name) {}; + bool Execute(Event event) override; +}; + +class McGolemaggAssistTankAttackCoreRagerAction : public McGolemaggTankAction +{ +public: + McGolemaggAssistTankAttackCoreRagerAction(PlayerbotAI* botAI, std::string const name = "mc golemagg assist tank attack core rager") + : McGolemaggTankAction(botAI, name) {}; + bool Execute(Event event) override; +}; + #endif diff --git a/src/strategy/raids/moltencore/RaidMcHelpers.h b/src/strategy/raids/moltencore/RaidMcHelpers.h new file mode 100644 index 0000000000..5dc821246d --- /dev/null +++ b/src/strategy/raids/moltencore/RaidMcHelpers.h @@ -0,0 +1,22 @@ +#ifndef _PLAYERBOT_RAIDMCHELPERS_H +#define _PLAYERBOT_RAIDMCHELPERS_H + +namespace MoltenCoreHelpers +{ +enum MoltenCoreNPCs +{ + // Golemagg + NPC_CORE_RAGER = 11672, +}; +enum MoltenCoreSpells +{ + // Baron Geddon + SPELL_INFERNO = 19695, + SPELL_LIVING_BOMB = 20475, + + // Golemagg + SPELL_GOLEMAGGS_TRUST = 20553, +}; +} + +#endif diff --git a/src/strategy/raids/moltencore/RaidMcMultipliers.cpp b/src/strategy/raids/moltencore/RaidMcMultipliers.cpp new file mode 100644 index 0000000000..d1ee936b0d --- /dev/null +++ b/src/strategy/raids/moltencore/RaidMcMultipliers.cpp @@ -0,0 +1,117 @@ +#include "RaidMcMultipliers.h" + +#include "Playerbots.h" +#include "ChooseTargetActions.h" +#include "GenericSpellActions.h" +#include "DruidActions.h" +#include "HunterActions.h" +#include "PaladinActions.h" +#include "ShamanActions.h" +#include "WarriorActions.h" +#include "DKActions.h" +#include "RaidMcActions.h" +#include "RaidMcHelpers.h" + +using namespace MoltenCoreHelpers; + +static bool IsDpsBotWithAoeAction(Player* bot, Action* action) +{ + if (PlayerbotAI::IsDps(bot)) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action)) + return true; + + if (auto castSpellAction = dynamic_cast(action)) + { + if (castSpellAction->getThreatType() == Action::ActionThreatType::Aoe) + return true; + } + } + return false; +} + +float GarrDisableDpsAoeMultiplier::GetValue(Action* action) +{ + if (AI_VALUE2(Unit*, "find target", "garr")) + { + if (IsDpsBotWithAoeAction(bot, action)) + return 0.0f; + } + return 1.0f; +} + +static bool IsAllowedGeddonMovementAction(Action* action) +{ + if (dynamic_cast(action) && + !dynamic_cast(action) && + !dynamic_cast(action)) + return false; + + if (dynamic_cast(action)) + return false; + + return true; +} + +float BaronGeddonAbilityMultiplier::GetValue(Action* action) +{ + if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon")) + { + if (boss->HasAura(SPELL_INFERNO)) + { + if (!IsAllowedGeddonMovementAction(action)) + return 0.0f; + } + } + + // No check for Baron Geddon, because bots may have the bomb even after Geddon died. + if (bot->HasAura(SPELL_LIVING_BOMB)) + { + if (!IsAllowedGeddonMovementAction(action)) + return 0.0f; + } + + return 1.0f; +} + +static bool IsSingleLivingTankInGroup(Player* bot) +{ + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || member == bot) + continue; + if (PlayerbotAI::IsTank(member)) + return false; + } + } + return true; +} + +float GolemaggMultiplier::GetValue(Action* action) +{ + if (AI_VALUE2(Unit*, "find target", "golemagg the incinerator")) + { + if (PlayerbotAI::IsTank(bot) && IsSingleLivingTankInGroup(bot)) + { + // Only one tank => Pick up Golemagg and the two Core Ragers + if (dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + if (PlayerbotAI::IsAssistTank(bot)) + { + // The first two assist tanks manage the Core Ragers. The remaining assist tanks attack the boss. + if (dynamic_cast(action)) + return 0.0f; + } + if (IsDpsBotWithAoeAction(bot, action)) + return 0.0f; + } + return 1.0f; +} diff --git a/src/strategy/raids/moltencore/RaidMcMultipliers.h b/src/strategy/raids/moltencore/RaidMcMultipliers.h new file mode 100644 index 0000000000..56dc7a5f80 --- /dev/null +++ b/src/strategy/raids/moltencore/RaidMcMultipliers.h @@ -0,0 +1,27 @@ +#ifndef _PLAYERBOT_RAIDMCMULTIPLIERS_H +#define _PLAYERBOT_RAIDMCMULTIPLIERS_H + +#include "Multiplier.h" + +class GarrDisableDpsAoeMultiplier : public Multiplier +{ +public: + GarrDisableDpsAoeMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "garr disable dps aoe multiplier") {} + float GetValue(Action* action) override; +}; + +class BaronGeddonAbilityMultiplier : public Multiplier +{ +public: + BaronGeddonAbilityMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "baron geddon ability multiplier") {} + float GetValue(Action* action) override; +}; + +class GolemaggMultiplier : public Multiplier +{ +public: + GolemaggMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "golemagg multiplier") {} + float GetValue(Action* action) override; +}; + +#endif diff --git a/src/strategy/raids/moltencore/RaidMcStrategy.cpp b/src/strategy/raids/moltencore/RaidMcStrategy.cpp index 67793de906..c6a5042eb5 100644 --- a/src/strategy/raids/moltencore/RaidMcStrategy.cpp +++ b/src/strategy/raids/moltencore/RaidMcStrategy.cpp @@ -1,13 +1,81 @@ #include "RaidMcStrategy.h" +#include "RaidMcMultipliers.h" #include "Strategy.h" void RaidMcStrategy::InitTriggers(std::vector& triggers) { + // Lucifron + triggers.push_back( + new TriggerNode("mc lucifron shadow resistance", + NextAction::array(0, new NextAction("mc lucifron shadow resistance", ACTION_RAID), nullptr))); + + // Magmadar + // TODO: Fear ward / tremor totem, or general anti-fear strat development. Same as King Dred (Drak'Tharon) and faction commander (Nexus). + triggers.push_back( + new TriggerNode("mc magmadar fire resistance", + NextAction::array(0, new NextAction("mc magmadar fire resistance", ACTION_RAID), nullptr))); + + // Gehennas + triggers.push_back( + new TriggerNode("mc gehennas shadow resistance", + NextAction::array(0, new NextAction("mc gehennas shadow resistance", ACTION_RAID), nullptr))); + + // Garr + triggers.push_back( + new TriggerNode("mc garr fire resistance", + NextAction::array(0, new NextAction("mc garr fire resistance", ACTION_RAID), nullptr))); + + // Baron Geddon + triggers.push_back( + new TriggerNode("mc baron geddon fire resistance", + NextAction::array(0, new NextAction("mc baron geddon fire resistance", ACTION_RAID), nullptr))); triggers.push_back( new TriggerNode("mc living bomb debuff", - NextAction::array(0, new NextAction("mc check should move from group", ACTION_RAID), nullptr))); + NextAction::array(0, new NextAction("mc move from group", ACTION_RAID), nullptr))); triggers.push_back( new TriggerNode("mc baron geddon inferno", NextAction::array(0, new NextAction("mc move from baron geddon", ACTION_RAID), nullptr))); + + // Shazzrah + triggers.push_back( + new TriggerNode("mc shazzrah ranged", + NextAction::array(0, new NextAction("mc shazzrah move away", ACTION_RAID), nullptr))); + + // Sulfuron Harbinger + // Alternatively, shadow resistance is also possible. + triggers.push_back( + new TriggerNode("mc sulfuron harbinger fire resistance", + NextAction::array(0, new NextAction("mc sulfuron harbinger fire resistance", ACTION_RAID), nullptr))); + + // Golemagg the Incinerator + triggers.push_back( + new TriggerNode("mc golemagg fire resistance", + NextAction::array(0, new NextAction("mc golemagg fire resistance", ACTION_RAID), nullptr))); + triggers.push_back( + new TriggerNode("mc golemagg mark boss", + NextAction::array(0, new NextAction("mc golemagg mark boss", ACTION_RAID), nullptr))); + triggers.push_back( + new TriggerNode("mc golemagg is main tank", + NextAction::array(0, new NextAction("mc golemagg main tank attack golemagg", ACTION_RAID), nullptr))); + triggers.push_back( + new TriggerNode("mc golemagg is assist tank", + NextAction::array(0, new NextAction("mc golemagg assist tank attack core rager", ACTION_RAID), nullptr))); + + // Majordomo Executus + triggers.push_back( + new TriggerNode("mc majordomo shadow resistance", + NextAction::array(0, new NextAction("mc majordomo shadow resistance", ACTION_RAID), nullptr))); + + // Ragnaros + triggers.push_back( + new TriggerNode("mc ragnaros fire resistance", + NextAction::array(0, new NextAction("mc ragnaros fire resistance", ACTION_RAID), nullptr))); +} + +void RaidMcStrategy::InitMultipliers(std::vector& multipliers) +{ + multipliers.push_back(new GarrDisableDpsAoeMultiplier(botAI)); + multipliers.push_back(new BaronGeddonAbilityMultiplier(botAI)); + multipliers.push_back(new GolemaggMultiplier(botAI)); } diff --git a/src/strategy/raids/moltencore/RaidMcStrategy.h b/src/strategy/raids/moltencore/RaidMcStrategy.h index 82b7d8f25e..45b503e933 100644 --- a/src/strategy/raids/moltencore/RaidMcStrategy.h +++ b/src/strategy/raids/moltencore/RaidMcStrategy.h @@ -8,10 +8,10 @@ class RaidMcStrategy : public Strategy { public: - RaidMcStrategy(PlayerbotAI* ai) : Strategy(ai) {} - virtual std::string const getName() override { return "mc"; } - virtual void InitTriggers(std::vector& triggers) override; - // virtual void InitMultipliers(std::vector &multipliers) override; + RaidMcStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} + std::string const getName() override { return "moltencore"; } + void InitTriggers(std::vector& triggers) override; + void InitMultipliers(std::vector &multipliers) override; }; #endif diff --git a/src/strategy/raids/moltencore/RaidMcTriggerContext.h b/src/strategy/raids/moltencore/RaidMcTriggerContext.h index 93fe6df0f8..b74958919c 100644 --- a/src/strategy/raids/moltencore/RaidMcTriggerContext.h +++ b/src/strategy/raids/moltencore/RaidMcTriggerContext.h @@ -10,13 +10,39 @@ class RaidMcTriggerContext : public NamedObjectContext public: RaidMcTriggerContext() { + creators["mc lucifron shadow resistance"] = &RaidMcTriggerContext::lucifron_shadow_resistance; + creators["mc magmadar fire resistance"] = &RaidMcTriggerContext::magmadar_fire_resistance; + creators["mc gehennas shadow resistance"] = &RaidMcTriggerContext::gehennas_shadow_resistance; + creators["mc garr fire resistance"] = &RaidMcTriggerContext::garr_fire_resistance; + creators["mc baron geddon fire resistance"] = &RaidMcTriggerContext::baron_geddon_fire_resistance; creators["mc living bomb debuff"] = &RaidMcTriggerContext::living_bomb_debuff; creators["mc baron geddon inferno"] = &RaidMcTriggerContext::baron_geddon_inferno; + creators["mc shazzrah ranged"] = &RaidMcTriggerContext::shazzrah_ranged; + creators["mc sulfuron harbinger fire resistance"] = &RaidMcTriggerContext::sulfuron_harbinger_fire_resistance; + creators["mc golemagg fire resistance"] = &RaidMcTriggerContext::golemagg_fire_resistance; + creators["mc golemagg mark boss"] = &RaidMcTriggerContext::golemagg_mark_boss; + creators["mc golemagg is main tank"] = &RaidMcTriggerContext::golemagg_is_main_tank; + creators["mc golemagg is assist tank"] = &RaidMcTriggerContext::golemagg_is_assist_tank; + creators["mc majordomo shadow resistance"] = &RaidMcTriggerContext::majordomo_shadow_resistance; + creators["mc ragnaros fire resistance"] = &RaidMcTriggerContext::ragnaros_fire_resistance; } private: - static Trigger* living_bomb_debuff(PlayerbotAI* ai) { return new McLivingBombDebuffTrigger(ai); } - static Trigger* baron_geddon_inferno(PlayerbotAI* ai) { return new McBaronGeddonInfernoTrigger(ai); } + static Trigger* lucifron_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceTrigger(botAI, "lucifron"); } + static Trigger* magmadar_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "magmadar"); } + static Trigger* gehennas_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceTrigger(botAI, "gehennas"); } + static Trigger* garr_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "garr"); } + static Trigger* baron_geddon_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "baron geddon"); } + static Trigger* living_bomb_debuff(PlayerbotAI* botAI) { return new McLivingBombDebuffTrigger(botAI); } + static Trigger* baron_geddon_inferno(PlayerbotAI* botAI) { return new McBaronGeddonInfernoTrigger(botAI); } + static Trigger* shazzrah_ranged(PlayerbotAI* botAI) { return new McShazzrahRangedTrigger(botAI); } + static Trigger* sulfuron_harbinger_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "sulfuron harbinger"); } + static Trigger* golemagg_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "golemagg the incinerator"); } + static Trigger* golemagg_mark_boss(PlayerbotAI* botAI) { return new McGolemaggMarkBossTrigger(botAI); } + static Trigger* golemagg_is_main_tank(PlayerbotAI* botAI) { return new McGolemaggIsMainTankTrigger(botAI); } + static Trigger* golemagg_is_assist_tank(PlayerbotAI* botAI) { return new McGolemaggIsAssistTankTrigger(botAI); } + static Trigger* majordomo_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceTrigger(botAI, "majordomo executus"); } + static Trigger* ragnaros_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "ragnaros"); } }; #endif diff --git a/src/strategy/raids/moltencore/RaidMcTriggers.cpp b/src/strategy/raids/moltencore/RaidMcTriggers.cpp index f77b70f20e..834d703d33 100644 --- a/src/strategy/raids/moltencore/RaidMcTriggers.cpp +++ b/src/strategy/raids/moltencore/RaidMcTriggers.cpp @@ -1,22 +1,40 @@ #include "RaidMcTriggers.h" #include "SharedDefines.h" +#include "RaidMcHelpers.h" + +using namespace MoltenCoreHelpers; bool McLivingBombDebuffTrigger::IsActive() { - // if bot has barron geddon's living bomb, we need to add strat, otherwise we need to remove - // only do when fighting baron geddon (to avoid modifying strat set by player outside this fight) - if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon")) - { - if (boss->IsInCombat()) - return bot->HasAura(20475) != botAI->HasStrategy("move from group", BotState::BOT_STATE_COMBAT); - } - return false; + // No check for Baron Geddon, because bots may have the bomb even after Geddon died. + return bot->HasAura(SPELL_LIVING_BOMB); } bool McBaronGeddonInfernoTrigger::IsActive() { if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon")) - return boss->HasAura(19695); + return boss->HasAura(SPELL_INFERNO); return false; } + +bool McShazzrahRangedTrigger::IsActive() +{ + return AI_VALUE2(Unit*, "find target", "shazzrah") && PlayerbotAI::IsRanged(bot); +} + +bool McGolemaggMarkBossTrigger::IsActive() +{ + // any tank may mark the boss + return AI_VALUE2(Unit*, "find target", "golemagg the incinerator") && PlayerbotAI::IsTank(bot); +} + +bool McGolemaggIsMainTankTrigger::IsActive() +{ + return AI_VALUE2(Unit*, "find target", "golemagg the incinerator") && PlayerbotAI::IsMainTank(bot); +} + +bool McGolemaggIsAssistTankTrigger::IsActive() +{ + return AI_VALUE2(Unit*, "find target", "golemagg the incinerator") && PlayerbotAI::IsAssistTank(bot); +} diff --git a/src/strategy/raids/moltencore/RaidMcTriggers.h b/src/strategy/raids/moltencore/RaidMcTriggers.h index 9d2fb985a2..4cd84a2df2 100644 --- a/src/strategy/raids/moltencore/RaidMcTriggers.h +++ b/src/strategy/raids/moltencore/RaidMcTriggers.h @@ -19,4 +19,32 @@ class McBaronGeddonInfernoTrigger : public Trigger bool IsActive() override; }; +class McShazzrahRangedTrigger : public Trigger +{ +public: + McShazzrahRangedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc shazzrah ranged") {} + bool IsActive() override; +}; + +class McGolemaggMarkBossTrigger : public Trigger +{ +public: + McGolemaggMarkBossTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc golemagg mark boss") {} + bool IsActive() override; +}; + +class McGolemaggIsMainTankTrigger : public Trigger +{ +public: + McGolemaggIsMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc golemagg is main tank") {} + bool IsActive() override; +}; + +class McGolemaggIsAssistTankTrigger : public Trigger +{ +public: + McGolemaggIsAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc golemagg is assist tank") {} + bool IsActive() override; +}; + #endif diff --git a/src/strategy/rogue/RogueTriggers.cpp b/src/strategy/rogue/RogueTriggers.cpp index 16aada0164..fd9901552e 100644 --- a/src/strategy/rogue/RogueTriggers.cpp +++ b/src/strategy/rogue/RogueTriggers.cpp @@ -22,8 +22,8 @@ bool UnstealthTrigger::IsActive() return botAI->HasAura("stealth", bot) && !AI_VALUE(uint8, "attacker count") && (AI_VALUE2(bool, "moving", "self target") && ((botAI->GetMaster() && - sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "master target"), 10.0f) && - AI_VALUE2(bool, "moving", "master target")) || + sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "group leader"), 10.0f) && + AI_VALUE2(bool, "moving", "group leader")) || !AI_VALUE(uint8, "attacker count"))); } diff --git a/src/strategy/triggers/RangeTriggers.cpp b/src/strategy/triggers/RangeTriggers.cpp index 3c60934fd2..af29f984d1 100644 --- a/src/strategy/triggers/RangeTriggers.cpp +++ b/src/strategy/triggers/RangeTriggers.cpp @@ -213,7 +213,7 @@ PartyMemberToHealOutOfSpellRangeTrigger::PartyMemberToHealOutOfSpellRangeTrigger bool FarFromMasterTrigger::IsActive() { - return sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "master target"), distance); + return sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "group leader"), distance); } bool TooCloseToCreatureTrigger::TooCloseToCreature(uint32 creatureId, float range, bool alive) diff --git a/src/strategy/values/Formations.cpp b/src/strategy/values/Formations.cpp index 21c43f402a..30c738252b 100644 --- a/src/strategy/values/Formations.cpp +++ b/src/strategy/values/Formations.cpp @@ -62,7 +62,7 @@ class MeleeFormation : public FollowFormation public: MeleeFormation(PlayerbotAI* botAI) : FollowFormation(botAI, "melee") {} - std::string const GetTargetName() override { return "master target"; } + std::string const GetTargetName() override { return "group leader"; } }; class QueueFormation : public FollowFormation diff --git a/src/strategy/values/MasterTargetValue.cpp b/src/strategy/values/GroupLeaderValue.cpp similarity index 70% rename from src/strategy/values/MasterTargetValue.cpp rename to src/strategy/values/GroupLeaderValue.cpp index 6fddb6d99b..18ed007648 100644 --- a/src/strategy/values/MasterTargetValue.cpp +++ b/src/strategy/values/GroupLeaderValue.cpp @@ -3,8 +3,8 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "MasterTargetValue.h" +#include "GroupLeaderValue.h" #include "Playerbots.h" -Unit* MasterTargetValue::Calculate() { return botAI->GetGroupMaster(); } +Unit* GroupLeaderValue::Calculate() { return botAI->GetGroupLeader(); } diff --git a/src/strategy/values/MasterTargetValue.h b/src/strategy/values/GroupLeaderValue.h similarity index 57% rename from src/strategy/values/MasterTargetValue.h rename to src/strategy/values/GroupLeaderValue.h index a9f8f781df..a359620437 100644 --- a/src/strategy/values/MasterTargetValue.h +++ b/src/strategy/values/GroupLeaderValue.h @@ -3,18 +3,18 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#ifndef _PLAYERBOT_MASTERTARGETVALUE_H -#define _PLAYERBOT_MASTERTARGETVALUE_H +#ifndef _PLAYERBOT_GROUPLEADERVALUE_H +#define _PLAYERBOT_GROUPLEADERVALUE_H #include "Value.h" class PlayerbotAI; class Unit; -class MasterTargetValue : public UnitCalculatedValue +class GroupLeaderValue : public UnitCalculatedValue { public: - MasterTargetValue(PlayerbotAI* botAI, std::string const name = "master target") : UnitCalculatedValue(botAI, name) + GroupLeaderValue(PlayerbotAI* botAI, std::string const name = "group leader") : UnitCalculatedValue(botAI, name) { } diff --git a/src/strategy/values/GroupValues.cpp b/src/strategy/values/GroupValues.cpp index f9cb21028e..01049a7969 100644 --- a/src/strategy/values/GroupValues.cpp +++ b/src/strategy/values/GroupValues.cpp @@ -28,7 +28,7 @@ GuidVector GroupMembersValue::Calculate() bool IsFollowingPartyValue::Calculate() { - if (botAI->GetGroupMaster() == bot) + if (botAI->GetGroupLeader() == bot) return true; if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT)) @@ -39,15 +39,15 @@ bool IsFollowingPartyValue::Calculate() bool IsNearLeaderValue::Calculate() { - Player* groupMaster = botAI->GetGroupMaster(); + Player* groupLeader = botAI->GetGroupLeader(); - if (!groupMaster) + if (!groupLeader) return false; - if (groupMaster == bot) + if (groupLeader == bot) return true; - return sServerFacade->GetDistance2d(bot, botAI->GetGroupMaster()) < sPlayerbotAIConfig->sightDistance; + return sServerFacade->GetDistance2d(bot, botAI->GetGroupLeader()) < sPlayerbotAIConfig->sightDistance; } bool BoolANDValue::Calculate() @@ -154,8 +154,8 @@ bool GroupReadyValue::Calculate() // We only wait for members that are in range otherwise we might be waiting for bots stuck in dead loops // forever. - if (botAI->GetGroupMaster() && - sServerFacade->GetDistance2d(member, botAI->GetGroupMaster()) > sPlayerbotAIConfig->sightDistance) + if (botAI->GetGroupLeader() && + sServerFacade->GetDistance2d(member, botAI->GetGroupLeader()) > sPlayerbotAIConfig->sightDistance) continue; if (member->GetHealthPct() < sPlayerbotAIConfig->almostFullHealth) diff --git a/src/strategy/values/ValueContext.h b/src/strategy/values/ValueContext.h index b8e79d6e5a..adf03be8ea 100644 --- a/src/strategy/values/ValueContext.h +++ b/src/strategy/values/ValueContext.h @@ -30,6 +30,7 @@ #include "Formations.h" #include "GrindTargetValue.h" #include "GroupValues.h" +#include "GroupLeaderValue.h" #include "GuildValues.h" #include "HasAvailableLootValue.h" #include "HasTotemValue.h" @@ -51,7 +52,6 @@ #include "LootStrategyValue.h" #include "MaintenanceValues.h" #include "ManaSaveLevelValue.h" -#include "MasterTargetValue.h" #include "NearestAdsValue.h" #include "NearestCorpsesValue.h" #include "NearestFriendlyPlayersValue.h" @@ -130,7 +130,7 @@ class ValueContext : public NamedObjectContext creators["party member to resurrect"] = &ValueContext::party_member_to_resurrect; creators["current target"] = &ValueContext::current_target; creators["self target"] = &ValueContext::self_target; - creators["master target"] = &ValueContext::master; + creators["group leader"] = &ValueContext::group_leader; creators["line target"] = &ValueContext::line_target; creators["tank target"] = &ValueContext::tank_target; creators["dps target"] = &ValueContext::dps_target; @@ -439,7 +439,7 @@ class ValueContext : public NamedObjectContext static UntypedValue* current_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); } static UntypedValue* old_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); } static UntypedValue* self_target(PlayerbotAI* botAI) { return new SelfTargetValue(botAI); } - static UntypedValue* master(PlayerbotAI* botAI) { return new MasterTargetValue(botAI); } + static UntypedValue* group_leader(PlayerbotAI* botAI) { return new GroupLeaderValue(botAI); } static UntypedValue* line_target(PlayerbotAI* botAI) { return new LineTargetValue(botAI); } static UntypedValue* tank_target(PlayerbotAI* botAI) { return new TankTargetValue(botAI); } static UntypedValue* dps_target(PlayerbotAI* botAI) { return new DpsTargetValue(botAI); }