This commit is contained in:
Bobblybook
2024-10-20 16:18:27 +11:00
14 changed files with 411 additions and 194 deletions

View File

@@ -1263,11 +1263,6 @@ Playerbots.Updates.EnableDatabases = 1
# Command server port, 0 - disabled # Command server port, 0 - disabled
AiPlayerbot.CommandServerPort = 8888 AiPlayerbot.CommandServerPort = 8888
# Diff with/without player in server. The server will tune bot activity to reach the desired server tick speed (in ms).# PLAYERBOT SYSTEM SETTINGS #
AiPlayerbot.EnablePrototypePerformanceDiff = 0
AiPlayerbot.DiffWithPlayer = 100
AiPlayerbot.DiffEmpty = 200
# #
# #
# #
@@ -1462,9 +1457,18 @@ AiPlayerbot.RandombotsWalkingRPG.InDoors = 0
# The default is 10. With 10% of all bots going active or inactive each minute. # The default is 10. With 10% of all bots going active or inactive each minute.
AiPlayerbot.BotActiveAlone = 100 AiPlayerbot.BotActiveAlone = 100
# Specify 1 for enabled, 0 for disabled. # Specify smart scaling is enabled or not.
# The default is 1. Automatically adjusts 'BotActiveAlone' percentage based on server latency. # The default is 1. When enabled (smart) scales the 'BotActiveAlone' value.
AiPlayerbot.botActiveAloneAutoScale = 1 AiPlayerbot.botActiveAloneSmartScale = 1
# Only when botLevel is between WhenMinLevel and WhenMaxLevel.
AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel = 1
AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel = 80
# The server will tune bot activity to reach the desired server tick speed (in ms)
# bots will only join battleground when there is no lag based on latency diffs below
AiPlayerbot.botActiveAloneSmartScaleDiffWithPlayer = 100
AiPlayerbot.botActiveAloneSmartScaleDiffEmpty = 200
# Premade spell to avoid (undetected spells) # Premade spell to avoid (undetected spells)
# spellid-radius, ... # spellid-radius, ...

View File

