Compare commits

..

7 Commits

Author SHA1 Message Date
bashermens
965d300203 [HOTFIX] Revert "Fix/Feat: Stop bots in party from PVP when master isn't, and … (#2003)
…PVP probablity system (#1914)"

This reverts commit 02e8465a3b.


[worldserver_gdb_20260111_000520.log](https://github.com/user-attachments/files/24547263/worldserver_gdb_20260111_000520.log)


Noticed the server kept crashing all the sudden, first thought it was
local issue but see dump.
2026-01-11 01:52:25 +01:00
privatecore
dc55ecfd9c Fix wrong logic when processing quest reward spells during maintenance level-up action (#2001)
The previous behavior failed cuz if a spell has no
`SPELL_EFFECT_LEARN_SPELL`, bots learned the original spell from
`quest_template` rewards w/o any checks, when they should never learn
any spells specified in the `RewardSpell` and/or `RewardDisplaySpell`.

PR: https://github.com/mod-playerbots/mod-playerbots/pull/1996 Thx
@Wishmaster117 for the collaboration!

Fix issue https://github.com/mod-playerbots/mod-playerbots/issues/1759
2026-01-10 01:31:54 +01:00
Crow
59d6eb139e Exclude additional test enchants from bots (#1952)
Added two test enchants to exclude from the list of enchants that bots
can apply: 19927 and 44119. I also reordered the list to be in
alphanumeric order.

The important exclusion to add is 44119:
https://www.wowhead.com/wotlk/spell=44119/enchant-bracer-template

It is a test enchant that gives 34 AP on bracers; this is stronger than
any other bracer enchant for level 70 physical attackers so bots are
applying it.
2026-01-09 22:16:33 +01:00
Crow
00171a8c82 Minor fixes to .dist descriptions (#1945)
Clarified that MinEnchantingBotLevel also determines whether maintenance
will socket gems, in addition to applying enchants.

Fixed max iLevel for ZA gear in RandomGearScoreLimit description (iLevel
138 is the max that drops from the final boss, but the 3rd timed chest
gives iLevel 141 equipment).
2026-01-09 19:57:29 +01:00
NoxMax
02e8465a3b Fix/Feat: Stop bots in party from PVP when master isn't, and PVP probablity system (#1914)
There are two related PVP components in this PR. First is the simple yet
fundamental change to bot behaviour when they are in party. Right now
bots with a master will go into PVP when there's a nearby PVP target,
even if master is not in PVP. This absolutely should not happen. Bots
should not consider PVP at all if master is not in PVP. The fix is only
3 lines in EnemyPlayerValue

The second component is introducing PVP probabilities, to make decisions
more realistic. Right now even a level 1 bot will 100% go into PVP if it
sees a level 80 PVP target. They can't help themselves. So the change
here addresses that insanity. Several thresholds (subject to community
review) are introduced:

1. Bots will not fight a target 5 or more levels higher than them
2. Bots have a 25% chance starting a fight with a target +/- 4 levels
from them.
3. Bots have a 50% chance starting a fight with a target +/- 3 levels
from them.
4. Bots have a 75% chance starting a fight with a target +/- 2 levels
from them.
5. Bots have a 100% chance starting a fight with a target +/- 1 level
from them.
6. Bots have a 25% chance starting a fight with a target 5 or more
levels below them (ganking. thought it would be funny, and technically
realistic of player behaviour)

Exception of course exist for BG/Arena/Duel, and in capitals where bots
will always PVP. Also bots will always defend themselves if attacked.

Few notes: 
1. The if/ else if logic can be further simplified, but only if we use
thresholds that are different by one. So current logic allows for
flexibility of using values like 10/7/5/3 instead of 5/4/3/2.
2. The caching system is per-bot basis. So for some target X, if some
bot decides to attack it, another bot will make its own decision. At
first I used a simplified global system (thinking there might be
performance concerns) where if one bot decides to attack a target then
they all do, but when I switched to the more realistic per-bot basis, I
didn't see an effect on performance.
3. Variables are obviously not configurable right now. I'm starting to
see Bash's POV that maybe we have too many configs 😬 Still,
they can be easily exposed in the future, and if someone is reading this
then, remember to change constexpr to const.
2026-01-08 22:44:09 +01:00
Crow
f53a8704eb Remove InitSpells() (#1983)
InitSpells() is never called, and even if it were to be, it would do
nothing but call InitAvailableSpells().
2026-01-08 11:46:36 -08:00
bashermens
e5525958c8 [BUG FIX] Movement flag update called for bots while rooted (#1991)
Fix for the error "Attempted sending heartbeat with root flag for guid"

The core does not allow movement flag updates when unit/player has the
MOVEMENTFLAG_ROOT flag. Change scope bots alone.
2026-01-08 20:38:49 +01:00
8 changed files with 93 additions and 56 deletions

View File

@@ -21,7 +21,7 @@
# THRESHOLDS
# QUESTS
# COMBAT
# PALADIN BUFFS STRATEGIES
# GREATER BUFFS STRATEGIES
# CHEATS
# SPELLS
# FLIGHTPATH
@@ -91,17 +91,20 @@ AiPlayerbot.MinRandomBots = 500
AiPlayerbot.MaxRandomBots = 500
# Randombot accounts
# If you are not using any expansion at all, you may have to set this manually, in which case please ensure that RandomBotAccountCount is at least greater than (MaxRandomBots / 10 + AddClassAccountPoolSize)
# If you are not using any expansion at all, you may have to set this manually, in which case please
# ensure that RandomBotAccountCount is at least greater than (MaxRandomBots / 10 + AddClassAccountPoolSize)
# Default: 0 (automatic)
AiPlayerbot.RandomBotAccountCount = 0
# Delete all randombot accounts
# To apply this, set the number to 1 and run the Worldserver. Once deletion is complete, if you would like to recreate randombots, set the number back to 0 and rerun the Worldserver.
# To apply this, set the number to 1 and run the Worldserver. Once deletion is complete, if you would
# like to recreate randombots, set the number back to 0 and rerun the Worldserver.
AiPlayerbot.DeleteRandomBotAccounts = 0
# Disable randombots when no real players are logged in
# Default: 0 (randombots will login when server starts)
# If enabled, randombots will only log in 30 seconds (default) after a real player logs in, and will log out 300 seconds (default) after all real players log out
# If enabled, randombots will only log in 30 seconds (default) after a real player logs in, and will
# log out 300 seconds (default) after all real players log out
AiPlayerbot.DisabledWithoutRealPlayer = 0
AiPlayerbot.DisabledWithoutRealPlayerLoginDelay = 30
AiPlayerbot.DisabledWithoutRealPlayerLogoutDelay = 300
@@ -153,7 +156,8 @@ AiPlayerbot.AllowGuildBots = 1
AiPlayerbot.AllowTrustedAccountBots = 1
# Randombots will create guilds with nearby randombots
# Note: currently, randombots will not invite more bots after a guild is created (i.e., randombot guilds will have only the 10 initial randombots needed to sign the charter)
# Note: currently, randombots will not invite more bots after a guild is created,
# meaning randombot guilds will have only the 10 initial randombots needed to sign the charter
# Default: 0 (disabled)
AiPlayerbot.RandomBotGuildNearby = 0
@@ -187,7 +191,8 @@ AiPlayerbot.AutoInitOnly = 0
# Default: 1.0 (same with the player)
AiPlayerbot.AutoInitEquipLevelLimitRatio = 1.0
# Bot automatically trains spells when talking to trainer (yes = train all available spells as long as the bot has the money, free = auto trains with no money cost, no = only list spells)
# Bot automatically trains spells when talking to trainer
# yes = train all available spells as long as the bot has the money, free = auto trains with no money cost, no = only list spells
AiPlayerbot.AutoTrainSpells = yes
#
@@ -264,7 +269,7 @@ AiPlayerbot.UseFastFlyMountAtMinLevel = 70
AiPlayerbot.RandomBotShowHelmet = 1
AiPlayerbot.RandomBotShowCloak = 1
# Randombots and altbots automatically equip upgrades (bots will equip any item obtained from looting or a quest if they are sufficient upgrades)
# Randombots and altbots automatically equip any items in their inventory that are sufficient upgrades
# Default: 1 (enabled)
AiPlayerbot.AutoEquipUpgradeLoot = 1
@@ -312,7 +317,8 @@ AiPlayerbot.GlobalCooldown = 500
# Max wait time when moving
AiPlayerbot.MaxWaitForMove = 5000
# Disable use of MoveSplinePath for bot movement, will result in more erratic bot movement but means stun/snare/root/etc will work on bots (they wont reliably work when MoveSplinePath is enabled, though slowing effects still work ok)
# Enable/disable use of MoveSplinePath for bot movement
# Disabling will result in more erratic movement but is required for stuns, snares, and roots to work on bots
# Default: 0 - MoveSplinePath enabled
# 1 - MoveSplinePath disabled in BG/Arena only
# 2 - MoveSplinePath disabled everywhere
@@ -406,10 +412,11 @@ AiPlayerbot.HighMana = 65
#
#
# Bots pick their quest rewards (yes = picks the most useful item, no = list all rewards, ask = pick useful item and lists if multiple)
# Bots pick their quest rewards
# yes = picks the most useful item, no = list all rewards, ask = pick useful item and lists if multiple
AiPlayerbot.AutoPickReward = yes
# Sync quests with player (Bots will complete quests the moment you hand them in and will not loot quest items.)
# Sync quests with player (bots will complete quests the moment you hand them in and will not loot quest items.)
# Default: 1 (enabled)
AiPlayerbot.SyncQuestWithPlayer = 1
@@ -434,7 +441,7 @@ AiPlayerbot.DropObsoleteQuests = 1
# Auto add dungeon/raid strategies when entering the instance if implemented
AiPlayerbot.ApplyInstanceStrategies = 1
# Enable auto avoid aoe strategy (experimental)
# Enable auto avoid aoe strategy
# Default: 1 (enabled)
AiPlayerbot.AutoAvoidAoe = 1
@@ -461,7 +468,7 @@ AiPlayerbot.FleeingEnabled = 1
####################################################################################################
####################################################################################################
# PALADIN BUFFS STRATEGIES
# GREATER BUFFS STRATEGIES
#
#
@@ -484,12 +491,13 @@ AiPlayerbot.RPWarningCooldown = 30
#
# Enable/Disable maintenance command
# Learn all available spells and skills, refresh consumables, repair, enchant equipment and socket gems if bot's level is above AiPlayerbot.MinEnchantingBotLevel
# Learn all available spells and skills, assign talent points, refresh consumables, repair, enchant equipment, socket gems, etc.
# Applies if bot's level is above AiPlayerbot.MinEnchantingBotLevel
# Default: 1 (enabled)
AiPlayerbot.MaintenanceCommand = 1
# Enable/Disable specific maintenance command functionality for alt bots
# Disable to prevent players from giving free bags, spells, skill levels etc to their alt bots
# Disable to prevent players from giving free bags, spells, skill levels, etc. to their alt bots
# Default: 1 (enabled)
AiPlayerbot.AltMaintenanceAmmo = 1
AiPlayerbot.AltMaintenanceFood = 1
@@ -501,6 +509,7 @@ AiPlayerbot.AltMaintenanceBags = 1
AiPlayerbot.AltMaintenanceMounts = 1
AiPlayerbot.AltMaintenanceSkills = 1
# "Special Spells" consist of any spells listed in AiPlayerbot.RandomBotSpellIds and Death Gate for Death Knights
AiPlayerbot.AltMaintenanceClassSpells = 1
AiPlayerbot.AltMaintenanceAvailableSpells = 1
AiPlayerbot.AltMaintenanceSpecialSpells = 1
@@ -515,8 +524,8 @@ AiPlayerbot.AltMaintenanceReputation = 1
AiPlayerbot.AltMaintenanceAttunementQuests = 1
AiPlayerbot.AltMaintenanceKeyring = 1
# Enable/Disable autogear command, which automatically upgrades bots' gear; the quality is limited by AutoGearQualityLimit and AutoGearScoreLimit
# Enable/Disable autogear command, which automatically upgrades bots' gear
# The quality is limited by AutoGearQualityLimit and AutoGearScoreLimit
# Default: 1 (enabled)
AiPlayerbot.AutoGearCommand = 1
@@ -582,15 +591,21 @@ AiPlayerbot.BotTaxiGapJitterMs = 100
####################################################################################################
# PROFESSIONS
# Random bots currently do not get professions.
# Note: Random bots currently do not get professions
#
# EnableFishingWithMaster automatically adds the 'master fishing' strategy to bots that can fish that can.
# Automatically adds the 'master fishing' strategy to bots that have the fishing skill when the bots master fishes.
# Default: 1 (Enabled)
AiPlayerbot.EnableFishingWithMaster = 1
#FishingDistance sets how far a bot without a master will search for water, while FishingDistanceFromMaster limits it to a closer range, and overrides the following distance to the same value. EndFishingWithMaster sets the distance from water a bot needs to have to automatically drop the 'master fishing' strategy.
# Distance from itself (in yards) that a bot with a master will search for water to fish
AiPlayerbot.FishingDistanceFromMaster = 10.0
# Distance from itself (in yards) that a bot without a master will search for water to fish
# Currently not relevant since masterless bots will not fish
AiPlayerbot.FishingDistance = 40.0
# Distance from water (in yards) beyond which a bot will remove the 'master fishing' strategy
AiPlayerbot.EndFishingWithMaster = 30.0
#
@@ -719,7 +734,7 @@ AiPlayerbot.RandomGearQualityLimit = 3
# Max iLVL Phase 1(MC, Ony, ZG) = 78 | Phase 2(BWL) = 83 | Phase 2.5(AQ40) = 88 | Phase 3(Naxx40) = 92
# TBC
# Max iLVL Tier 4 = 120 | Tier 5 = 133 | Tier 6 = 164
# Max iLVL Phase 1(Kara, Gruul, Mag) = 125 | Phase 1.5(ZA) = 138 | Phase 2(SC, TK) = 141 | Phase 3(Hyjal, BT) = 156 | Phase 4(Sunwell) = 164
# Max iLVL Phase 1(Kara, Gruul, Mag) = 125 | Phase 2(SSC, TK, ZA) = 141 | Phase 3(Hyjal, BT) = 156 | Phase 4(Sunwell) = 164
# Wotlk
# Max iLVL Tier 7(10/25) = 200/213 | Tier 8(10/25) = 225/232 | Tier 9(10/25) = 232/245 | Tier 10(10/25/HC) = 251/264/290
# Max iLVL Phase 1(Naxx) = 224 | Phase 2(Ulduar) = 245 | Phase 3(ToC) = 258 | Phase 4(ICC) = 290
@@ -730,7 +745,8 @@ AiPlayerbot.RandomGearScoreLimit = 0
# Default: 1 (enabled)
AiPlayerbot.IncrementalGearInit = 1
# Set minimum level of bots that will enchant their equipment (if greater than RandomBotMaxlevel, bots will not enchant equipment)
# Set minimum level of bots that will enchant and socket gems into their equipment with maintenance
# If greater than RandomBotMaxlevel, bots will not automatically enchant equipment or socket gems
# Default: 60
AiPlayerbot.MinEnchantingBotLevel = 60
@@ -882,13 +898,15 @@ AiPlayerbot.OpenGoSpell = 6477
#
# Additional randombot strategies
# Strategies added here are applied to all randombots, in addition (or subtraction) to spec/role-based default strategies. These rules are processed after the defaults.
# Strategies added here are applied to all randombots, in addition (or subtraction) to spec/role-based default strategies.
# These rules are processed after the defaults.
# Example: "+threat,-potions"
AiPlayerbot.RandomBotCombatStrategies = ""
AiPlayerbot.RandomBotNonCombatStrategies = ""
# Additional altbot strategies
# Strategies added here are applied to all altbots, in addition (or subtraction) to spec/role-based default strategies. These rules are processed after the defaults.
# Strategies added here are applied to all altbots, in addition (or subtraction) to spec/role-based default strategies.
# These rules are processed after the defaults.
AiPlayerbot.CombatStrategies = ""
AiPlayerbot.NonCombatStrategies = ""

View File

@@ -1426,8 +1426,8 @@ void PlayerbotAI::DoNextAction(bool min)
return;
}
// Change engine if just ressed
if (currentEngine == engines[BOT_STATE_DEAD] && isBotAlive)
// Change engine if just ressed (no movement update when rooted)
if (currentEngine == engines[BOT_STATE_DEAD] && isBotAlive && !bot->IsRooted())
{
bot->SendMovementFlagUpdate();

View File

@@ -1654,6 +1654,10 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
if (bot->IsBeingTeleported() || !bot->IsInWorld())
return;
// no teleport / movement update when rooted.
if (bot->IsRooted())
return;
// ignore when in queue for battle grounds.
if (bot->InBattlegroundQueue())
return;

View File

@@ -41,13 +41,17 @@ bool ServerFacade::IsDistanceLessOrEqualThan(float dist1, float dist2) { return
void ServerFacade::SetFacingTo(Player* bot, WorldObject* wo, bool force)
{
if (!bot)
return;
float angle = bot->GetAngle(wo);
// if (!force && bot->isMoving())
// bot->SetFacingTo(bot->GetAngle(wo));
// else
// {
bot->SetOrientation(angle);
bot->SendMovementFlagUpdate();
if (!bot->IsRooted())
bot->SendMovementFlagUpdate();
// }
}

View File

@@ -122,7 +122,11 @@ void PlayerbotFactory::Init()
uint32 maxStoreSize = sSpellMgr->GetSpellInfoStoreSize();
for (uint32 id = 1; id < maxStoreSize; ++id)
{
if (id == 47181 || id == 50358 || id == 47242 || id == 52639 || id == 47147 || id == 7218) // Test Enchant
if (id == 7218 || id == 19927 || id == 44119 || id == 47147 || id == 47181 ||
id == 47242 || id == 50358 || id == 52639) // Test Enchants
continue;
if (id == 35791 || id == 39405) // Grandfathered TBC Enchants
continue;
if (id == 15463 || id == 15490) // Legendary Arcane Amalgamation
@@ -1103,8 +1107,6 @@ void PlayerbotFactory::ResetQuests()
}
}
void PlayerbotFactory::InitSpells() { InitAvailableSpells(); }
void PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_template /*true*/, bool reset /*false*/)
{
uint32 specTab;

View File

@@ -95,7 +95,6 @@ private:
void InitTradeSkills();
void UpdateTradeSkills();
void SetRandomSkill(uint16 id);
void InitSpells();
void ClearSpells();
void ClearSkills();
void InitTalents(uint32 specNo);

View File

@@ -90,18 +90,25 @@ void AutoMaintenanceOnLevelupAction::LearnQuestSpells(std::ostringstream* out)
//uint32 questId = i->first; //not used, line marked for removal.
Quest const* quest = i->second;
if (!quest->GetRequiredClasses() || quest->IsRepeatable() || quest->GetMinLevel() < 10)
// only process class-specific quests to learn class-related spells, cuz
// we don't want all these bunch of entries to be handled!
if (!quest->GetRequiredClasses())
continue;
if (!bot->SatisfyQuestClass(quest, false) || quest->GetMinLevel() > bot->GetLevel() ||
!bot->SatisfyQuestRace(quest, false))
// skip quests that are repeatable, too low level, or above bots' level
if (quest->IsRepeatable() || quest->GetMinLevel() < 10 || quest->GetMinLevel() > bot->GetLevel())
continue;
if (quest->GetRewSpellCast() > 0)
// skip if bot doesnt satisfy class, race, or skill requirements
if (!bot->SatisfyQuestClass(quest, false) || !bot->SatisfyQuestRace(quest, false) ||
!bot->SatisfyQuestSkill(quest, false))
continue;
if (quest->GetRewSpellCast() > 0) // RewardSpell - expected route
{
LearnSpell(quest->GetRewSpellCast(), out);
}
else if (quest->GetRewSpell() > 0)
else if (quest->GetRewSpell() > 0) // RewardDisplaySpell - fallback
{
LearnSpell(quest->GetRewSpell(), out);
}
@@ -123,35 +130,37 @@ std::string const AutoMaintenanceOnLevelupAction::FormatSpell(SpellInfo const* s
void AutoMaintenanceOnLevelupAction::LearnSpell(uint32 spellId, std::ostringstream* out)
{
SpellInfo const* proto = sSpellMgr->GetSpellInfo(spellId);
if (!proto)
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
return;
bool learned = false;
for (uint8 j = 0; j < 3; ++j)
SpellInfo const* triggeredInfo;
// find effect with learn spell and check if we have this spell
bool found = false;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{
if (proto->Effects[j].Effect == SPELL_EFFECT_LEARN_SPELL)
if (spellInfo->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL && spellInfo->Effects[i].TriggerSpell &&
!bot->HasSpell(spellInfo->Effects[i].TriggerSpell))
{
uint32 learnedSpell = proto->Effects[j].TriggerSpell;
triggeredInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[i].TriggerSpell);
if (!bot->HasSpell(learnedSpell))
{
bot->learnSpell(learnedSpell);
*out << FormatSpell(sSpellMgr->GetSpellInfo(learnedSpell)) << ", ";
}
// do not learn profession specialties!
if (!triggeredInfo || triggeredInfo->Effects[0].Effect == SPELL_EFFECT_TRADE_SKILL)
break;
learned = true;
found = true;
break;
}
}
if (!learned)
{
if (!bot->HasSpell(spellId))
{
bot->learnSpell(spellId);
*out << FormatSpell(proto) << ", ";
}
}
if (!found)
return;
// NOTE: When rewarding quests, core casts spells instead of learning them,
// but we sacrifice safe cast checks here in favor of performance/speed
bot->learnSpell(triggeredInfo->Id);
*out << FormatSpell(triggeredInfo) << ", ";
}
void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()

View File

@@ -417,7 +417,8 @@ bool FishingAction::Execute(Event event)
{
float angle = bot->GetAngle(pos.GetPositionX(), pos.GetPositionY());
bot->SetOrientation(angle);
bot->SendMovementFlagUpdate();
if (!bot->IsRooted())
bot->SendMovementFlagUpdate();
}
EquipFishingPoleAction equipAction(botAI);