diff --git a/src/server/game/AI/NpcBots/bot_ai.cpp b/src/server/game/AI/NpcBots/bot_ai.cpp index c3de583e8ad20..4f2c945ce22aa 100644 --- a/src/server/game/AI/NpcBots/bot_ai.cpp +++ b/src/server/game/AI/NpcBots/bot_ai.cpp @@ -5320,15 +5320,17 @@ void bot_ai::_extendAttackRange(float& dist) const } bool bot_ai::_canSwitchToTarget(Unit const* from, Unit const* newTarget, int8 byspell) const { - if (newTarget) + if (newTarget && newTarget != me->GetVictim()) { if (IAmFree()) { - if (newTarget != me->GetVictim() && - (!from || me->GetDistance(newTarget) < me->GetDistance(from) - 10.0f || newTarget->GetHealth() < from->GetHealth()) && + if ((!from || me->GetDistance(newTarget) < me->GetDistance(from) - 10.0f || newTarget->GetHealth() < from->GetHealth()) && CanBotAttack(newTarget, byspell)) return true; } + else if (!from && me->GetDistance(newTarget) < 0.75f * _getAttackDistance(float(master->GetBotMgr()->GetBotFollowDist())) && + CanBotAttack(newTarget, byspell)) + return true; } return false; @@ -17659,13 +17661,17 @@ bool bot_ai::GlobalUpdate(uint32 diff) //Faction //ensure master is not controlled ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(master->GetRace()); - uint32 fac = rEntry ? rEntry->FactionID : 0; - if (me->GetFaction() != master->GetFaction() && master->GetFaction() == fac) + uint32 fac_orig = rEntry ? rEntry->FactionID : 0; + if (master->GetFaction() == fac_orig) { - //std::ostringstream msg; - //msg << "Something changed my faction (now " << me->GetFaction() << "), changing back to " << fac << "!"; - //BotWhisper(msg.str().c_str()); - me->SetFaction(fac); + uint32 fac = (!IAmFree() && me->GetMap()->IsBattleArena()) ? FACTION_MONSTER : fac_orig; + if (me->GetFaction() != fac) + { + //std::ostringstream msg; + //msg << "Something changed my faction (now " << me->GetFaction() << "), changing back to " << fac << "!"; + //BotWhisper(msg.str().c_str()); + me->SetFaction(fac); + } } //Visibility if (!me->IsVisible() && master->IsVisible()) @@ -18658,6 +18664,16 @@ bool bot_ai::FinishTeleport(bool reset) } //me->CastSpell(me, HONORLESS_TARGET, true); + //Arena flags + Battleground const* bg = GetBG(); + if (bg && bg->isArena()) + { + TeamId teamId = bg->GetBotTeamId(me->GetGUID()); + uint32 flag_spell = teamId == TEAM_ALLIANCE ? master->GetTeamId() == TEAM_HORDE ? ARENA_FLAG_TEAM_H_GOLD : ARENA_FLAG_TEAM_A_GOLD : + master->GetTeamId() == TEAM_HORDE ? ARENA_FLAG_TEAM_H_GREEN : ARENA_FLAG_TEAM_A_GREEN; + me->CastSpell(me, flag_spell, true); + } + //update group member online state if (Group* gr = master->GetGroup()) if (gr->IsMember(me->GetGUID())) @@ -19992,18 +20008,23 @@ void bot_ai::OnBotEnterBattleground() if (bg->GetStatus() != STATUS_IN_PROGRESS && IsWanderer()) { BotWPFlags myTeamSpawnFlags; - switch (bg->GetBotTeamId(me->GetGUID())) + if (bg->isArena()) + myTeamSpawnFlags = BotWPFlags::BOTWP_FLAG_SPAWN; + else { - case TEAM_ALLIANCE: myTeamSpawnFlags = BotWPFlags::BOTWP_FLAG_ALLIANCE_SPAWN_POINT; break; - case TEAM_HORDE: myTeamSpawnFlags = BotWPFlags::BOTWP_FLAG_HORDE_SPAWN_POINT; break; - default: myTeamSpawnFlags = BotWPFlags::BOTWP_FLAG_SPAWN; break; + switch (bg->GetBotTeamId(me->GetGUID())) + { + case TEAM_ALLIANCE: myTeamSpawnFlags = BotWPFlags::BOTWP_FLAG_ALLIANCE_SPAWN_POINT; break; + case TEAM_HORDE: myTeamSpawnFlags = BotWPFlags::BOTWP_FLAG_HORDE_SPAWN_POINT; break; + default: myTeamSpawnFlags = BotWPFlags::BOTWP_FLAG_SPAWN; break; + } } uint32 mapId = bg->GetBgMap()->GetId(); float mindist = 50000.0f; WanderNode const* startNode = nullptr; WanderNode::DoForAllMapWPs(mapId, [pos = me->GetPosition(), spawnFlags = myTeamSpawnFlags, &mindist, &startNode](WanderNode const* wp) { - if (wp->HasFlag(spawnFlags)) + if (wp->HasAllFlags(spawnFlags)) { float dist = pos.GetExactDist2d(wp); if (dist < mindist) diff --git a/src/server/game/AI/NpcBots/botmgr.cpp b/src/server/game/AI/NpcBots/botmgr.cpp index 49f0656838248..dad9c4e432a73 100644 --- a/src/server/game/AI/NpcBots/botmgr.cpp +++ b/src/server/game/AI/NpcBots/botmgr.cpp @@ -1188,9 +1188,8 @@ void BotMgr::Update(uint32 diff) if (ai->GetReviveTimer() <= diff) { if (bot->IsInMap(_owner) && !bot->IsAlive() && !ai->IsDuringTeleport() && _owner->IsAlive() && !_owner->IsInCombat() && - !_owner->IsBeingTeleported() && !_owner->InArena() && !_owner->IsInFlight() && - !_owner->HasUnitFlag2(UNIT_FLAG2_FEIGN_DEATH) && - !_owner->HasInvisibilityAura() && !_owner->HasStealthAura()) + !_owner->IsBeingTeleported() && !_owner->GetMap()->IsBattleArena() && !_owner->IsInFlight() && + !_owner->HasUnitFlag2(UNIT_FLAG2_FEIGN_DEATH) && !_owner->HasInvisibilityAura() && !_owner->HasStealthAura()) { _reviveBot(bot); continue; @@ -1252,10 +1251,11 @@ bool BotMgr::RestrictBots(Creature const* bot, bool add) const if (LimitBots(currMap)) { + Group const* gr = _owner->GetGroup(); + //if bot is not in instance group - deny (only if trying to teleport to instance) if (add) { - Group const* gr = _owner->GetGroup(); if (!gr || !gr->IsMember(bot->GetGUID())) return true; @@ -1292,13 +1292,23 @@ bool BotMgr::RestrictBots(Creature const* bot, bool add) const uint32 max_players = 0; if (currMap->IsDungeon()) max_players = currMap->ToInstanceMap()->GetMaxPlayers(); - else if (currMap->IsBattleground()) + else if (currMap->IsBattleground() || currMap->IsBattleArena()) max_players = _owner->GetBattleground()->GetMaxPlayersPerTeam(); - else if (currMap->IsBattleArena()) - max_players = _owner->GetBattleground()->GetArenaType(); - if (max_players && currMap->GetPlayersCountExceptGMs() + uint32(add) > max_players) - return true; + if (max_players) + { + uint32 curPlayers; + if (gr && currMap->IsBattlegroundOrArena()) + { + curPlayers = std::ranges::count_if(GetAllGroupMembers(gr), [this](Unit const* u) { + return u->IsInWorld() && u->IsInMap(_owner) && !(u->IsNPCBot() && u->ToCreature()->IsTempBot()); + }); + } + else + curPlayers = currMap->GetPlayersCountExceptGMs(); + if (curPlayers + uint32(add) > max_players) + return true; + } } return false; diff --git a/src/server/game/AI/NpcBots/botspell.h b/src/server/game/AI/NpcBots/botspell.h index 9e5d64fd9e80a..e34677929376b 100644 --- a/src/server/game/AI/NpcBots/botspell.h +++ b/src/server/game/AI/NpcBots/botspell.h @@ -23,6 +23,10 @@ enum BotSpells : uint32 SUMMONING_STONE_EFFECT = 59782,//Cast time 5s + Channeled 2m SHOOT_WAND = 5019, OPEN_FLAG_BG = 21651, + ARENA_FLAG_TEAM_A_GOLD = 32724, + ARENA_FLAG_TEAM_A_GREEN = 32725, + ARENA_FLAG_TEAM_H_GOLD = 35774, + ARENA_FLAG_TEAM_H_GREEN = 35775, ///Portals PORTAL_STORMWIND = 10059, PORTAL_IRONFORGE = 11416, diff --git a/src/server/game/AI/NpcBots/bpet_ai.cpp b/src/server/game/AI/NpcBots/bpet_ai.cpp index 829a9a11526a2..8b1b258ff15ae 100644 --- a/src/server/game/AI/NpcBots/bpet_ai.cpp +++ b/src/server/game/AI/NpcBots/bpet_ai.cpp @@ -2436,13 +2436,12 @@ bool bot_pet_ai::GlobalUpdate(uint32 diff) //Faction //ensure master is not controlled ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(petOwner->GetBotOwner()->GetRace()); - uint32 fac = rEntry ? rEntry->FactionID : 0; - if (me->GetFaction() != petOwner->GetBotOwner()->GetFaction() && petOwner->GetBotOwner()->GetFaction() == fac) + uint32 fac_orig = rEntry ? rEntry->FactionID : 0; + if (petOwner->GetBotOwner()->GetFaction() == fac_orig) { - //std::ostringstream msg; - //msg << "Something changed my faction (now " << me->GetFaction() << "), changing back to " << fac << "!"; - //BotWhisper(msg.str().c_str()); - me->SetFaction(fac); + uint32 fac = (!IAmFree() && me->GetMap()->IsBattleArena()) ? FACTION_MONSTER : fac_orig; + if (me->GetFaction() != fac) + me->SetFaction(fac); } //Visibility if (!me->IsVisible() && petOwner->GetBotOwner()->IsVisible()) diff --git a/src/server/game/Battlegrounds/Arena.cpp b/src/server/game/Battlegrounds/Arena.cpp index 6d5ebc28e4dcb..cd3d31fbbe646 100644 --- a/src/server/game/Battlegrounds/Arena.cpp +++ b/src/server/game/Battlegrounds/Arena.cpp @@ -96,6 +96,25 @@ void Arena::AddPlayer(Player* player) UpdateArenaWorldState(); } +//npcbot +void Arena::AddBot(Creature* bot) +{ + ASSERT(bot->IsNPCBot() && !bot->IsFreeBot()); + + bool const isInBattleground = IsPlayerInBattleground(bot->GetGUID()); + Battleground::AddBot(bot); + + uint32 botteam = bot->GetBotOwner()->GetBGTeam(); + + if (!isInBattleground) + BotScores[bot->GetEntry()] = new ArenaScore(bot->GetGUID(), botteam); + + //No flags - handled by AI + + UpdateArenaWorldState(); +} +//end npcbot + void Arena::RemovePlayer(Player* /*player*/, ObjectGuid /*guid*/, uint32 /*team*/) { if (GetStatus() == STATUS_WAIT_LEAVE) @@ -105,6 +124,17 @@ void Arena::RemovePlayer(Player* /*player*/, ObjectGuid /*guid*/, uint32 /*team* CheckWinConditions(); } +//npcbot +void Arena::RemoveBot(ObjectGuid /*guid*/) +{ + if (GetStatus() == STATUS_WAIT_LEAVE) + return; + + UpdateArenaWorldState(); + CheckWinConditions(); +} +//end npcbot + void Arena::FillInitialWorldStates(WorldPackets::WorldState::InitWorldStates& packet) { packet.Worldstates.emplace_back(ARENA_WORLD_STATE_ALIVE_PLAYERS_GREEN, GetAlivePlayersCountByTeam(HORDE)); @@ -128,6 +158,33 @@ void Arena::HandleKillPlayer(Player* player, Player* killer) CheckWinConditions(); } +//npcbot +void Arena::HandleBotKillPlayer(Creature* killer, Player* victim) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + Battleground::HandleBotKillPlayer(killer, victim); + UpdateArenaWorldState(); + CheckWinConditions(); +} +void Arena::HandleBotKillBot(Creature* killer, Creature* victim) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + Battleground::HandleBotKillBot(killer, victim); + UpdateArenaWorldState(); + CheckWinConditions(); +} +void Arena::HandlePlayerKillBot(Creature* victim, Player* killer) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + Battleground::HandlePlayerKillBot(victim, killer); + UpdateArenaWorldState(); + CheckWinConditions(); +} +//end npcbot + void Arena::RemovePlayerAtLeave(ObjectGuid guid, bool transport, bool sendPacket) { if (isRated() && GetStatus() == STATUS_IN_PROGRESS) @@ -156,6 +213,35 @@ void Arena::RemovePlayerAtLeave(ObjectGuid guid, bool transport, bool sendPacket Battleground::RemovePlayerAtLeave(guid, transport, sendPacket); } +//npcbot +void Arena::RemoveBotAtLeave(ObjectGuid guid) +{ + //if (isRated() && GetStatus() == STATUS_IN_PROGRESS) + //{ + // BattlegroundBotMap::const_iterator itr = m_Bots.find(guid); + // if (itr != m_Bots.end()) // check if the player was a participant of the match, or only entered through gm command (appear) + // { + // // if the player was a match participant, calculate rating + // uint32 team = itr->second.Team; + + // ArenaTeam* winnerArenaTeam = sArenaTeamMgr->GetArenaTeamById(GetArenaTeamIdForTeam(GetOtherTeam(team))); + // ArenaTeam* loserArenaTeam = sArenaTeamMgr->GetArenaTeamById(GetArenaTeamIdForTeam(team)); + + // // left a rated match while the encounter was in progress, consider as loser + // if (winnerArenaTeam && loserArenaTeam && winnerArenaTeam != loserArenaTeam) + // { + // if (Player* player = _GetPlayer(itr->first, itr->second.OfflineRemoveTime != 0, "Arena::RemovePlayerAtLeave")) + // loserArenaTeam->MemberLost(player, GetArenaMatchmakerRating(GetOtherTeam(team))); + // else + // loserArenaTeam->OfflineMemberLost(guid, GetArenaMatchmakerRating(GetOtherTeam(team))); + // } + // } + //} + + Battleground::RemoveBotAtLeave(guid); +} +//end npcbot + void Arena::CheckWinConditions() { if (!GetAlivePlayersCountByTeam(ALLIANCE) && GetPlayersCountByTeam(HORDE)) diff --git a/src/server/game/Battlegrounds/Arena.h b/src/server/game/Battlegrounds/Arena.h index 1aaf613cda4b6..29021bbe01e79 100644 --- a/src/server/game/Battlegrounds/Arena.h +++ b/src/server/game/Battlegrounds/Arena.h @@ -52,6 +52,15 @@ class TC_GAME_API Arena : public Battleground void AddPlayer(Player* player) override; void RemovePlayer(Player* /*player*/, ObjectGuid /*guid*/, uint32 /*team*/) override; + //npcbot + void AddBot(Creature* bot) override; + void RemoveBotAtLeave(ObjectGuid guid) override; + void RemoveBot(ObjectGuid /*guid*/) override; + void HandleBotKillPlayer(Creature* killer, Player* victim) override; + void HandleBotKillBot(Creature* killer, Creature* victim) override; + void HandlePlayerKillBot(Creature* victim, Player* killer) override; + //end npcbot + void FillInitialWorldStates(WorldPackets::WorldState::InitWorldStates& packet) override; void UpdateArenaWorldState(); diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp index d4d930b12dc28..e6b0233850327 100644 --- a/src/server/game/Battlegrounds/Battleground.cpp +++ b/src/server/game/Battlegrounds/Battleground.cpp @@ -1255,7 +1255,7 @@ void Battleground::AddPlayer(Player* player) void Battleground::AddBot(Creature* bot) { ObjectGuid guid = bot->GetGUID(); - uint32 team = (BotDataMgr::GetTeamIdForFaction(bot->GetFaction()) == TEAM_ALLIANCE) ? ALLIANCE : HORDE; + uint32 team = !bot->IsFreeBot() ? bot->GetBotOwner()->GetBGTeam() : (BotDataMgr::GetTeamIdForFaction(bot->GetFaction()) == TEAM_ALLIANCE) ? ALLIANCE : HORDE; // Add to list/maps BattlegroundBot bb; diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 7007b75eb9fc0..c5e7ce33cee46 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -1364,6 +1364,11 @@ void Creature::SetLootRecipient(Unit* unit, bool withGroup) else m_lootRecipientGroup = ObjectGuid::Empty; + //npcbot: prevent visual tap on owned bots + if (IsNPCBotOrPet() && !IsFreeBot()) + return; + //end npcbot + SetDynamicFlag(UNIT_DYNFLAG_TAPPED); } diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index a456afa7e145c..fe66f7c58016b 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -2410,6 +2410,14 @@ GroupJoinBattlegroundResult Group::CanJoinBattlegroundQueue(Battleground const* return ERR_BATTLEGROUND_JOIN_FAILED; } + //npcbot + for (GroupBotReference* bitr = GetFirstBotMember(); bitr != nullptr; bitr = bitr->next(), ++memberscount) + { + if (!bitr->GetSource()) + return ERR_BATTLEGROUND_JOIN_FAILED; + } + //end npcbot + // only check for MinPlayerCount since MinPlayerCount == MaxPlayerCount for arenas... if (bgOrTemplate->isArena() && memberscount != MinPlayerCount) return ERR_ARENA_TEAM_PARTY_SIZE; diff --git a/src/server/game/Handlers/BattleGroundHandler.cpp b/src/server/game/Handlers/BattleGroundHandler.cpp index 86d30af93334a..4fd415a716c1b 100644 --- a/src/server/game/Handlers/BattleGroundHandler.cpp +++ b/src/server/game/Handlers/BattleGroundHandler.cpp @@ -753,6 +753,21 @@ void WorldSession::HandleBattlemasterJoinArena(WorldPacket& recvData) GroupJoinBattlegroundResult err = ERR_GROUP_JOIN_BATTLEGROUND_FAIL; + //npcbot + bool have_bots_in_group = false; + if (_player->GetGroup() && _player->HaveBot()) + { + for (auto const& mslot : _player->GetGroup()->GetMemberSlots()) + { + if (mslot.guid.IsCreature() && _player->GetBotMgr()->GetBot(mslot.guid)) + { + have_bots_in_group = true; + break; + } + } + } + //end npcbot + if (!asGroup) { if (_player->isUsingLfg()) @@ -779,6 +794,16 @@ void WorldSession::HandleBattlemasterJoinArena(WorldPacket& recvData) // check if has free queue slots if (!_player->HasFreeBattlegroundQueueId()) return; + + //npcbot: do not allow entering as group if there are bots in group + if (have_bots_in_group) + { + WorldPacket data; + sBattlegroundMgr->BuildGroupJoinedBattlegroundPacket(&data, ERR_BATTLEGROUND_JOIN_FAILED); + _player->SendDirectMessage(&data); + return; + } + //end npcbot } else { @@ -803,6 +828,17 @@ void WorldSession::HandleBattlemasterJoinArena(WorldPacket& recvData) _player->GetSession()->SendNotInArenaTeamPacket(arenatype); return; } + + //npcbot: do not allow bots in rated matches + if (have_bots_in_group) + { + WorldPacket data; + sBattlegroundMgr->BuildGroupJoinedBattlegroundPacket(&data, ERR_BATTLEGROUND_JOIN_TIMED_OUT); + _player->SendDirectMessage(&data); + return; + } + //end npcbot + // get the team rating for queueing arenaRating = at->GetRating(); matchmakerRating = at->GetAverageMMR(grp); @@ -858,6 +894,22 @@ void WorldSession::HandleBattlemasterJoinArena(WorldPacket& recvData) sBattlegroundMgr->BuildGroupJoinedBattlegroundPacket(&data, err); member->SendDirectMessage(&data); TC_LOG_DEBUG("bg.battleground", "Battleground: player joined queue for arena as group bg queue type {} bg type {}: {}, NAME {}", bgQueueTypeId, bgTypeId, member->GetGUID().ToString(), member->GetName()); + + //npcbot: list bots + if (!member->HaveBot()) + continue; + + BotMap const* map = member->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + Creature const* bot = itr->second; + if (!bot || !grp->IsMember(bot->GetGUID())) + continue; + + TC_LOG_DEBUG("bg.battleground", "Battleground: NPCBot joined queue for arena bg queue type {} bg type {}: GUID {}, NAME {} (owner: {})", + bgQueueTypeId, bgTypeId, bot->GetGUID().ToString(), bot->GetName(), member->GetName()); + } + //end npcbot } } else