@@ -464,7 +464,7 @@ INSERT INTO `ai_playerbot_texts` (`name`, `text`, `say_type`, `reply_type`, `tex
-- %my_level -- %my_level
('suggest_something', 'Wanna party in %zone_name.', 0, 0, '', 'Je veux faire la fête dans %zone_name.', '', '', '', '¡Vamos a perrear a %zone_name!', '', 'Ищу группу в %zone_name.'), ('suggest_something', 'Wanna party in %zone_name.', 0, 0, '', 'Je veux faire la fête dans %zone_name.', '', '', '', '¡Vamos a perrear a %zone_name!', '', 'Ищу группу в %zone_name.'),
('suggest_something', 'Anyone is looking for %my_role?', 0, 0, '', 'Quelqu\'un cherche un %my_role ?', '', '', '', '¿Alguien está buscando %my_role?', '', 'Кто-нибудь ищет %my_role?'), ('suggest_something', 'Anyone is looking for %my_role?', 0, 0, '', 'Quelqu\'un cherche un %my_role ?', '', '', '', '¿Alguien está buscando %my_role?', '', 'Кто-нибудь ищет %my_role?'),
('suggest_something', '%my_role is looking for quild.', 0, 0, '', '%my_role recherche une guilde.', '', '', '', '%my_role está buscando hermandad.', '', '%my_role ищу гильдию.'), ('suggest_something', '%my_role is looking for guild.', 0, 0, '', '%my_role recherche une guilde.', '', '', '', '%my_role está buscando hermandad.', '', '%my_role ищу гильдию.'),
('suggest_something', 'Looking for gold.', 0, 0, '', 'A la recherche de l\'or.', '', '', '', 'Buscando oro.', '', 'Дайте голды'), ('suggest_something', 'Looking for gold.', 0, 0, '', 'A la recherche de l\'or.', '', '', '', 'Buscando oro.', '', 'Дайте голды'),
('suggest_something', '%my_role wants to join a good guild.', 0, 0, '', '%my_role veut rejoindre une bonne guilde.', '', '', '', '%my_role quiere unirse a una buen hermandad.', '', '%my_role хочу в хорошую гильдию.'), ('suggest_something', '%my_role wants to join a good guild.', 0, 0, '', '%my_role veut rejoindre une bonne guilde.', '', '', '', '%my_role quiere unirse a una buen hermandad.', '', '%my_role хочу в хорошую гильдию.'),
('suggest_something', 'Need a friend.', 0, 0, '', 'Besoin d\'un ami.', '', '', '', 'Necesito un amigo...', '', 'Ищу друга.'), ('suggest_something', 'Need a friend.', 0, 0, '', 'Besoin d\'un ami.', '', '', '', 'Necesito un amigo...', '', 'Ищу друга.'),
@@ -1457,19 +1457,3 @@ INSERT INTO `ai_playerbot_texts` (`name`, `text`, `say_type`, `reply_type`, `tex
('dummy_end', 'dummy', 0, 0, '', '', '', '', '', '', '', ''); ('dummy_end', 'dummy', 0, 0, '', '', '', '', '', '', '', '');
DROP TABLE IF EXISTS `ai_playerbot_texts_chance`;
CREATE TABLE IF NOT EXISTS `ai_playerbot_texts_chance` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`probability` bigint(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=UTF8;
/*!40000 ALTER TABLE `ai_playerbot_texts_chance` DISABLE KEYS */;
INSERT INTO `ai_playerbot_texts_chance` (`id`, `name`, `probability`) VALUES
(1, 'taunt', 30),
(2, 'aoe', 75),
(3, 'loot', 20);
/*!40000 ALTER TABLE `ai_playerbot_texts_chance` ENABLE KEYS */;

View File

@@ -0,0 +1,14 @@
DROP TABLE IF EXISTS `ai_playerbot_texts_chance`;
CREATE TABLE IF NOT EXISTS `ai_playerbot_texts_chance` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`probability` bigint(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=UTF8;
/*!40000 ALTER TABLE `ai_playerbot_texts_chance` DISABLE KEYS */;
INSERT INTO `ai_playerbot_texts_chance` (`id`, `name`, `probability`) VALUES
(1, 'taunt', 30),
(2, 'aoe', 75),
(3, 'loot', 20);
/*!40000 ALTER TABLE `ai_playerbot_texts_chance` ENABLE KEYS */;

View File

@@ -304,9 +304,13 @@ ItemIds ChatHelper::parseItems(std::string const text)
std::string const ChatHelper::FormatQuest(Quest const* quest) std::string const ChatHelper::FormatQuest(Quest const* quest)
{ {
if (!quest)
{
return "Invalid quest";
}
std::ostringstream out; std::ostringstream out;
out << "|cFFFFFF00|Hquest:" << quest->GetQuestId() << ':' << quest->GetQuestLevel() << "|h[" << quest->GetTitle() out << "|cFFFFFF00|Hquest:" << quest->GetQuestId() << ':' << quest->GetQuestLevel() << "|h[" << quest->GetTitle() << "]|h|r";
<< "]|h|r";
return out.str(); return out.str();
} }

View File

@@ -239,6 +239,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
{ {
return; return;
} }
// if (!GetMaster() || !GetMaster()->IsInWorld() || !GetMaster()->GetSession() || // if (!GetMaster() || !GetMaster()->IsInWorld() || !GetMaster()->GetSession() ||
// GetMaster()->GetSession()->isLogingOut()) { // GetMaster()->GetSession()->isLogingOut()) {
// return; // return;
@@ -301,6 +302,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
// bot->GetMotionMaster()->Clear(); // bot->GetMotionMaster()->Clear();
// bot->GetMotionMaster()->MoveIdle(); // bot->GetMotionMaster()->MoveIdle();
// } // }
// cheat options // cheat options
if (bot->IsAlive() && ((uint32)GetCheat() > 0 || (uint32)sPlayerbotAIConfig->botCheatMask > 0)) if (bot->IsAlive() && ((uint32)GetCheat() > 0 || (uint32)sPlayerbotAIConfig->botCheatMask > 0))
{ {
@@ -3926,7 +3928,10 @@ Player* PlayerbotAI::GetGroupMaster()
uint32 PlayerbotAI::GetFixedBotNumer(BotTypeNumber typeNumber, uint32 maxNum, float cyclePerMin) uint32 PlayerbotAI::GetFixedBotNumer(BotTypeNumber typeNumber, uint32 maxNum, float cyclePerMin)
{ {
uint32 randseed = rand32(); // Seed random number //deterministic seed
uint8 seedNumber = uint8(typeNumber);
std::mt19937 rng(seedNumber);
uint32 randseed = rng(); // Seed random number
uint32 randnum = bot->GetGUID().GetCounter() + randseed; // Semi-random but fixed number for each bot. uint32 randnum = bot->GetGUID().GetCounter() + randseed; // Semi-random but fixed number for each bot.
if (cyclePerMin > 0) if (cyclePerMin > 0)
@@ -3936,8 +3941,7 @@ uint32 PlayerbotAI::GetFixedBotNumer(BotTypeNumber typeNumber, uint32 maxNum, fl
randnum += cycle; // Make the random number cylce. randnum += cycle; // Make the random number cylce.
} }
randnum = randnum = (randnum % (maxNum + 1)); // Loops the randomnumber at maxNum. Bassically removes all the numbers above 99.
(randnum % (maxNum + 1)); // Loops the randomnumber at maxNum. Bassically removes all the numbers above 99.
return randnum; // Now we have a number unique for each bot between 0 and maxNum that increases by cyclePerMin. return randnum; // Now we have a number unique for each bot between 0 and maxNum that increases by cyclePerMin.
} }
@@ -4075,18 +4079,15 @@ inline bool HasRealPlayers(Map* map)
return false; return false;
} }
bool PlayerbotAI::AllowActive(ActivityType activityType) ActivePiorityType PlayerbotAI::GetPriorityType(ActivityType activityType)
{ {
// General exceptions // First priority - priorities disabled or has player master. Always active.
if (activityType == PACKET_ACTIVITY) if (HasRealPlayerMaster())
return true; return ActivePiorityType::HAS_REAL_PLAYER_MASTER;
if (GetMaster()) // Has player master. Always active. // Self bot in a group with a bot master.
{ if (IsRealPlayer())
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(GetMaster()); return ActivePiorityType::IS_REAL_PLAYER;
if (!masterBotAI || masterBotAI->IsRealPlayer())
return true;
}
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (group) if (group)
@@ -4100,21 +4101,49 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
if (member == bot) if (member == bot)
continue; continue;
//IN_GROUP_WITH_REAL_PLAYER
PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member); PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member);
if (!memberBotAI || memberBotAI->HasRealPlayerMaster()) if (!memberBotAI || memberBotAI->HasRealPlayerMaster())
return true; return ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER;
//IN_GROUP_WITH_REAL_PLAYER
if (group->IsLeader(member->GetGUID())) if (group->IsLeader(member->GetGUID()))
{
if (!memberBotAI->AllowActivity(PARTY_ACTIVITY)) if (!memberBotAI->AllowActivity(PARTY_ACTIVITY))
return false; return ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER;
}
} }
} }
if (!WorldPosition(bot).isOverworld()) // bg, raid, dungeon //IN_INSTANCE
return true; if (bot->IsBeingTeleported()) // Allow activity while teleportation.
return ActivePiorityType::IN_INSTANCE;
if (bot->InBattlegroundQueue()) // In bg queue. Speed up bg queue/join. //IN_INSTANCE
return true; if (!WorldPosition(bot).isOverworld())
return ActivePiorityType::IN_INSTANCE;
//VISIBLE_FOR_PLAYER
if (HasPlayerNearby(sPlayerbotAIConfig->reactDistance))
return ActivePiorityType::VISIBLE_FOR_PLAYER;
//IN_COMBAT
if (activityType != OUT_OF_PARTY_ACTIVITY && activityType != PACKET_ACTIVITY)
{
// Is in combat, defend yourself.
if (bot->IsInCombat())
return ActivePiorityType::IN_COMBAT;
}
//NEARBY_PLAYER
if (HasPlayerNearby(300.f))
return ActivePiorityType::NEARBY_PLAYER;
//if (sPlayerbotAIConfig->IsFreeAltBot(bot) || HasStrategy("travel once", BotState::BOT_STATE_NON_COMBAT))
// return ActivePiorityType::IS_ALWAYS_ACTIVE;
if (bot->InBattlegroundQueue())
return ActivePiorityType::IN_BG_QUEUE;
bool isLFG = false; bool isLFG = false;
if (group) if (group)
@@ -4124,62 +4153,172 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
isLFG = true; isLFG = true;
} }
} }
if (sLFGMgr->GetState(bot->GetGUID()) != lfg::LFG_STATE_NONE) if (sLFGMgr->GetState(bot->GetGUID()) != lfg::LFG_STATE_NONE)
{ {
isLFG = true; isLFG = true;
} }
if (isLFG) if (isLFG)
return true; return ActivePiorityType::IN_LFG;
if (activityType != OUT_OF_PARTY_ACTIVITY && activityType != PACKET_ACTIVITY) // Is in combat. Defend yourself. //IN_EMPTY_SERVER
if (bot->IsInCombat()) if (sRandomPlayerbotMgr->GetPlayers().empty())
return true; return ActivePiorityType::IN_EMPTY_SERVER;
if (HasPlayerNearby(300.f)) // Player is near. Always active. //PLAYER_FRIEND (on friendlist of real player) (needs to be tested for stability)
return true; /*for (auto& player : sRandomPlayerbotMgr->GetPlayers())
// friends always active
// HasFriend sometimes cause crash, disable
// for (auto& player : sRandomPlayerbotMgr->GetPlayers())
// {
// if (!player || !player->IsInWorld())
// continue;
// if (player->GetSocial()->HasFriend(bot->GetGUID()))
// return true;
// }
if (activityType == OUT_OF_PARTY_ACTIVITY ||
activityType == GRIND_ACTIVITY) // Many bots nearby. Do not do heavy area checks.
if (HasManyPlayersNearby())
return false;
// Bots don't need to move using PathGenerator.
if (activityType == DETAILED_MOVE_ACTIVITY)
return false;
// All exceptions are now done.
// Below is code to have a specified % of bots active at all times.
// The default is 10%. With 0.1% of all bots going active or inactive each minute.
if (sPlayerbotAIConfig->botActiveAlone <= 0)
return false;
uint32 mod = sPlayerbotAIConfig->botActiveAlone > 100 ? 100 : sPlayerbotAIConfig->botActiveAlone;
if (sPlayerbotAIConfig->botActiveAloneAutoScale)
{ {
mod = AutoScaleActivity(mod); if (!player || !player->IsInWorld())
continue;
if (player->GetSocial() &&
bot->GetGUID() &&
player->GetSocial()->HasFriend(bot->GetGUID()))
return ActivePiorityType::PLAYER_FRIEND;
}*/
//PLAYER_GUILD (contains real player)
if (IsInRealGuild())
return ActivePiorityType::PLAYER_GUILD;
//IN_INACTIVE_MAP
if (bot->IsBeingTeleported() || !bot->IsInWorld() || !HasRealPlayers(bot->GetMap()))
return ActivePiorityType::IN_INACTIVE_MAP;
//IN_ACTIVE_MAP
if (!bot->IsBeingTeleported() && bot->IsInWorld() && HasRealPlayers(bot->GetMap()))
return ActivePiorityType::IN_ACTIVE_MAP;
// IN_ACTIVE_AREA
if (activityType == OUT_OF_PARTY_ACTIVITY || activityType == GRIND_ACTIVITY)
{
if (HasManyPlayersNearby(20, sPlayerbotAIConfig->sightDistance))
return ActivePiorityType::IN_ACTIVE_AREA;
} }
uint32 ActivityNumber = return ActivePiorityType::IN_ACTIVE_AREA;
GetFixedBotNumer(BotTypeNumber::ACTIVITY_TYPE_NUMBER, 100, }
sPlayerbotAIConfig->botActiveAlone * static_cast<float>(mod) / 100 * 0.01f);
return ActivityNumber <= // Returns the lower and upper bracket for bots to be active.
(sPlayerbotAIConfig->botActiveAlone * mod) / // Ie. { 10, 20 } means all bots in this bracket will be inactive below 10% activityMod,
100; // The given percentage of bots should be active and rotate 1% of those active bots each minute. // and will be active above 20% activityMod and scale between those values.
std::pair<uint32, uint32> PlayerbotAI::GetPriorityBracket(ActivePiorityType type)
{
switch (type)
{
case ActivePiorityType::HAS_REAL_PLAYER_MASTER:
case ActivePiorityType::IS_REAL_PLAYER:
case ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER:
case ActivePiorityType::IN_INSTANCE:
case ActivePiorityType::VISIBLE_FOR_PLAYER:
return {0, 0};
case ActivePiorityType::IS_ALWAYS_ACTIVE:
case ActivePiorityType::IN_COMBAT:
return {0, 10};
case ActivePiorityType::IN_BG_QUEUE:
return {0, 20};
case ActivePiorityType::IN_LFG:
return {0, 30};
case ActivePiorityType::NEARBY_PLAYER:
return {0, 40};
case ActivePiorityType::PLAYER_FRIEND:
case ActivePiorityType::PLAYER_GUILD:
return {0, 50};
case ActivePiorityType::IN_ACTIVE_AREA:
case ActivePiorityType::IN_EMPTY_SERVER:
return {50, 100};
case ActivePiorityType::IN_ACTIVE_MAP:
return {70, 100};
case ActivePiorityType::IN_INACTIVE_MAP:
return {80, 100};
default:
return {90, 100};
}
return {90, 100};
}
bool PlayerbotAI::AllowActive(ActivityType activityType)
{
// General exceptions
if (activityType == PACKET_ACTIVITY)
return true;
ActivePiorityType type = GetPriorityType(activityType);
if (activityType == DETAILED_MOVE_ACTIVITY)
{
switch (type)
{
case ActivePiorityType::HAS_REAL_PLAYER_MASTER:
case ActivePiorityType::IS_REAL_PLAYER:
case ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER:
case ActivePiorityType::IN_INSTANCE:
case ActivePiorityType::VISIBLE_FOR_PLAYER:
case ActivePiorityType::IN_COMBAT:
case ActivePiorityType::NEARBY_PLAYER:
return true;
break;
case ActivePiorityType::IS_ALWAYS_ACTIVE:
case ActivePiorityType::IN_BG_QUEUE:
case ActivePiorityType::IN_LFG:
case ActivePiorityType::PLAYER_FRIEND:
case ActivePiorityType::PLAYER_GUILD:
case ActivePiorityType::IN_ACTIVE_AREA:
case ActivePiorityType::IN_EMPTY_SERVER:
case ActivePiorityType::IN_ACTIVE_MAP:
case ActivePiorityType::IN_INACTIVE_MAP:
default:
break;
}
}
else if (activityType == REACT_ACTIVITY)
{
switch (type)
{
case ActivePiorityType::HAS_REAL_PLAYER_MASTER:
case ActivePiorityType::IS_REAL_PLAYER:
case ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER:
case ActivePiorityType::IN_INSTANCE:
case ActivePiorityType::VISIBLE_FOR_PLAYER:
case ActivePiorityType::IS_ALWAYS_ACTIVE:
case ActivePiorityType::IN_COMBAT:
return true;
break;
case ActivePiorityType::NEARBY_PLAYER:
case ActivePiorityType::IN_BG_QUEUE:
case ActivePiorityType::IN_LFG:
case ActivePiorityType::PLAYER_FRIEND:
case ActivePiorityType::PLAYER_GUILD:
case ActivePiorityType::IN_ACTIVE_AREA:
case ActivePiorityType::IN_EMPTY_SERVER:
case ActivePiorityType::IN_ACTIVE_MAP:
case ActivePiorityType::IN_INACTIVE_MAP:
default:
return false;
break;
}
}
// GetPriorityBracket acitivity
float normalizedBotActiveAlone = sPlayerbotAIConfig->botActiveAlone > 100 ? 100 : sPlayerbotAIConfig->botActiveAlone;
float activePerc = normalizedBotActiveAlone;
if (sPlayerbotAIConfig->botActiveAloneSmartScale &&
bot->GetLevel() >= sPlayerbotAIConfig->botActiveAloneSmartScaleWhenMinLevel &&
bot->GetLevel() <= sPlayerbotAIConfig->botActiveAloneSmartScaleWhenMaxLevel)
{
std::pair<uint8, uint8> priorityBracket = GetPriorityBracket(type);
if (!priorityBracket.second) return true;
float activityPercentage = sRandomPlayerbotMgr->getActivityPercentage();
if (priorityBracket.first >= activityPercentage) return false;
if (priorityBracket.second <= activityPercentage && priorityBracket.second < 100) return true;
activePerc = (activityPercentage - priorityBracket.first) / (priorityBracket.second - priorityBracket.first);
activePerc *= (priorityBracket.second == 100) ? normalizedBotActiveAlone : 100;
}
// The last number if the amount it cycles per min. Currently set to 1% of the active bots.
uint32 ActivityNumber = GetFixedBotNumer(BotTypeNumber::ACTIVITY_TYPE_NUMBER, 100, activePerc * 0.01f);
// The given percentage of bots should be active and rotate 1% of those active bots each minute.
return ActivityNumber <= (activePerc);
} }
bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow) bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
@@ -4196,31 +4335,6 @@ bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
return allowed; return allowed;
} }
uint32 PlayerbotAI::AutoScaleActivity(uint32 mod)
{
uint32 maxDiff = sWorldUpdateTime.GetAverageUpdateTime();
if (maxDiff > 500) return 0;
if (maxDiff > 250)
{
if (Map* map = bot->GetMap())
{
if (map->GetEntry()->IsWorldMap() &&
(!HasRealPlayers(map) ||
!map->IsGridLoaded(bot->GetPositionX(), bot->GetPositionY())))
return 0;
}
return (mod * 1) / 10;
}
if (maxDiff > 200) return (mod * 3) / 10;
if (maxDiff > 150) return (mod * 5) / 10;
if (maxDiff > 100) return (mod * 6) / 10;
if (maxDiff > 80) return (mod * 9) / 10;
return mod;
}
bool PlayerbotAI::IsOpposing(Player* player) { return IsOpposing(player->getRace(), bot->getRace()); } bool PlayerbotAI::IsOpposing(Player* player) { return IsOpposing(player->getRace(), bot->getRace()); }
bool PlayerbotAI::IsOpposing(uint8 race1, uint8 race2) bool PlayerbotAI::IsOpposing(uint8 race1, uint8 race2)
@@ -5328,15 +5442,29 @@ bool PlayerbotAI::CanMove()
if (IsInVehicle() && !IsInVehicle(true)) if (IsInVehicle() && !IsInVehicle(true))
return false; return false;
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) || if (bot->isFrozen() ||
bot->IsBeingTeleported() || bot->isInRoots() || bot->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION) || bot->IsPolymorphed() ||
bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) || bot->IsCharmed() || bot->HasAuraType(SPELL_AURA_MOD_STUN) || (bot->isDead() && !bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) ||
bot->HasUnitState(UNIT_STATE_IN_FLIGHT) || bot->HasUnitState(UNIT_STATE_LOST_CONTROL)) bot->IsBeingTeleported() ||
bot->isInRoots() ||
bot->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION) ||
bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) ||
bot->IsCharmed() ||
bot->HasAuraType(SPELL_AURA_MOD_STUN) ||
bot->HasUnitState(UNIT_STATE_IN_FLIGHT) ||
bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
return false; return false;
return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE; return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE;
} }
bool PlayerbotAI::IsTaxiFlying()
{
return bot->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) &&
bot->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING);
}
bool PlayerbotAI::IsInRealGuild() bool PlayerbotAI::IsInRealGuild()
{ {
if (!bot->GetGuildId()) if (!bot->GetGuildId())

View File

@@ -241,6 +241,27 @@ enum class GuilderType : uint8
VERY_LARGE = 250 VERY_LARGE = 250
}; };
enum class ActivePiorityType : uint8
{
IS_REAL_PLAYER = 0,
HAS_REAL_PLAYER_MASTER = 1,
IN_GROUP_WITH_REAL_PLAYER = 2,
IN_INSTANCE = 3,
VISIBLE_FOR_PLAYER = 4,
IS_ALWAYS_ACTIVE = 5,
IN_COMBAT = 6,
IN_BG_QUEUE = 7,
IN_LFG = 8,
NEARBY_PLAYER = 9,
PLAYER_FRIEND = 10,
PLAYER_GUILD = 11,
IN_ACTIVE_AREA = 12,
IN_ACTIVE_MAP = 13,
IN_INACTIVE_MAP = 14,
IN_EMPTY_SERVER = 15,
MAX_TYPE
};
enum ActivityType enum ActivityType
{ {
GRIND_ACTIVITY = 1, GRIND_ACTIVITY = 1,
@@ -250,8 +271,8 @@ enum ActivityType
PACKET_ACTIVITY = 5, PACKET_ACTIVITY = 5,
DETAILED_MOVE_ACTIVITY = 6, DETAILED_MOVE_ACTIVITY = 6,
PARTY_ACTIVITY = 7, PARTY_ACTIVITY = 7,
ALL_ACTIVITY = 8, REACT_ACTIVITY = 8,
ALL_ACTIVITY = 9,
MAX_ACTIVITY_TYPE MAX_ACTIVITY_TYPE
}; };
@@ -525,9 +546,10 @@ public:
bool HasPlayerNearby(WorldPosition* pos, float range = sPlayerbotAIConfig->reactDistance); bool HasPlayerNearby(WorldPosition* pos, float range = sPlayerbotAIConfig->reactDistance);
bool HasPlayerNearby(float range = sPlayerbotAIConfig->reactDistance); bool HasPlayerNearby(float range = sPlayerbotAIConfig->reactDistance);
bool HasManyPlayersNearby(uint32 trigerrValue = 20, float range = sPlayerbotAIConfig->sightDistance); bool HasManyPlayersNearby(uint32 trigerrValue = 20, float range = sPlayerbotAIConfig->sightDistance);
ActivePiorityType GetPriorityType(ActivityType activityType);
std::pair<uint32, uint32> GetPriorityBracket(ActivePiorityType type);
bool AllowActive(ActivityType activityType); bool AllowActive(ActivityType activityType);
bool AllowActivity(ActivityType activityType = ALL_ACTIVITY, bool checkNow = false); bool AllowActivity(ActivityType activityType = ALL_ACTIVITY, bool checkNow = false);
uint32 AutoScaleActivity(uint32 mod);
// Check if player is safe to use. // Check if player is safe to use.
bool IsSafe(Player* player); bool IsSafe(Player* player);
@@ -554,6 +576,7 @@ public:
void ResetJumpDestination() { jumpDestination = Position(); } void ResetJumpDestination() { jumpDestination = Position(); }
bool CanMove(); bool CanMove();
bool IsTaxiFlying();
bool IsInRealGuild(); bool IsInRealGuild();
static std::vector<std::string> dispel_whitelist; static std::vector<std::string> dispel_whitelist;
bool EqualLowercaseName(std::string s1, std::string s2); bool EqualLowercaseName(std::string s1, std::string s2);

View File

@@ -465,11 +465,15 @@ bool PlayerbotAIConfig::Initialize()
playerbotsXPrate = sConfigMgr->GetOption<int32>("AiPlayerbot.KillXPRate", 1); playerbotsXPrate = sConfigMgr->GetOption<int32>("AiPlayerbot.KillXPRate", 1);
disableDeathKnightLogin = sConfigMgr->GetOption<bool>("AiPlayerbot.DisableDeathKnightLogin", 0); disableDeathKnightLogin = sConfigMgr->GetOption<bool>("AiPlayerbot.DisableDeathKnightLogin", 0);
botActiveAlone = sConfigMgr->GetOption<int32>("AiPlayerbot.BotActiveAlone", 100); botActiveAlone = sConfigMgr->GetOption<int32>("AiPlayerbot.BotActiveAlone", 100);
botActiveAloneAutoScale = sConfigMgr->GetOption<bool>("AiPlayerbot.botActiveAloneAutoScale", true); botActiveAloneSmartScale = sConfigMgr->GetOption<bool>("AiPlayerbot.botActiveAloneSmartScale", 1);
botActiveAloneSmartScaleWhenMinLevel =
enablePrototypePerformanceDiff = sConfigMgr->GetOption<bool>("AiPlayerbot.EnablePrototypePerformanceDiff", false); sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel", 1);
diffWithPlayer = sConfigMgr->GetOption<int32>("AiPlayerbot.DiffWithPlayer", 100); botActiveAloneSmartScaleWhenMaxLevel =
diffEmpty = sConfigMgr->GetOption<int32>("AiPlayerbot.DiffEmpty", 200); sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel", 80);
botActiveAloneSmartScaleDiffWithPlayer =
sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleDiffWithPlayer", 100);
botActiveAloneSmartScaleDiffEmpty =
sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleDiffEmpty", 200);
randombotsWalkingRPG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG", false); randombotsWalkingRPG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG", false);
randombotsWalkingRPGInDoors = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG.InDoors", false); randombotsWalkingRPGInDoors = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG.InDoors", false);

View File

@@ -263,11 +263,11 @@ public:
uint32 playerbotsXPrate; uint32 playerbotsXPrate;
bool disableDeathKnightLogin; bool disableDeathKnightLogin;
uint32 botActiveAlone; uint32 botActiveAlone;
bool botActiveAloneAutoScale; bool botActiveAloneSmartScale;
uint32 botActiveAloneSmartScaleWhenMinLevel;
uint32 enablePrototypePerformanceDiff; uint32 botActiveAloneSmartScaleWhenMaxLevel;
uint32 diffWithPlayer; uint32 botActiveAloneSmartScaleDiffWithPlayer;
uint32 diffEmpty; uint32 botActiveAloneSmartScaleDiffEmpty;
bool freeMethodLoot; bool freeMethodLoot;
int32 lootRollLevel; int32 lootRollLevel;

View File

@@ -100,6 +100,8 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
Player* bot = botSession->GetPlayer(); Player* bot = botSession->GetPlayer();
if (!bot) if (!bot)
{ {
// Log para debug
LOG_ERROR("mod-playerbots", "Bot player could not be loaded for account ID: {}", botAccountId);
botSession->LogoutPlayer(true); botSession->LogoutPlayer(true);
delete botSession; delete botSession;
botLoading.erase(holder.GetGuid()); botLoading.erase(holder.GetGuid());
@@ -108,6 +110,14 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
uint32 masterAccount = holder.GetMasterAccountId(); uint32 masterAccount = holder.GetMasterAccountId();
WorldSession* masterSession = masterAccount ? sWorld->FindSession(masterAccount) : nullptr; WorldSession* masterSession = masterAccount ? sWorld->FindSession(masterAccount) : nullptr;
// Check if masterSession->GetPlayer() is valid
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
if (masterSession && !masterPlayer)
{
LOG_ERROR("mod-playerbots", "Master session found but no player is associated for master account ID: {}", masterAccount);
}
std::ostringstream out; std::ostringstream out;
bool allowed = false; bool allowed = false;
if (botAccountId == masterAccount) if (botAccountId == masterAccount)
@@ -115,7 +125,7 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
allowed = true; allowed = true;
} }
else if (masterSession && sPlayerbotAIConfig->allowGuildBots && bot->GetGuildId() != 0 && else if (masterSession && sPlayerbotAIConfig->allowGuildBots && bot->GetGuildId() != 0 &&
bot->GetGuildId() == masterSession->GetPlayer()->GetGuildId()) bot->GetGuildId() == masterPlayer->GetGuildId())
{ {
allowed = true; allowed = true;
} }
@@ -129,10 +139,14 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
out << "Failure: You are not allowed to control bot " << bot->GetName().c_str(); out << "Failure: You are not allowed to control bot " << bot->GetName().c_str();
} }
if (allowed && masterSession) if (allowed && masterSession && masterPlayer)
{ {
Player* player = masterSession->GetPlayer(); PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(masterPlayer);
PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(player); if (!mgr)
{
LOG_ERROR("mod-playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue());
}
uint32 count = mgr->GetPlayerbotsCount(); uint32 count = mgr->GetPlayerbotsCount();
uint32 cls_count = mgr->GetPlayerbotsCountByClass(bot->getClass()); uint32 cls_count = mgr->GetPlayerbotsCountByClass(bot->getClass());
if (count >= sPlayerbotAIConfig->maxAddedBots) if (count >= sPlayerbotAIConfig->maxAddedBots)
@@ -428,14 +442,17 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI) if (!botAI)
{ {
// Log a warning here to indicate that the botAI is null
LOG_ERROR("mod-playerbots", "PlayerbotAI is null for bot with GUID: {}", bot->GetGUID().GetRawValue());
return; return;
} }
Player* master = botAI->GetMaster(); Player* master = botAI->GetMaster();
if (master) if (!master)
{ {
ObjectGuid masterGuid = master->GetGUID(); // Log a warning to indicate that the master is null
if (master->GetGroup() && !master->GetGroup()->IsLeader(masterGuid)) LOG_ERROR("mod-playerbots", "Master is null for bot with GUID: {}", bot->GetGUID().GetRawValue());
master->GetGroup()->ChangeLeader(masterGuid); return;
} }
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();

View File

@@ -292,12 +292,8 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/)
if (!sPlayerbotAIConfig->randomBotAutologin || !sPlayerbotAIConfig->enabled) if (!sPlayerbotAIConfig->randomBotAutologin || !sPlayerbotAIConfig->enabled)
return; return;
if (sPlayerbotAIConfig->enablePrototypePerformanceDiff) if (sPlayerbotAIConfig->botActiveAloneSmartScale)
{ {
LOG_INFO("playerbots", "---------------------------------------");
LOG_INFO("playerbots",
"PROTOTYPE: Playerbot performance enhancements are active. Issues and instability may occur.");
LOG_INFO("playerbots", "---------------------------------------");
ScaleBotActivity(); ScaleBotActivity();
} }
@@ -414,8 +410,9 @@ void RandomPlayerbotMgr::ScaleBotActivity()
// max/min activity // max/min activity
// % increase/decrease wanted diff , avg diff // % increase/decrease wanted diff , avg diff
float activityPercentageMod = pid.calculate( float activityPercentageMod = pid.calculate(sRandomPlayerbotMgr->GetPlayers().empty() ?
sRandomPlayerbotMgr->GetPlayers().empty() ? sPlayerbotAIConfig->diffEmpty : sPlayerbotAIConfig->diffWithPlayer, sPlayerbotAIConfig->botActiveAloneSmartScaleDiffEmpty :
sPlayerbotAIConfig->botActiveAloneSmartScaleDiffWithPlayer,
sWorldUpdateTime.GetAverageUpdateTime()); sWorldUpdateTime.GetAverageUpdateTime());
activityPercentage = activityPercentageMod + 50; activityPercentage = activityPercentageMod + 50;
@@ -1108,6 +1105,9 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
bool RandomPlayerbotMgr::ProcessBot(Player* player) bool RandomPlayerbotMgr::ProcessBot(Player* player)
{ {
if (!player || !player->IsInWorld() || player->IsBeingTeleported() || player->GetSession()->isLogingOut())
return false;
uint32 bot = player->GetGUID().GetCounter(); uint32 bot = player->GetGUID().GetCounter();
if (player->InBattleground()) if (player->InBattleground())
@@ -1262,9 +1262,7 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
if (botAI) if (botAI)
{ {
// ignore when in when taxi with boat/zeppelin and has players nearby // ignore when in when taxi with boat/zeppelin and has players nearby
if (bot->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && if (botAI->IsTaxiFlying() && botAI->HasPlayerNearby())
bot->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING) &&
botAI->HasPlayerNearby())
return; return;
} }

View File

@@ -234,16 +234,15 @@ bool BGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battlegroun
return false; return false;
TeamId teamId = bot->GetTeamId(); TeamId teamId = bot->GetTeamId();
bool noLag = sWorldUpdateTime.GetAverageUpdateTime() < (sRandomPlayerbotMgr->GetPlayers().empty() bool noLag = sWorldUpdateTime.GetAverageUpdateTime() < (sRandomPlayerbotMgr->GetPlayers().empty() ?
? sPlayerbotAIConfig->diffEmpty sPlayerbotAIConfig->botActiveAloneSmartScaleDiffEmpty :
: sPlayerbotAIConfig->diffWithPlayer) * sPlayerbotAIConfig->botActiveAloneSmartScaleDiffWithPlayer) * 1.1;
1.1;
uint32 BracketSize = bg->GetMaxPlayersPerTeam() * 2; uint32 BracketSize = bg->GetMaxPlayersPerTeam() * 2;
uint32 TeamSize = bg->GetMaxPlayersPerTeam(); uint32 TeamSize = bg->GetMaxPlayersPerTeam();
// If performance diff is enabled, only queue if there is no lag // If performance diff is enabled, only queue if there is no lag
if (sPlayerbotAIConfig->enablePrototypePerformanceDiff && !noLag) if (sPlayerbotAIConfig->botActiveAloneSmartScale && !noLag)
return false; return false;
// If the bot is in a group, only the leader can queue // If the bot is in a group, only the leader can queue
@@ -578,16 +577,15 @@ bool FreeBGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battleg
return false; return false;
TeamId teamId = bot->GetTeamId(); TeamId teamId = bot->GetTeamId();
bool noLag = sWorldUpdateTime.GetAverageUpdateTime() < (sRandomPlayerbotMgr->GetPlayers().empty() bool noLag = sWorldUpdateTime.GetAverageUpdateTime() < (sRandomPlayerbotMgr->GetPlayers().empty() ?
? sPlayerbotAIConfig->diffEmpty sPlayerbotAIConfig->botActiveAloneSmartScaleDiffEmpty :
: sPlayerbotAIConfig->diffWithPlayer) * sPlayerbotAIConfig->botActiveAloneSmartScaleDiffWithPlayer) * 1.1;
1.1;
uint32 BracketSize = bg->GetMaxPlayersPerTeam() * 2; uint32 BracketSize = bg->GetMaxPlayersPerTeam() * 2;
uint32 TeamSize = bg->GetMaxPlayersPerTeam(); uint32 TeamSize = bg->GetMaxPlayersPerTeam();
// If performance diff is enabled, only queue if there is no lag // If performance diff is enabled, only queue if there is no lag
if (sPlayerbotAIConfig->enablePrototypePerformanceDiff && !noLag) if (sPlayerbotAIConfig->botActiveAloneSmartScale && !noLag)
return false; return false;
// If the bot is in a group, only the leader can queue // If the bot is in a group, only the leader can queue

View File

@@ -58,55 +58,92 @@ bool DropQuestAction::Execute(Event event)
return true; return true;
} }
bool CleanQuestLogAction::Execute(Event event) bool CleanQuestLogAction::Execute(Event event)
{ {
Player* requester = event.getOwner() ? event.getOwner() : GetMaster(); Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
std::string link = event.getParam(); if (!requester)
if (botAI->HasActivePlayerMaster() || !sRandomPlayerbotMgr->IsRandomBot(bot)) {
botAI->TellMaster("No event owner detected");
return false; return false;
uint8 totalQuests = 0;
// Count the total quests
DropQuestType(totalQuests);
if (MAX_QUEST_LOG_SIZE - totalQuests > 6)
{
// Drop failed quests
DropQuestType(totalQuests, MAX_QUEST_LOG_SIZE, true, true);
return true;
} }
// Only drop gray quests when able to fight proper lvl quests. // Only output this message if "debug rpg" strategy is enabled
if (AI_VALUE(bool, "can fight equal")) if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{ {
// Drop gray/red quests. botAI->TellMaster("Clean Quest Log command received, removing grey/trivial quests...");
DropQuestType(totalQuests, MAX_QUEST_LOG_SIZE - 6);
// Drop gray/red quests with progress.
DropQuestType(totalQuests, MAX_QUEST_LOG_SIZE - 6, false, true);
// Drop gray/red completed quests.
DropQuestType(totalQuests, MAX_QUEST_LOG_SIZE - 6, false, true, true);
} }
if (MAX_QUEST_LOG_SIZE - totalQuests > 4) uint8 botLevel = bot->GetLevel(); // Get bot's level
return true; uint8 numQuest = 0;
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{
if (bot->GetQuestSlotQuestId(slot))
{
numQuest++;
}
}
DropQuestType(totalQuests, MAX_QUEST_LOG_SIZE - 4, true); // Drop quests without progress. for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{
uint32 questId = bot->GetQuestSlotQuestId(slot);
if (!questId)
continue;
if (MAX_QUEST_LOG_SIZE - totalQuests > 2) const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
return true; if (!quest)
continue;
DropQuestType(totalQuests, MAX_QUEST_LOG_SIZE - 2, true, true); // Drop quests with progress. // Determine if quest is trivial by comparing levels
int32 questLevel = quest->GetQuestLevel();
if (questLevel == -1) // For scaling quests, default to bot level
{
questLevel = botLevel;
}
if (MAX_QUEST_LOG_SIZE - totalQuests > 0) // Check if the quest is trivial (grey) for the bot
return true; if ((botLevel - questLevel) >= 5)
{
// Output only if "debug rpg" strategy is enabled
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] will be removed because it is trivial (grey).");
}
DropQuestType(totalQuests, MAX_QUEST_LOG_SIZE - 1, true, true, true); // Drop completed quests. // Remove quest
bot->SetQuestSlot(slot, 0);
bot->TakeQuestSourceItem(questId, false);
bot->SetQuestStatus(questId, QUEST_STATUS_NONE);
bot->RemoveRewardedQuest(questId);
if (MAX_QUEST_LOG_SIZE - totalQuests > 0) numQuest--;
return true;
return false; if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{
const std::string text_quest = ChatHelper::FormatQuest(quest);
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), quest->GetTitle());
bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL);
}
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] has been removed.");
}
}
else
{
// Only output if "debug rpg" strategy is enabled
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] is not trivial and will be kept.");
}
}
}
return true;
} }
void CleanQuestLogAction::DropQuestType(uint8& numQuest, uint8 wantNum, bool isGreen, bool hasProgress, bool isComplete) void CleanQuestLogAction::DropQuestType(uint8& numQuest, uint8 wantNum, bool isGreen, bool hasProgress, bool isComplete)
{ {
std::vector<uint8> slots; std::vector<uint8> slots;

View File

@@ -30,6 +30,8 @@ bool PartyCommandAction::Execute(Event event)
Player* master = GetMaster(); Player* master = GetMaster();
if (master && member == master->GetName()) if (master && member == master->GetName())
return Leave(bot); return Leave(bot);
botAI->Reset();
return false; return false;
} }
@@ -62,6 +64,8 @@ bool UninviteAction::Execute(Event event)
if (bot->GetGUID() == guid) if (bot->GetGUID() == guid)
return Leave(bot); return Leave(bot);
} }
botAI->Reset();
return false; return false;
} }
@@ -160,6 +164,8 @@ bool LeaveFarAwayAction::isUseful()
{ {
return true; return true;
} }
botAI->Reset();
return false; return false;
} }

View File

@@ -79,7 +79,7 @@ bool RpgAction::SetNextRpgAction()
{ {
NextAction* nextAction = nextActions[i]; NextAction* nextAction = nextActions[i];
if (nextAction->getRelevance() > 2.0f) if (nextAction->getRelevance() > 5.0f)
continue; continue;
if (!isChecked && !trigger->IsActive()) if (!isChecked && !trigger->IsActive())
@@ -92,7 +92,7 @@ bool RpgAction::SetNextRpgAction()
continue; continue;
actions.push_back(action); actions.push_back(action);
relevances.push_back((nextAction->getRelevance() - 1) * 1000); relevances.push_back((nextAction->getRelevance() - 1) * 500);
} }
NextAction::destroy(nextActions); NextAction::destroy(nextActions);
} }