Compare commits

...

80 Commits

Author SHA1 Message Date
bash
2acf8e4b13 Minor updates after patches
only freeze bots during init when not in combat
2024-12-19 19:49:07 +01:00
bash
ea1ec98645 Revert "Make AutoScaleActivity further configurable (#766)" (#798)
This reverts commit 66f0cf27d3.
2024-12-19 00:41:33 +01:00
SaW
66f0cf27d3 Make AutoScaleActivity further configurable (#766)
Make AutoScaleActivity configurable
2024-12-18 23:21:50 +01:00
avirar
a1b4681c58 Resolved issues with rndbots and equipmentPersistence (#778)
* Update PlayerbotFactory.cpp

* Update PlayerbotFactory.cpp

* Added !incremental checks for talent/gear init
2024-12-18 18:32:01 +01:00
SaW
570787ff16 Improve check to work in custom defines zones as well (#797) 2024-12-18 18:29:35 +01:00
SaW
3dd92b7fd8 Fix bots attacking others despite being in a prohibited zone - Issue 227 (#793)
* Fix bots attacking despite being in a prohibited zone

* Protect pets as well and move logic to not skip IsFriendlyTo

* Fix prohibited typo

* Update AttackAction.cpp
2024-12-17 23:31:38 +01:00
Yunfan Li
2e4db37c4d Merge pull request #791 from avirar/stop_attacking_argent_tournament_dummy_targets
Prevent bots attacking dummy targets at the Argent Tournament Grounds
2024-12-17 20:48:36 +08:00
Yunfan Li
fd1b8089af Merge pull request #792 from xSparky911x/master
fix typo in requested config value
2024-12-17 20:48:12 +08:00
Yunfan Li
42404ac403 Merge pull request #759 from liyunfan1223/new_rpg_strats
New rpg strategy
2024-12-17 20:43:38 +08:00
xSparky911x
e54fc0fb46 fix typo in requested config value 2024-12-17 03:36:19 -06:00
avirar
649e55dd84 Prevent bots attacking dummy Targets at the Argent Tournament Grounds
There was existing code to ignore practice targets (anything with Dummy in the name) so I've just added the names of practice targets located in the tournament grounds:

Charge Target
Melee Target
Ranged Target

I considered just using the phrase "Target", but it is too generic and there are 246 creature_template names containing Target.
2024-12-17 12:35:49 +11:00
Bobblybook
418a0c4be2 Merge pull request #786 from Bobblybook/master
EoE cleanup
2024-12-16 17:49:35 +11:00
Bobblybook
d031bffb54 Merge branch 'master' of https://github.com/Bobblybook/mod-playerbots 2024-12-16 17:47:58 +11:00
Bobblybook
c6a0b012e8 EoE cleanup 2024-12-16 17:47:44 +11:00
xSparky911x
cbadb5765f Prevent autogear on player alt bots (#772)
* prevent autogear on player alt bots

* update chat message to match other message format

* add config option with default set to 1

* replace end of file blank line

* remove extra space
2024-12-15 23:37:13 +01:00
Revision
9fed6bc73a Fixed config options (#785) 2024-12-15 22:55:39 +01:00
Bobblybook
80d0ac4bdd Merge pull request #779 from Bobblybook/master
EoE Implementation (WIP)
2024-12-16 08:29:27 +11:00
bash
19cda1068d botActiveAlone; added new botActiveAlone confguration option (#783) 2024-12-15 20:04:24 +01:00
Gabriel Comeau
183bf7ae8e Allow bots to trade conjured items (#781)
Since they didn't have any sell value you couldn't get a bot to give you a healthstone or conjured food/water.  This change allows them to be traded to you without altering any of the other logic for bot trades.
2024-12-15 20:03:46 +01:00
bash
ea944aeefa added additional botActiveAlone confguration options (#780) 2024-12-15 19:18:36 +01:00
Yunfan Li
a9e33bbcae Increase pet attack priority 2024-12-16 00:53:07 +08:00
Yunfan Li
677ee83e45 Update locs level 2024-12-15 22:30:24 +08:00
Yunfan Li
f45d03f62b Merge pull request #777 from avirar/titans_grip_weapon_type_restrictions
Enhanced dual wield/titan grip equip logic
2024-12-15 21:39:03 +08:00
Yunfan Li
45629cb3df Starter location collection 2024-12-15 21:36:26 +08:00
Bobblybook
a9ff1dbc5e Merge branch 'master' into master 2024-12-15 23:47:38 +11:00
Bobblybook
af6eb61d33 Prelim EoE completion 2024-12-15 23:37:56 +11:00
Yunfan Li
a91aa3e392 New rpg bug fix that preventing bots from long distance movement 2024-12-15 16:46:44 +08:00
SaW
453fa4b4f5 Fix reload command security (#774) 2024-12-14 17:55:06 +01:00
Yunfan Li
9b41798eee Fix get zone id 2024-12-14 22:35:24 +08:00
Yunfan Li
a5d1d7579d EnableNewRpgStrategy switch for initialization 2024-12-14 21:46:33 +08:00
Yunfan Li
2656b2d0d9 Update starter position 2024-12-14 16:23:33 +08:00
Yunfan Li
df5b10c9ad Revert classes folder 2024-12-14 16:23:13 +08:00
avirar
d77dbb65e5 Enhanced dual wield logic in EquipItem
Implemented logic to ensure the strongest weapon is always placed in the main hand for dual-wielding or Titan Grip-capable bots.
When equipping a new weapon, the code now compares the new weapon’s score with the currently equipped main-hand and off-hand weapons.
If the new weapon is the strongest, it goes into the main hand. The previous main-hand weapon may be moved to the off-hand if it is allowed (e.g., not a main-hand-only weapon) and provides a performance improvement.
Titan Grip conditions are accounted for, allowing valid two-handed weapons (2H axes, maces, swords) to be placed in the off-hand as well.
2024-12-14 18:52:57 +11:00
avirar
0c16f308db Update EquipAction.cpp 2024-12-14 18:41:32 +11:00
avirar
34c0759c90 Update EquipAction.cpp 2024-12-14 18:28:28 +11:00
avirar
f8da773ce1 Update EquipAction.cpp 2024-12-14 18:21:28 +11:00
avirar
a9eb41600d Update EquipAction.cpp 2024-12-14 18:07:02 +11:00
avirar
ea9bd18102 Enhanced dual-wield logic
Description of Changes:

    Implemented logic to ensure the strongest weapon is always placed in the main hand for dual-wielding or Titan Grip-capable bots.
    When equipping a new weapon, the code now compares the new weapon’s score with the currently equipped main-hand and off-hand weapons.
    If the new weapon is the strongest, it goes into the main hand. The previous main-hand weapon may be moved to the off-hand if it is allowed (e.g., not a main-hand-only weapon) and provides a performance improvement.
    Titan Grip conditions are accounted for, allowing valid two-handed weapons (2H axes, maces, swords) to be placed in the off-hand as well.
2024-12-14 17:57:16 +11:00
avirar
5962dc3d0c Update ItemUsageValue.cpp 2024-12-14 17:23:48 +11:00
Yunfan Li
be71872112 Update far move to prevent invalid movement 2024-12-14 02:33:52 +08:00
Yunfan Li
912ef6d56f Increase reach melee relevance 2024-12-14 01:43:38 +08:00
Yunfan Li
1f8d0d244c Update starter position 2024-12-14 01:33:39 +08:00
Yunfan Li
2171493d5e Update GO_INNKEEPER prob 2024-12-13 20:20:13 +08:00
Yunfan Li
e7416db7dc Modify file structure 2024-12-13 20:14:25 +08:00
Yunfan Li
a7496b3eb5 Merge branch 'master' into new_rpg_strats 2024-12-13 18:51:55 +08:00
Yunfan Li
f26cebb518 Update rpg status probability 2024-12-11 23:00:03 +08:00
Yunfan Li
c436781d39 Merge pull request #767 from liyunfan1223/crash_fix_bg
[Crash fix] Fix race condition on BattlegroundData
2024-12-10 21:52:07 +08:00
Yunfan Li
0cfae2673f [Crash fix] Fix race condition on BattlegroundData 2024-12-10 20:22:52 +08:00
Yunfan Li
69fe9a2d81 Reduce near npc range distance 2024-12-10 20:20:19 +08:00
Yunfan Li
31f82cc322 locsPerLevelCache faction filter 2024-12-08 15:53:46 +08:00
Yunfan Li
98701a6f66 Modify starter position (add flightmaster) 2024-12-08 15:47:34 +08:00
Yunfan Li
4644fd8459 Minor spell enhancement 2024-12-07 12:57:18 +08:00
Yunfan Li
6d82b134d1 Merge branch 'master' into new_rpg_strats 2024-12-06 20:39:51 +08:00
Revision
f096c2089f Add a config option for dropping obsolete quests (#745) 2024-12-06 08:45:41 +01:00
kadeshar
8c84026116 - added spec configuration for world buffs (#761)
- added world buffs to configuration file
- fixed NeedWorldBuffTrigger
2024-12-05 19:29:52 +01:00
Yunfan Li
e7f0ee16f7 Merge pull request #757 from noisiver/support-newer-master
Support newer master
2024-12-05 22:13:54 +08:00
Yunfan Li
0fa548593f Improve performance by ZoneHasRealPlayers and MoveFarTo 2024-12-04 23:26:58 +08:00
Yunfan Li
618358aa13 Improve near npc move 2024-12-03 23:30:09 +08:00
Yunfan Li
87e8c05b20 Innkeeper position and windows compile error 2024-12-03 22:57:39 +08:00
Bobblybook
f20e5d76d1 Eye of Eternity skeleton 2024-12-03 21:13:28 +11:00
kadeshar
437f816b15 - fixed attack rti target action (#758) 2024-12-03 10:59:11 +01:00
Yunfan
5c3591ae5a Update random bots level stats 2024-12-02 15:28:30 +08:00
Yunfan
5ca7c71920 Logs and rpg status for new rpg strats 2024-12-02 11:48:51 +08:00
Yunfan
bb3328670b Remove redundant logs (set to debug level) 2024-12-02 11:42:31 +08:00
Revision
fe896c1d3d Use another helper and revert one instance HasPlayerFlag 2024-12-02 00:28:08 +01:00
Revision
f0ccb0a371 Fix compiler errors and switch to using available helpers 2024-12-02 00:16:06 +01:00
kadeshar
9e20eb452d - fixed mark rti bug (#755) 2024-12-01 23:11:01 +01:00
kadeshar
7291fae5b3 Warlock soulstone action (#753)
* - added warlock soulstone action

* - added master as soulstone target
2024-12-01 22:33:03 +01:00
Bobblybook
b7c2fe9947 Obsidian Sanctum implementation (#752)
OS+2 implemented.
- Kill Vesperon before fight
- Mark Main Tank in raid interface

Offtank still needs a bit of work, and dps needs to stop running around once they're safe. But it's usable currently.
I think this should probably work for OS+1 and OS+0 with no changes but I was more concerned about implementing +2.
2024-12-01 22:26:35 +01:00
Yunfan Li
dba84da6f3 [New Rpg] Implement GO_INNKEEPER and NEAR_NPC status 2024-12-02 00:35:23 +08:00
Bobblybook
657bb9a6da Obsidian Sanctum implementation
OS+2 implemented.
- Kill Vesperon before fight
- Mark Main Tank in raid interface

Offtank still needs a bit of work, and dps needs to stop running around once they're safe. But it's usable currently.
I think this should probably work for OS+1 and OS+0 with no changes but I was more concerned about implementing +2.
2024-12-02 00:25:36 +11:00
Yunfan Li
83c407f1c6 Merge pull request #747 from Raz0r1337/master
fix small typo and remove double logging
2024-12-01 11:47:24 +08:00
Yunfan Li
21684a051d Merge pull request #751 from noisiver/update-character-online
Change statement to a newly created one
2024-12-01 11:45:42 +08:00
Revision
4f06019436 Change statement to a newly created one 2024-11-30 17:09:49 +01:00
Yunfan Li
0fd894176b [New Rpg] New rpg start up (add GO_GRIND and NEAR_RANDOM status) 2024-11-30 23:48:29 +08:00
Sascha
f54dbcdc54 Update playerbots.conf.dist
these loggers already exist in the worldserver.conf
these duplicate entries interfere with each other
2024-11-29 13:06:57 +01:00
Sascha
b39e7304cb Update playerbots.conf.dist
small indentation error fixed
2024-11-29 13:00:35 +01:00
Revision
3e449fff73 Merge pull request #742 from avirar/inCombatErrorTypo
When bots try to equip armour while in combat they say "I am not in combat"
2024-11-27 15:16:20 +01:00
bash
c0b185935b database update data folder location (#741)
added acore_playerbots update script
2024-11-25 23:44:39 +01:00
avirar
37e5e3942d Update InventoryChangeFailureAction.cpp
Corrected bot output for EQUIP_ERR_NOT_IN_COMBAT which is returned when the bot is in combat and cannot perform the equip action. Bot would previously say "I am not in combat", it will now say "I am in combat"
2024-11-26 08:54:39 +11:00
92 changed files with 3104 additions and 349 deletions

View File

@@ -67,7 +67,7 @@
###################################
# #
# GENERAL SETTINGS #
# GENERAL SETTINGS #
# #
###################################
@@ -368,6 +368,10 @@ AiPlayerbot.SyncQuestWithPlayer = 1
# Default: 0 (disabled)
AiPlayerbot.SyncQuestForPlayer = 0
# Bots will drop obsolete quests
# Default: 1 (enabled)
AiPlayerbot.DropObsoleteQuests = 1
#
#
#
@@ -420,6 +424,10 @@ AiPlayerbot.MaintenanceCommand = 1
# default: 1 (enable)
AiPlayerbot.AutoGearCommand = 1
# Enable/Disable autogear command on player alt bots
# Default: 1 (enable)
AiPlayerbot.AutoGearCommandAltBots = 1
# Equips quality limitation for auto gear command (1 = normal, 2 = uncommon, 3 = rare, 4 = epic, 5 = legendary)
# default: 3 (rare)
AiPlayerbot.AutoGearQualityLimit = 3
@@ -588,6 +596,11 @@ AiPlayerbot.RandomBotGroupNearby = 0
# Default: 1 (enabled)
AiPlayerbot.AutoDoQuests = 1
# Random Bots will behave more like real players (exprimental)
# This option will override AutoDoQuests
# Default: 0 (disabled)
AiPlayerbot.EnableNewRpgStrategy = 0
# Quest items to leave (do not destroy)
AiPlayerbot.RandomBotQuestItems = "6948,5175,5176,5177,5178,16309,12382,13704,11000"
@@ -709,10 +722,10 @@ AiPlayerbot.RandomBotArenaTeamMinRating = 1000
AiPlayerbot.DeleteRandomBotArenaTeams = 0
# PvP Restricted Zones (bots don't pvp)
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298"
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,139"
# PvP Restricted Areas (bots don't pvp)
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392"
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268"
# Improve react speed in battleground and arena (may cause lag)
AiPlayerbot.FastReactInBG = 1
@@ -1022,6 +1035,56 @@ AiPlayerbot.PremadeSpecLink.11.3.80 = -553202032322010053100030310511-205503012
#
###################################################################################################
###################################
# #
# WORLD BUFFS #
# #
###################################
####################################################################################################
#
#
#
# Applies a permanent buff to all bots when not in combat simulating flasks, food, rune etc.
# WorldBuff.Faction.Class.Spec.MinLevel.MaxLevel
AiPlayerbot.WorldBuff.0.1.0.80.80 = 53760,57358 #WARRIOR ARMS
AiPlayerbot.WorldBuff.0.1.1.80.80 = 53760,57358 #WARRIOR FURY
AiPlayerbot.WorldBuff.0.1.2.80.80 = 53758,57356 #WARRIOR PROTECTION
AiPlayerbot.WorldBuff.0.2.0.80.80 = 60347,53749,57332 #PALADIN HOLY
AiPlayerbot.WorldBuff.0.2.1.80.80 = 53758,57356 #PALADIN PROTECTION
AiPlayerbot.WorldBuff.0.2.2.80.80 = 53760,57371 #PALADIN RETRIBUTION
AiPlayerbot.WorldBuff.0.3.0.80.80 = 53760,57325 #HUNTER BEAST
AiPlayerbot.WorldBuff.0.3.1.80.80 = 53760,57358 #HUNTER MARKSMANSHIP
AiPlayerbot.WorldBuff.0.3.2.80.80 = 53760,57367 #HUNTER SURVIVAL
AiPlayerbot.WorldBuff.0.4.0.80.80 = 53760,57325 #ROGUE ASSASINATION
AiPlayerbot.WorldBuff.0.4.1.80.80 = 53760,57358 #ROGUE COMBAT
AiPlayerbot.WorldBuff.0.4.2.80.80 = 53760,57367 #ROGUE SUBTLETY
AiPlayerbot.WorldBuff.0.5.0.80.80 = 53755,57327 #PRIEST DISCIPLINE
AiPlayerbot.WorldBuff.0.5.1.80.80 = 53755,57327 #PRIEST HOLY
AiPlayerbot.WorldBuff.0.5.2.80.80 = 53755,57327 #PRIEST SHADOW
AiPlayerbot.WorldBuff.0.6.0.80.80 = 53758,57356 #DEATH KNIGHT BLOOD
AiPlayerbot.WorldBuff.0.6.1.80.80 = 53760,57358 #DEATH KNIGHT FROST
AiPlayerbot.WorldBuff.0.6.2.80.80 = 53760,57358 #DEATH KNIGHT UNHOLY
AiPlayerbot.WorldBuff.0.7.0.80.80 = 53755,57327 #SHAMAN ELEMENTAL
AiPlayerbot.WorldBuff.0.7.1.80.80 = 53760,57325 #SHAMAN ENHANCEMENT
AiPlayerbot.WorldBuff.0.7.2.80.80 = 53755,57327 #SHAMAN RESTORATION
AiPlayerbot.WorldBuff.0.8.0.80.80 = 53755,57327 #MAGE ARCANE
AiPlayerbot.WorldBuff.0.8.1.80.80 = 53755,57327 #MAGE FIRE
AiPlayerbot.WorldBuff.0.8.2.80.80 = 53755,57327 #MAGE FROST
AiPlayerbot.WorldBuff.0.9.0.80.80 = 53755,57327 #WARLOCK AFFLICTION
AiPlayerbot.WorldBuff.0.9.1.80.80 = 53755,57327 #WARLOCK DEMONOLOGY
AiPlayerbot.WorldBuff.0.9.2.80.80 = 53755,57327 #WARLOCK DESTRUCTION
AiPlayerbot.WorldBuff.0.11.0.80.80 = 53755,57327 #DRUID BALANCE
AiPlayerbot.WorldBuff.0.11.1.80.80 = 53749,53763,57367 #DRUID FERAL
AiPlayerbot.WorldBuff.0.11.2.80.80 = 54212,57334 #DRUID RESTORATION
#
#
#
###################################################################################################
###################################
# #
# RANDOM BOT DEFAULT TALENT SPEC #
@@ -1411,9 +1474,6 @@ AiPlayerbot.BroadcastChanceGuildManagement = 30000
# Example: AiPlayerbot.AllowedLogFiles = travelNodes.csv,travelPaths.csv,TravelNodeStore.h,bot_movement.csv,bot_location.csv
AiPlayerbot.AllowedLogFiles = ""
Appender.Playerbots=2,5,0,Playerbots.log,w
Logger.playerbots=5,Console Playerbots
#
#
#
@@ -1460,6 +1520,13 @@ AiPlayerbot.RandombotsWalkingRPG.InDoors = 0
# enforced to 100%
AiPlayerbot.BotActiveAlone = 100
# Force botActiveAlone when bot is ... of real player
AiPlayerbot.BotActiveAloneForceWhenInRadius = 150
AiPlayerbot.BotActiveAloneForceWhenInZone = 1
AiPlayerbot.BotActiveAloneForceWhenInMap = 0
AiPlayerbot.BotActiveAloneForceWhenIsFriend = 1
AiPlayerbot.BotActiveAloneForceWhenInGuild = 1
# Specify smart scaling is enabled or not.
# The default is 1. When enabled (smart) scales the 'BotActiveAlone' value.
# Only when botLevel is between WhenMinLevel and WhenMaxLevel.

View File

@@ -0,0 +1,11 @@
UPDATE `updates_include`
SET `path` = '$/data/sql/playerbots/updates'
WHERE `state` = 'RELEASED';
UPDATE `updates_include`
SET `path` = '$/data/sql/playerbots/custom'
WHERE `state` = 'CUSTOM';
UPDATE `updates_include`
SET `path` = '$/data/sql/playerbots/archive'
WHERE `state` = 'ARCHIVED';

View File

@@ -631,7 +631,11 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
// nonCombatEngine->addStrategy("group");
// nonCombatEngine->addStrategy("guild");
if (sPlayerbotAIConfig->autoDoQuests)
if (sPlayerbotAIConfig->enableNewRpgStrategy)
{
nonCombatEngine->addStrategy("new rpg", false);
}
else if (sPlayerbotAIConfig->autoDoQuests)
{
// nonCombatEngine->addStrategy("travel");
nonCombatEngine->addStrategy("rpg", false);

View File

@@ -29,6 +29,7 @@
#include "MotionMaster.h"
#include "MoveSpline.h"
#include "MoveSplineInit.h"
#include "NewRpgStrategy.h"
#include "ObjectGuid.h"
#include "PerformanceMonitor.h"
#include "Player.h"
@@ -38,6 +39,7 @@
#include "Playerbots.h"
#include "PointMovementGenerator.h"
#include "PositionValue.h"
#include "RandomPlayerbotMgr.h"
#include "SayAction.h"
#include "ScriptMgr.h"
#include "ServerFacade.h"
@@ -718,7 +720,7 @@ void PlayerbotAI::HandleTeleportAck()
// SetNextCheckDelay(urand(2000, 5000));
if (sPlayerbotAIConfig->applyInstanceStrategies)
ApplyInstanceStrategies(bot->GetMapId(), true);
Reset();
Reset(true);
}
SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
@@ -767,14 +769,15 @@ void PlayerbotAI::Reset(bool full)
->setTarget(sTravelMgr->nullTravelDestination, sTravelMgr->nullWorldPosition, true);
aiObjectContext->GetValue<TravelTarget*>("travel target")->Get()->setStatus(TRAVEL_STATUS_EXPIRED);
aiObjectContext->GetValue<TravelTarget*>("travel target")->Get()->setExpireIn(1000);
rpgInfo = NewRpgInfo();
}
aiObjectContext->GetValue<GuidSet&>("ignore rpg target")->Get().clear();
bot->GetMotionMaster()->Clear();
// bot->CleanupAfterTaxiFlight();
InterruptSpell();
InterruptSpell();
if (full)
{
for (uint8 i = 0; i < BOT_STATE_MAX; i++)
@@ -1603,6 +1606,12 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
case 608:
strategyName = "wotlk-vh"; // Violet Hold
break;
case 615:
strategyName = "wotlk-os"; // Obsidian Sanctum
break;
case 616:
strategyName = "wotlk-eoe"; // Eye Of Eternity
break;
case 619:
strategyName = "wotlk-ok"; // Ahn'kahet: The Old Kingdom
break;
@@ -4105,20 +4114,11 @@ inline bool ZoneHasRealPlayers(Player* bot)
{
return false;
}
Map::PlayerList const& players = bot->GetMap()->GetPlayers();
if (players.IsEmpty())
for (auto& player : sRandomPlayerbotMgr->GetPlayers())
{
return false;
}
for (auto const& itr : players)
{
Player* player = itr.GetSource();
if (!player || !player->IsVisible())
{
if (player->GetMapId() != bot->GetMapId())
continue;
}
if (player->GetZoneId() == bot->GetZoneId())
{
@@ -4135,6 +4135,15 @@ inline bool ZoneHasRealPlayers(Player* bot)
bool PlayerbotAI::AllowActive(ActivityType activityType)
{
// Is in combat. Always defend yourself.
if (activityType != OUT_OF_PARTY_ACTIVITY && activityType != PACKET_ACTIVITY)
{
if (bot->IsInCombat())
{
return true;
}
}
// only keep updating till initializing time has completed,
// which prevents unneeded expensive GameTime calls.
if (_isBotInitializing)
@@ -4154,35 +4163,53 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
return true;
}
// when botActiveAlone is 100% and smartScale disabled
if (sPlayerbotAIConfig->botActiveAlone >= 100 && !sPlayerbotAIConfig->botActiveAloneSmartScale)
{
return true;
}
// bg, raid, dungeon
if (!WorldPosition(bot).isOverworld())
{
return true;
}
// Is in combat. Defend yourself.
if (activityType != OUT_OF_PARTY_ACTIVITY && activityType != PACKET_ACTIVITY)
// bot map has active players.
if (sPlayerbotAIConfig->BotActiveAloneForceWhenInMap)
{
if (bot->IsInCombat())
if (HasRealPlayers(bot->GetMap()))
{
return true;
}
}
// bot zone has active players.
if (ZoneHasRealPlayers(bot))
if (sPlayerbotAIConfig->BotActiveAloneForceWhenInZone)
{
return true;
if (ZoneHasRealPlayers(bot))
{
return true;
}
}
// when in real guild
if (IsInRealGuild())
if (sPlayerbotAIConfig->BotActiveAloneForceWhenInGuild)
{
if (IsInRealGuild())
{
return true;
}
}
// Player is near. Always active.
if (HasPlayerNearby(sPlayerbotAIConfig->BotActiveAloneForceWhenInRadius))
{
return true;
}
// Has player master. Always active.
if (GetMaster())
if (GetMaster())
{
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(GetMaster());
if (!masterBotAI || masterBotAI->IsRealPlayer())
@@ -4250,30 +4277,27 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
return true;
}
// Player is near. Always active.
if (HasPlayerNearby(300.f))
{
return true;
}
// HasFriend
for (auto& player : sRandomPlayerbotMgr->GetPlayers())
if (sPlayerbotAIConfig->BotActiveAloneForceWhenIsFriend)
{
if (!player || !player->IsInWorld() || !player->GetSocial() || !bot->GetGUID())
for (auto& player : sRandomPlayerbotMgr->GetPlayers())
{
continue;
}
if (!player || !player->IsInWorld() || !player->GetSocial() || !bot->GetGUID())
{
continue;
}
if (player->GetSocial()->HasFriend(bot->GetGUID()))
{
return true;
if (player->GetSocial()->HasFriend(bot->GetGUID()))
{
return true;
}
}
}
// Force the bots to spread
if (activityType == OUT_OF_PARTY_ACTIVITY || activityType == GRIND_ACTIVITY)
{
if (HasManyPlayersNearby(10, sPlayerbotAIConfig->sightDistance))
if (HasManyPlayersNearby(10, 40))
{
return true;
}
@@ -4289,11 +4313,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
{
return false;
}
if (sPlayerbotAIConfig->botActiveAlone >= 100 && !sPlayerbotAIConfig->botActiveAloneSmartScale)
{
return true;
}
// #######################################################################################
// All mandatory conditations are checked to be active or not, from here the remaining
// situations are usable for scaling when enabled.
@@ -5472,10 +5492,10 @@ bool PlayerbotAI::CanMove()
if (IsInVehicle() && !IsInVehicle(true))
return false;
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) ||
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))
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() ||
bot->HasConfuseAura() || bot->IsCharmed() || bot->HasStunAura() ||
bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
return false;
return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE;

View File

@@ -21,6 +21,7 @@
#include "PlayerbotTextMgr.h"
#include "SpellAuras.h"
#include "WorldPacket.h"
#include "NewRpgStrategy.h"
class AiObjectContext;
class Creature;
@@ -432,7 +433,7 @@ public:
std::vector<Player*> GetPlayersInGroup();
const AreaTableEntry* GetCurrentArea();
const AreaTableEntry* GetCurrentZone();
std::string GetLocalizedAreaName(const AreaTableEntry* entry);
static std::string GetLocalizedAreaName(const AreaTableEntry* entry);
bool TellMaster(std::ostringstream& stream, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
bool TellMaster(std::string const text, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
@@ -572,6 +573,7 @@ public:
std::set<uint32> GetCurrentIncompleteQuestIds();
void PetFollow();
static float GetItemScoreMultiplier(ItemQualities quality);
NewRpgInfo rpgInfo;
private:
static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore,
@@ -580,7 +582,7 @@ private:
void HandleCommands();
void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL);
bool _isBotInitializing = true;
bool _isBotInitializing = false;
protected:
Player* bot;

View File

@@ -399,15 +399,19 @@ bool PlayerbotAIConfig::Initialize()
worldBuffs.clear();
LOG_INFO("playerbots", "Loading Worldbuff...");
for (uint32 factionId = 0; factionId < 3; factionId++)
{
for (uint32 classId = 0; classId < MAX_CLASSES; classId++)
{
for (uint32 minLevel = 0; minLevel < MAX_LEVEL; minLevel++)
for (uint32 specId = 0; specId < MAX_SPECNO; specId++)
{
for (uint32 maxLevel = 0; maxLevel < MAX_LEVEL; maxLevel++)
for (uint32 minLevel = 0; minLevel < MAX_LEVEL; minLevel++)
{
loadWorldBuf(factionId, classId, minLevel, maxLevel);
for (uint32 maxLevel = 0; maxLevel < MAX_LEVEL; maxLevel++)
{
loadWorldBuf(factionId, classId, specId, minLevel, maxLevel);
}
}
}
}
@@ -459,12 +463,18 @@ bool PlayerbotAIConfig::Initialize()
addClassAccountPoolSize = sConfigMgr->GetOption<int32>("AiPlayerbot.AddClassAccountPoolSize", 50);
maintenanceCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.MaintenanceCommand", 1);
autoGearCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearCommand", 1);
autoGearCommandAltBots = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearCommandAltBots", 1);
autoGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearQualityLimit", 3);
autoGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearScoreLimit", 0);
playerbotsXPrate = sConfigMgr->GetOption<int32>("AiPlayerbot.KillXPRate", 1);
disableDeathKnightLogin = sConfigMgr->GetOption<bool>("AiPlayerbot.DisableDeathKnightLogin", 0);
botActiveAlone = sConfigMgr->GetOption<int32>("AiPlayerbot.BotActiveAlone", 100);
BotActiveAloneForceWhenInRadius = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotActiveAloneForceWhenInRadius", 150);
BotActiveAloneForceWhenInZone = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenInZone", 1);
BotActiveAloneForceWhenInMap = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenInMap", 0);
BotActiveAloneForceWhenIsFriend = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenIsFriend", 1);
BotActiveAloneForceWhenInGuild = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenInGuild", 1);
botActiveAloneSmartScale = sConfigMgr->GetOption<bool>("AiPlayerbot.botActiveAloneSmartScale", 1);
botActiveAloneSmartScaleWhenMinLevel =
sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel", 1);
@@ -491,13 +501,15 @@ bool PlayerbotAIConfig::Initialize()
twoRoundsGearInit = sConfigMgr->GetOption<bool>("AiPlayerbot.TwoRoundsGearInit", false);
syncQuestWithPlayer = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncQuestWithPlayer", true);
syncQuestForPlayer = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncQuestForPlayer", false);
dropObsoleteQuests = sConfigMgr->GetOption<bool>("AiPlayerbot.DropObsoleteQuests", true);
autoTrainSpells = sConfigMgr->GetOption<std::string>("AiPlayerbot.AutoTrainSpells", "yes");
autoPickTalents = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoPickTalents", true);
autoUpgradeEquip = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoUpgradeEquip", false);
autoLearnTrainerSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnTrainerSpells", true);
autoLearnQuestSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnQuestSpells", false);
autoTeleportForLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoTeleportForLevel", false);
autoDoQuests = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoDoQuests", false);
autoDoQuests = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoDoQuests", true);
enableNewRpgStrategy = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableNewRpgStrategy", false);
syncLevelWithPlayers = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncLevelWithPlayers", false);
freeFood = sConfigMgr->GetOption<bool>("AiPlayerbot.FreeFood", true);
randomBotGroupNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGroupNearby", true);
@@ -559,7 +571,7 @@ bool PlayerbotAIConfig::IsInRandomQuestItemList(uint32 id)
bool PlayerbotAIConfig::IsPvpProhibited(uint32 zoneId, uint32 areaId)
{
return IsInPvpProhibitedZone(zoneId) || IsInPvpProhibitedArea(areaId);
return IsInPvpProhibitedZone(zoneId) || IsInPvpProhibitedArea(areaId) || IsInPvpProhibitedZone(areaId);
}
bool PlayerbotAIConfig::IsInPvpProhibitedZone(uint32 id)
@@ -644,36 +656,50 @@ void PlayerbotAIConfig::log(std::string const fileName, char const* str, ...)
fflush(stdout);
}
void PlayerbotAIConfig::loadWorldBuf(uint32 factionId1, uint32 classId1, uint32 minLevel1, uint32 maxLevel1)
void PlayerbotAIConfig::loadWorldBuf(uint32 factionId1, uint32 classId1, uint32 specId1, uint32 minLevel1, uint32 maxLevel1)
{
std::vector<uint32> buffs;
std::ostringstream os;
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << minLevel1 << "." << maxLevel1;
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << specId1 << "." << minLevel1 << "." << maxLevel1;
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
for (auto buff : buffs)
{
worldBuff wb = {buff, factionId1, classId1, minLevel1, maxLevel1};
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb);
}
if (maxLevel1 == 0)
{
std::ostringstream os;
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << minLevel1;
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << specId1 << "." << minLevel1;
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
for (auto buff : buffs)
{
worldBuff wb = {buff, factionId1, classId1, minLevel1, maxLevel1};
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb);
}
}
if (maxLevel1 == 0 && minLevel1 == 0)
{
std::ostringstream os;
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << factionId1 << "." << classId1 << "." << specId1;
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
for (auto buff : buffs)
{
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb);
}
}
if (maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0)
{
std::ostringstream os;
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << factionId1 << "." << classId1;
@@ -682,12 +708,12 @@ void PlayerbotAIConfig::loadWorldBuf(uint32 factionId1, uint32 classId1, uint32
for (auto buff : buffs)
{
worldBuff wb = {buff, factionId1, classId1, minLevel1, maxLevel1};
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb);
}
}
if (classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0)
if (classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0)
{
std::ostringstream os;
os << "AiPlayerbot.WorldBuff." << factionId1;
@@ -696,12 +722,12 @@ void PlayerbotAIConfig::loadWorldBuf(uint32 factionId1, uint32 classId1, uint32
for (auto buff : buffs)
{
worldBuff wb = {buff, factionId1, classId1, minLevel1, maxLevel1};
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb);
}
}
if (factionId1 == 0 && classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0)
if (factionId1 == 0 && classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0)
{
std::ostringstream os;
os << "AiPlayerbot.WorldBuff";
@@ -710,7 +736,7 @@ void PlayerbotAIConfig::loadWorldBuf(uint32 factionId1, uint32 classId1, uint32
for (auto buff : buffs)
{
worldBuff wb = {buff, factionId1, classId1, minLevel1, maxLevel1};
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb);
}
}

View File

@@ -247,6 +247,7 @@ public:
uint32 spellId;
uint32 factionId = 0;
uint32 classId = 0;
uint32 specId = 0;
uint32 minLevel = 0;
uint32 maxLevel = 0;
};
@@ -263,6 +264,11 @@ public:
uint32 playerbotsXPrate;
bool disableDeathKnightLogin;
uint32 botActiveAlone;
uint32 BotActiveAloneForceWhenInRadius;
bool BotActiveAloneForceWhenInZone;
bool BotActiveAloneForceWhenInMap;
bool BotActiveAloneForceWhenIsFriend;
bool BotActiveAloneForceWhenInGuild;
bool botActiveAloneSmartScale;
uint32 botActiveAloneSmartScaleWhenMinLevel;
uint32 botActiveAloneSmartScaleWhenMaxLevel;
@@ -275,11 +281,13 @@ public:
bool twoRoundsGearInit;
bool syncQuestWithPlayer;
bool syncQuestForPlayer;
bool dropObsoleteQuests;
std::string autoTrainSpells;
bool autoPickTalents;
bool autoUpgradeEquip;
bool autoLearnTrainerSpells;
bool autoDoQuests;
bool enableNewRpgStrategy;
bool syncLevelWithPlayers;
bool freeFood;
bool autoLearnQuestSpells;
@@ -312,7 +320,7 @@ public:
int32 addClassCommand;
int32 addClassAccountPoolSize;
int32 maintenanceCommand;
int32 autoGearCommand, autoGearQualityLimit, autoGearScoreLimit;
int32 autoGearCommand, autoGearCommandAltBots, autoGearQualityLimit, autoGearScoreLimit;
std::string const GetTimestampStr();
bool hasLog(std::string const fileName)
@@ -327,7 +335,7 @@ public:
}
void log(std::string const fileName, const char* str, ...);
void loadWorldBuf(uint32 factionId, uint32 classId, uint32 minLevel, uint32 maxLevel);
void loadWorldBuf(uint32 factionId, uint32 classId, uint32 specId, uint32 minLevel, uint32 maxLevel);
static std::vector<std::vector<uint32>> ParseTempTalentsOrder(uint32 cls, std::string temp_talents_order);
static std::vector<std::vector<uint32>> ParseTempPetTalentsOrder(uint32 spec, std::string temp_talents_order);
};

View File

@@ -958,9 +958,17 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
if (!strcmp(cmd, "reload"))
{
messages.push_back("Reloading config");
sPlayerbotAIConfig->Initialize();
return messages;
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
{
sPlayerbotAIConfig->Initialize();
messages.push_back("Config reloaded.");
return messages;
}
else
{
messages.push_back("ERROR: Only GM can use this command.");
return messages;
}
}
if (!strcmp(cmd, "tweak"))

View File

@@ -52,7 +52,7 @@ public:
void OnDatabaseSelectIndexLogout(Player* player, uint32& statementIndex, uint32& statementParam) override
{
statementIndex = CHAR_UPD_CHAR_ONLINE;
statementIndex = CHAR_UPD_CHAR_OFFLINE;
statementParam = player->GetGUID().GetCounter();
}

View File

@@ -556,7 +556,7 @@ void RandomPlayerbotFactory::CreateRandomBots()
totalRandomBotChars += AccountMgr::GetCharactersCount(accountId);
}
LOG_INFO("server.loading", "{} random bot accounts with {} characters available",
LOG_INFO("server.loading", ">> {} random bot accounts with {} characters available",
sPlayerbotAIConfig->randomBotAccounts.size(), totalRandomBotChars);
}

View File

@@ -8,6 +8,7 @@
#include <algorithm>
#include <boost/thread/thread.hpp>
#include <cstdlib>
#include <ctime>
#include <iomanip>
#include <random>
@@ -18,6 +19,8 @@
#include "BattlegroundMgr.h"
#include "CellImpl.h"
#include "ChannelMgr.h"
#include "DBCStores.h"
#include "DBCStructure.h"
#include "DatabaseEnv.h"
#include "Define.h"
#include "FleeManager.h"
@@ -28,15 +31,19 @@
#include "GuildTaskMgr.h"
#include "LFGMgr.h"
#include "MapMgr.h"
#include "NewRpgStrategy.h"
#include "PerformanceMonitor.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h"
#include "PlayerbotCommandServer.h"
#include "PlayerbotFactory.h"
#include "Playerbots.h"
#include "Position.h"
#include "Random.h"
#include "ServerFacade.h"
#include "SharedDefines.h"
#include "TravelMgr.h"
#include "Unit.h"
#include "UpdateTime.h"
#include "World.h"
@@ -165,6 +172,13 @@ RandomPlayerbotMgr::RandomPlayerbotMgr() : PlayerbotHolder(), processTicks(0)
}
BattlegroundData.clear();
for (int bracket = BG_BRACKET_ID_FIRST; bracket < MAX_BATTLEGROUND_BRACKETS; ++bracket)
{
for (int queueType = BATTLEGROUND_QUEUE_AV; queueType < MAX_BATTLEGROUND_QUEUE_TYPES; ++queueType)
{
BattlegroundData[queueType][bracket] = BattlegroundInfo();
}
}
BgCheckTimer = 0;
LfgCheckTimer = 0;
PlayersCheckTimer = 0;
@@ -341,21 +355,32 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/)
if (sPlayerbotAIConfig->syncLevelWithPlayers && !players.empty())
{
if (time(nullptr) > (PlayersCheckTimer + 60))
activateCheckPlayersThread();
sRandomPlayerbotMgr->CheckPlayers();
}
if (sPlayerbotAIConfig->randomBotJoinBG /* && !players.empty()*/)
{
if (time(nullptr) > (BgCheckTimer + 30))
activateCheckBgQueueThread();
sRandomPlayerbotMgr->CheckBgQueue();
}
if (sPlayerbotAIConfig->randomBotJoinLfg /* && !players.empty()*/)
{
if (time(nullptr) > (LfgCheckTimer + 30))
activateCheckLfgQueueThread();
sRandomPlayerbotMgr->CheckLfgQueue();
}
if (time(nullptr) > (printStatsTimer + 300))
{
if (!printStatsTimer)
{
printStatsTimer = time(nullptr);
}
else
{
activatePrintStatsThread();
}
}
uint32 updateBots = sPlayerbotAIConfig->randomBotsPerInterval * onlineBotFocus / 100;
uint32 maxNewBots = onlineBotCount < maxAllowedBotCount ? maxAllowedBotCount - onlineBotCount : 0;
uint32 loginBots = std::min(sPlayerbotAIConfig->randomBotsPerInterval - updateBots, maxNewBots);
@@ -410,25 +435,25 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/)
}
}
//void RandomPlayerbotMgr::ScaleBotActivity()
// void RandomPlayerbotMgr::ScaleBotActivity()
//{
// float activityPercentage = getActivityPercentage();
// float activityPercentage = getActivityPercentage();
//
// // if (activityPercentage >= 100.0f || activityPercentage <= 0.0f) pid.reset(); //Stop integer buildup during
// // max/min activity
// // if (activityPercentage >= 100.0f || activityPercentage <= 0.0f) pid.reset(); //Stop integer buildup during
// // max/min activity
//
// // % increase/decrease wanted diff , avg diff
// float activityPercentageMod = pid.calculate(
// sRandomPlayerbotMgr->GetPlayers().empty() ? sPlayerbotAIConfig->diffEmpty : sPlayerbotAIConfig->diffWithPlayer,
// sWorldUpdateTime.GetAverageUpdateTime());
// // % increase/decrease wanted diff , avg diff
// float activityPercentageMod = pid.calculate(
// sRandomPlayerbotMgr->GetPlayers().empty() ? sPlayerbotAIConfig->diffEmpty :
// sPlayerbotAIConfig->diffWithPlayer, sWorldUpdateTime.GetAverageUpdateTime());
//
// activityPercentage = activityPercentageMod + 50;
// activityPercentage = activityPercentageMod + 50;
//
// // Cap the percentage between 0 and 100.
// activityPercentage = std::max(0.0f, std::min(100.0f, activityPercentage));
// // Cap the percentage between 0 and 100.
// activityPercentage = std::max(0.0f, std::min(100.0f, activityPercentage));
//
// setActivityPercentage(activityPercentage);
//}
// setActivityPercentage(activityPercentage);
// }
uint32 RandomPlayerbotMgr::AddRandomBots()
{
@@ -514,7 +539,9 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
}
if (maxAllowedBotCount)
LOG_ERROR("playerbots", "Not enough random bot accounts available. Need {} more!!",
LOG_ERROR("playerbots",
"Not enough random bot accounts available. Need {} more, try to increase RandomBotAccountCount "
"in your conf file",
ceil(maxAllowedBotCount / 10));
}
@@ -590,10 +617,18 @@ void RandomPlayerbotMgr::CheckBgQueue()
BgCheckTimer = time(nullptr);
LOG_INFO("playerbots", "Checking BG Queue...");
LOG_DEBUG("playerbots", "Checking BG Queue...");
BattlegroundData.clear();
for (int bracket = BG_BRACKET_ID_FIRST; bracket < MAX_BATTLEGROUND_BRACKETS; ++bracket)
{
for (int queueType = BATTLEGROUND_QUEUE_AV; queueType < MAX_BATTLEGROUND_QUEUE_TYPES; ++queueType)
{
BattlegroundData[queueType][bracket] = BattlegroundInfo();
}
}
for (Player* player : players)
{
if (!player->InBattlegroundQueue())
@@ -840,7 +875,8 @@ void RandomPlayerbotMgr::LogBattlegroundInfo()
for (const auto& bracketIdPair : queueTypePair.second)
{
auto& bgInfo = bracketIdPair.second;
if (bgInfo.minLevel == 0)
continue;
LOG_INFO("playerbots",
"ARENA:{} {}: Player (Skirmish:{}, Rated:{}) Bots (Skirmish:{}, Rated:{}) Total (Skirmish:{} "
"Rated:{}), Instances (Skirmish:{} Rated:{})",
@@ -889,6 +925,8 @@ void RandomPlayerbotMgr::LogBattlegroundInfo()
for (const auto& bracketIdPair : queueTypePair.second)
{
auto& bgInfo = bracketIdPair.second;
if (bgInfo.minLevel == 0)
continue;
LOG_INFO("playerbots", "BG:{} {}: Player ({}:{}) Bot ({}:{}) Total (A:{} H:{}), Instances {}", _bgType,
std::to_string(bgInfo.minLevel) + "-" + std::to_string(bgInfo.maxLevel),
@@ -897,7 +935,7 @@ void RandomPlayerbotMgr::LogBattlegroundInfo()
bgInfo.bgHordePlayerCount + bgInfo.bgHordeBotCount, bgInfo.bgInstanceCount);
}
}
LOG_INFO("playerbots", "BG Queue check finished");
LOG_DEBUG("playerbots", "BG Queue check finished");
}
void RandomPlayerbotMgr::CheckLfgQueue()
@@ -905,7 +943,7 @@ void RandomPlayerbotMgr::CheckLfgQueue()
if (!LfgCheckTimer || time(nullptr) > (LfgCheckTimer + 30))
LfgCheckTimer = time(nullptr);
LOG_INFO("playerbots", "Checking LFG Queue...");
LOG_DEBUG("playerbots", "Checking LFG Queue...");
// Clear LFG list
LfgDungeons[TEAM_ALLIANCE].clear();
@@ -935,7 +973,7 @@ void RandomPlayerbotMgr::CheckLfgQueue()
}
}
LOG_INFO("playerbots", "LFG Queue check finished");
LOG_DEBUG("playerbots", "LFG Queue check finished");
}
void RandomPlayerbotMgr::CheckPlayers()
@@ -965,10 +1003,7 @@ void RandomPlayerbotMgr::CheckPlayers()
LOG_INFO("playerbots", "Max player level is {}, max bot level set to {}", playersLevel - 3, playersLevel);
}
void RandomPlayerbotMgr::ScheduleRandomize(uint32 bot, uint32 time)
{
SetEventValue(bot, "randomize", 1, time);
}
void RandomPlayerbotMgr::ScheduleRandomize(uint32 bot, uint32 time) { SetEventValue(bot, "randomize", 1, time); }
void RandomPlayerbotMgr::ScheduleTeleport(uint32 bot, uint32 time)
{
@@ -1084,9 +1119,7 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
if (update)
ProcessBot(player);
randomTime = urand(
sPlayerbotAIConfig->minRandomBotReviveTime,
sPlayerbotAIConfig->maxRandomBotReviveTime);
randomTime = urand(sPlayerbotAIConfig->minRandomBotReviveTime, sPlayerbotAIConfig->maxRandomBotReviveTime);
SetEventValue(bot, "update", 1, randomTime);
return true;
@@ -1099,9 +1132,8 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
player->GetLevel(), player->GetName().c_str());
LogoutPlayerBot(botGUID);
currentBots.remove(bot);
SetEventValue(bot, "logout", 1, urand(
sPlayerbotAIConfig->minRandomBotInWorldTime,
sPlayerbotAIConfig->maxRandomBotInWorldTime));
SetEventValue(bot, "logout", 1,
urand(sPlayerbotAIConfig->minRandomBotInWorldTime, sPlayerbotAIConfig->maxRandomBotInWorldTime));
return true;
}
@@ -1123,11 +1155,10 @@ bool RandomPlayerbotMgr::ProcessBot(Player* player)
{
if (!GetEventValue(bot, "dead"))
{
uint32 randomTime = urand(
sPlayerbotAIConfig->minRandomBotReviveTime,
sPlayerbotAIConfig->maxRandomBotReviveTime);
LOG_INFO("playerbots", "Mark bot {} as dead, will be revived in {}s.",
player->GetName().c_str(), randomTime);
uint32 randomTime =
urand(sPlayerbotAIConfig->minRandomBotReviveTime, sPlayerbotAIConfig->maxRandomBotReviveTime);
LOG_DEBUG("playerbots", "Mark bot {} as dead, will be revived in {}s.", player->GetName().c_str(),
randomTime);
SetEventValue(bot, "dead", 1, sPlayerbotAIConfig->maxRandomBotInWorldTime);
SetEventValue(bot, "revive", 1, randomTime);
return false;
@@ -1150,11 +1181,10 @@ bool RandomPlayerbotMgr::ProcessBot(Player* player)
LOG_INFO("playerbots", "Bot {} remove from group since leader is random bot.", player->GetName().c_str());
}
// only randomize and teleport idle bots
bool idleBot = false;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
if (botAI)
if (botAI)
{
if (TravelTarget* target = botAI->GetAiObjectContext()->GetValue<TravelTarget*>("travel target")->Get())
{
@@ -1163,10 +1193,10 @@ bool RandomPlayerbotMgr::ProcessBot(Player* player)
idleBot = true;
}
}
else
else
{
idleBot = true;
}
}
}
if (idleBot)
{
@@ -1174,7 +1204,7 @@ bool RandomPlayerbotMgr::ProcessBot(Player* player)
uint32 randomize = GetEventValue(bot, "randomize");
if (!randomize)
{
// bool randomiser = true;
// bool randomiser = true;
// if (player->GetGuildId())
// {
// if (Guild* guild = sGuildMgr->GetGuildById(player->GetGuildId()))
@@ -1196,11 +1226,10 @@ bool RandomPlayerbotMgr::ProcessBot(Player* player)
// if (randomiser)
// {
Randomize(player);
LOG_INFO("playerbots", "Bot #{} {}:{} <{}>: randomized",
bot, player->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", player->GetLevel(), player->GetName());
uint32 randomTime = urand(
sPlayerbotAIConfig->minRandomBotRandomizeTime,
sPlayerbotAIConfig->maxRandomBotRandomizeTime);
LOG_DEBUG("playerbots", "Bot #{} {}:{} <{}>: randomized", bot,
player->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", player->GetLevel(), player->GetName());
uint32 randomTime =
urand(sPlayerbotAIConfig->minRandomBotRandomizeTime, sPlayerbotAIConfig->maxRandomBotRandomizeTime);
ScheduleRandomize(bot, randomTime);
return true;
}
@@ -1216,12 +1245,11 @@ bool RandomPlayerbotMgr::ProcessBot(Player* player)
uint32 teleport = GetEventValue(bot, "teleport");
if (!teleport)
{
LOG_INFO("playerbots", "Bot #{} <{}>: teleport for level and refresh", bot, player->GetName());
LOG_DEBUG("playerbots", "Bot #{} <{}>: teleport for level and refresh", bot, player->GetName());
Refresh(player);
RandomTeleportForLevel(player);
uint32 time = urand(
sPlayerbotAIConfig->minRandomBotTeleportInterval,
sPlayerbotAIConfig->maxRandomBotTeleportInterval);
uint32 time = urand(sPlayerbotAIConfig->minRandomBotTeleportInterval,
sPlayerbotAIConfig->maxRandomBotTeleportInterval);
ScheduleTeleport(bot, time);
return true;
}
@@ -1246,7 +1274,7 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
{
// ignore when alrdy teleported or not in the world yet.
if (bot->IsBeingTeleported() || !bot->IsInWorld())
return;
return;
// ignore when in queue for battle grounds.
if (bot->InBattlegroundQueue())
@@ -1262,11 +1290,10 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI)
{
{
// ignore when in when taxi with boat/zeppelin and has players nearby
if (bot->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) &&
bot->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING) &&
botAI->HasPlayerNearby())
if (bot->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && bot->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING) &&
botAI->HasPlayerNearby())
return;
}
@@ -1355,17 +1382,19 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
{
continue;
}
if (bot->GetLevel() <= 18 && (loc.GetMapId() != pInfo->mapId || dis > 10000.0f))
if (bot->GetLevel() <= 16 && (loc.GetMapId() != pInfo->mapId || dis > 10000.0f))
{
continue;
}
const LocaleConstant& locale = sWorld->GetDefaultDbcLocale();
LOG_INFO("playerbots",
"Random teleporting bot {} (level {}) to Map: {} ({}) Zone: {} ({}) Area: {} ({}) {},{},{} ({}/{} "
"locations)",
bot->GetName().c_str(), bot->GetLevel(), map->GetId(), map->GetMapName(), zone->ID,
zone->area_name[locale], area->ID, area->area_name[locale], x, y, z, i + 1, tlocs.size());
LOG_DEBUG("playerbots",
"Random teleporting bot {} (level {}) to Map: {} ({}) Zone: {} ({}) Area: {} ({}) ZoneLevel: {} "
"AreaLevel: {} {},{},{} ({}/{} "
"locations)",
bot->GetName().c_str(), bot->GetLevel(), map->GetId(), map->GetMapName(), zone->ID,
zone->area_name[locale], area->ID, area->area_name[locale], zone->area_level, area->area_level, x, y,
z, i + 1, tlocs.size());
if (hearth)
{
@@ -1404,32 +1433,35 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
"position_x, "
"position_y, "
"position_z, "
"t.minlevel "
"t.minlevel, "
"t.maxlevel "
"FROM "
"(SELECT "
"map, "
"MIN( c.guid ) guid, "
"t.entry "
"MIN( c.guid ) guid "
"FROM "
"creature c "
"INNER JOIN creature_template t ON c.id1 = t.entry "
"WHERE "
"t.npcflag = 0 "
"AND t.lootid != 0 "
"AND t.unit_flags != 768 "
"AND t.maxlevel - t.minlevel < 3 "
"AND map IN ({}) "
"AND c.id1 != 32820 "
"AND t.entry not in (32820, 24196, 30627, 30617) "
"AND c.spawntimesecs < 1000 "
"AND t.faction != 188 "
"AND t.faction not in (11, 71, 79, 85, 188, 1575) "
"AND (t.unit_flags & 256) = 0 "
"AND (t.unit_flags & 4096) = 0 "
"AND t.rank = 0 "
// "AND (t.flags_extra & 32768) = 0 "
"GROUP BY "
"map, "
"ROUND( position_x / 500 ), "
"ROUND( position_y / 500 ), "
"ROUND( position_z / 50), "
"t.entry "
"ROUND(position_x / 50), "
"ROUND(position_y / 50), "
"ROUND(position_z / 50) "
"HAVING "
"count(*) > 7) AS g "
"count(*) >= 2) "
"AS g "
"INNER JOIN creature c ON g.guid = c.guid "
"INNER JOIN creature_template t on c.id1 = t.entry "
"ORDER BY "
@@ -1445,7 +1477,9 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
float x = fields[1].Get<float>();
float y = fields[2].Get<float>();
float z = fields[3].Get<float>();
uint32 level = fields[4].Get<uint32>();
uint32 min_level = fields[4].Get<uint32>();
uint32 max_level = fields[5].Get<uint32>();
uint32 level = (min_level + max_level + 1) / 2;
WorldLocation loc(mapId, x, y, z, 0);
collected_locs++;
for (int32 l = (int32)level - (int32)sPlayerbotAIConfig->randomBotTeleHigherLevel;
@@ -1459,8 +1493,117 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
}
} while (results->NextRow());
}
LOG_INFO("playerbots", "{} locations for level collected.", collected_locs);
LOG_INFO("playerbots", ">> {} locations for level collected.", collected_locs);
LOG_INFO("playerbots", "Preparing innkeepers locations for level collected...");
if (sPlayerbotAIConfig->enableNewRpgStrategy)
{
results = WorldDatabase.Query(
"SELECT "
"map, "
"position_x, "
"position_y, "
"position_z, "
"orientation, "
"t.faction, "
"t.entry "
"FROM "
"creature c "
"INNER JOIN creature_template t on c.id1 = t.entry "
"WHERE "
"t.npcflag & 73728 "
"AND map IN ({}) "
"ORDER BY "
"t.minlevel;",
sPlayerbotAIConfig->randomBotMapsAsString.c_str());
collected_locs = 0;
if (results)
{
do
{
Field* fields = results->Fetch();
uint16 mapId = fields[0].Get<uint16>();
float x = fields[1].Get<float>();
float y = fields[2].Get<float>();
float z = fields[3].Get<float>();
float orient = fields[4].Get<float>();
uint32 faction = fields[5].Get<uint32>();
uint32 c_entry = fields[6].Get<uint32>();
const FactionTemplateEntry* entry = sFactionTemplateStore.LookupEntry(faction);
WorldLocation loc(mapId, x + cos(orient) * 5.0f, y + sin(orient) * 5.0f, z + 0.5f, orient + M_PI);
collected_locs++;
Map* map = sMapMgr->FindMap(loc.GetMapId(), 0);
if (!map)
continue;
const AreaTableEntry* area = sAreaTableStore.LookupEntry(map->GetAreaId(1, x, y, z));
uint32 zoneId = area->zone ? area->zone : area->ID;
uint32 level = area->area_level;
for (int i = 5; i <= maxLevel; i++)
{
std::vector<WorldLocation>& locs = locsPerLevelCache[i];
int counter = 0;
WorldLocation levelLoc;
for (auto& checkLoc : locs)
{
if (loc.GetMapId() != checkLoc.GetMapId())
continue;
if (loc.GetExactDist(checkLoc) > 1500.0f)
continue;
if (zoneId !=
map->GetZoneId(1, checkLoc.GetPositionX(), checkLoc.GetPositionY(), checkLoc.GetPositionZ()))
continue;
counter++;
levelLoc = checkLoc;
if (counter >= 15)
break;
}
if (counter < 15)
continue;
if (!(entry->hostileMask & 4))
{
hordeStarterPerLevelCache[i].push_back(loc);
}
if (!(entry->hostileMask & 2))
{
allianceStarterPerLevelCache[i].push_back(loc);
}
LOG_DEBUG("playerbots", "Area: {} Level: {} creature_entry: {} add to: {} {}({},{},{},{})", area->ID,
level, c_entry, i, counter, levelLoc.GetPositionX(), levelLoc.GetPositionY(),
levelLoc.GetPositionZ(), levelLoc.GetMapId());
}
} while (results->NextRow());
}
// add all initial position
for (uint32 i = 1; i < MAX_RACES; i++)
{
for (uint32 j = 1; j < MAX_CLASSES; j++)
{
PlayerInfo const* info = sObjectMgr->GetPlayerInfo(i, j);
if (!info)
continue;
WorldPosition pos(info->mapId, info->positionX, info->positionY, info->positionZ, info->orientation);
for (int32 l = 1; l <= 5; l++)
{
if ((1 << (i - 1)) & RACEMASK_ALLIANCE)
allianceStarterPerLevelCache[(uint8)l].push_back(pos);
else
hordeStarterPerLevelCache[(uint8)l].push_back(pos);
}
break;
}
}
LOG_INFO("playerbots", ">> {} innkeepers locations for level collected.", collected_locs);
}
results = WorldDatabase.Query(
"SELECT "
"map, "
@@ -1482,6 +1625,7 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
"AND t.faction != 69 "
"AND t.entry != 30606 "
"AND t.entry != 30608 "
"AND t.entry != 29282 "
"AND t.faction != 69 "
"AND map IN ({}) "
"ORDER BY "
@@ -1523,15 +1667,15 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
}
} while (results->NextRow());
}
LOG_INFO("playerbots", "{} banker locations for level collected.", collected_locs);
LOG_INFO("playerbots", ">> {} banker locations for level collected.", collected_locs);
}
void RandomPlayerbotMgr::PrepareAddclassCache()
{
int32 maxAccountId = sPlayerbotAIConfig->randomBotAccounts.back();
int32 minIdx =
sPlayerbotAIConfig->randomBotAccounts.size() - 1 >= sPlayerbotAIConfig->addClassAccountPoolSize
? sPlayerbotAIConfig->randomBotAccounts.size() - sPlayerbotAIConfig->addClassAccountPoolSize : 0;
int32 minIdx = sPlayerbotAIConfig->randomBotAccounts.size() - 1 >= sPlayerbotAIConfig->addClassAccountPoolSize
? sPlayerbotAIConfig->randomBotAccounts.size() - sPlayerbotAIConfig->addClassAccountPoolSize
: 0;
int32 minAccountId = sPlayerbotAIConfig->randomBotAccounts[minIdx];
if (minAccountId < 0)
{
@@ -1561,7 +1705,7 @@ void RandomPlayerbotMgr::PrepareAddclassCache()
} while (results->NextRow());
}
}
LOG_INFO("playerbots", "{} characters collected for addclass command.", collected);
LOG_INFO("playerbots", ">> {} characters collected for addclass command.", collected);
}
void RandomPlayerbotMgr::RandomTeleportForLevel(Player* bot)
@@ -1571,15 +1715,20 @@ void RandomPlayerbotMgr::RandomTeleportForLevel(Player* bot)
uint32 level = bot->GetLevel();
uint8 race = bot->getRace();
std::vector<WorldLocation>* locs = nullptr;
if (sPlayerbotAIConfig->enableNewRpgStrategy)
locs = IsAlliance(race) ? &allianceStarterPerLevelCache[level] : &hordeStarterPerLevelCache[level];
else
locs = &locsPerLevelCache[level];
LOG_DEBUG("playerbots", "Random teleporting bot {} for level {} ({} locations available)", bot->GetName().c_str(),
bot->GetLevel(), locsPerLevelCache[level].size());
if (level > 10 && urand(0, 100) < sPlayerbotAIConfig->probTeleToBankers * 100)
bot->GetLevel(), locs->size());
if (level >= 10 && urand(0, 100) < sPlayerbotAIConfig->probTeleToBankers * 100)
{
RandomTeleport(bot, bankerLocsPerLevelCache[level], true);
}
else
{
RandomTeleport(bot, locsPerLevelCache[level]);
RandomTeleport(bot, *locs);
}
}
@@ -1590,10 +1739,15 @@ void RandomPlayerbotMgr::RandomTeleportGrindForLevel(Player* bot)
uint32 level = bot->GetLevel();
uint8 race = bot->getRace();
std::vector<WorldLocation>* locs = nullptr;
if (sPlayerbotAIConfig->enableNewRpgStrategy)
locs = IsAlliance(race) ? &allianceStarterPerLevelCache[level] : &hordeStarterPerLevelCache[level];
else
locs = &locsPerLevelCache[level];
LOG_DEBUG("playerbots", "Random teleporting bot {} for level {} ({} locations available)", bot->GetName().c_str(),
bot->GetLevel(), locsPerLevelCache[level].size());
bot->GetLevel(), locs->size());
RandomTeleport(bot, locsPerLevelCache[level]);
RandomTeleport(bot, *locs);
}
void RandomPlayerbotMgr::RandomTeleport(Player* bot)
@@ -2243,7 +2397,7 @@ void RandomPlayerbotMgr::OnBotLoginInternal(Player* const bot)
{
LOG_INFO("playerbots", "{}/{} Bot {} logged in", playerBots.size(), sRandomPlayerbotMgr->GetMaxAllowedBotCount(),
bot->GetName().c_str());
if (sPlayerbotAIConfig->randomBotFixedLevel)
{
bot->SetPlayerFlag(PLAYER_FLAGS_NO_XP_GAIN);
@@ -2361,7 +2515,8 @@ Player* RandomPlayerbotMgr::GetRandomPlayer()
void RandomPlayerbotMgr::PrintStats()
{
LOG_INFO("playerbots", "{} Random Bots online", playerBots.size());
printStatsTimer = time(nullptr);
LOG_INFO("playerbots", "Random Bots Stats: {} online", playerBots.size());
std::map<uint8, uint32> alliance, horde;
for (uint32 i = 0; i < 10; ++i)
@@ -2403,13 +2558,17 @@ void RandomPlayerbotMgr::PrintStats()
uint32 engine_dead = 0;
uint32 stateCount[MAX_TRAVEL_STATE + 1] = {0};
std::vector<std::pair<Quest const*, int32>> questCount;
std::unordered_map<NewRpgStatus, int> rpgStatusCount;
std::unordered_map<uint32, int> zoneCount;
uint8 maxBotLevel = 0;
for (PlayerBotMap::iterator i = playerBots.begin(); i != playerBots.end(); ++i)
{
Player* bot = i->second;
if (IsAlliance(bot->getRace()))
++alliance[bot->GetLevel() / 10];
++alliance[bot->GetLevel()];
else
++horde[bot->GetLevel() / 10];
++horde[bot->GetLevel()];
maxBotLevel = std::max(maxBotLevel, bot->GetLevel());
++perRace[bot->getRace()];
++perClass[bot->getClass()];
@@ -2463,7 +2622,7 @@ void RandomPlayerbotMgr::PrintStats()
++engine_combat;
else
++engine_dead;
if (botAI->IsHeal(bot, true))
++heal;
else if (botAI->IsTank(bot, true))
@@ -2471,6 +2630,11 @@ void RandomPlayerbotMgr::PrintStats()
else
++dps;
zoneCount[bot->GetZoneId()]++;
if (sPlayerbotAIConfig->enableNewRpgStrategy)
rpgStatusCount[botAI->rpgInfo.status]++;
if (TravelTarget* target = botAI->GetAiObjectContext()->GetValue<TravelTarget*>("travel target")->Get())
{
TravelState state = target->getTravelState();
@@ -2500,18 +2664,22 @@ void RandomPlayerbotMgr::PrintStats()
}
LOG_INFO("playerbots", "Bots level:");
uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
for (uint8 i = 0; i < 10; ++i)
// uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
uint32 currentAlliance = 0, currentHorde = 0;
uint32 step = std::max(1, (maxBotLevel + 4) / 8);
uint32 from = 1;
for (uint8 i = 1; i <= maxBotLevel; ++i)
{
if (!alliance[i] && !horde[i])
continue;
currentAlliance += alliance[i];
currentHorde += horde[i];
uint32 from = i * 10;
uint32 to = std::min(from + 9, maxLevel);
if (!from)
from = 1;
LOG_INFO("playerbots", " {}..{}: {} alliance, {} horde", from, to, alliance[i], horde[i]);
if (((i + 1) % step == 0) || i == maxBotLevel)
{
LOG_INFO("playerbots", " {}..{}: {} alliance, {} horde", from, i, currentAlliance, currentHorde);
currentAlliance = 0;
currentHorde = 0;
from = i + 1;
}
}
LOG_INFO("playerbots", "Bots race:");
@@ -2548,20 +2716,39 @@ void RandomPlayerbotMgr::PrintStats()
LOG_INFO("playerbots", " In BG: {}", inBg);
LOG_INFO("playerbots", " In Rest: {}", rest);
LOG_INFO("playerbots", " Dead: {}", dead);
// LOG_INFO("playerbots", "Bots zone:");
// for (auto &[zond_id, counter] : zoneCount)
// {
// const AreaTableEntry* entry = sAreaTableStore.LookupEntry(zond_id);
// std::string name = PlayerbotAI::GetLocalizedAreaName(entry);
// LOG_INFO("playerbots", " {}: {}", name, counter);
// }
if (sPlayerbotAIConfig->enableNewRpgStrategy)
{
LOG_INFO("playerbots", "Bots rpg status:", dead);
LOG_INFO("playerbots", " IDLE: {}", rpgStatusCount[NewRpgStatus::IDLE]);
LOG_INFO("playerbots", " REST: {}", rpgStatusCount[NewRpgStatus::REST]);
LOG_INFO("playerbots", " GO_GRIND: {}", rpgStatusCount[NewRpgStatus::GO_GRIND]);
LOG_INFO("playerbots", " GO_INNKEEPER: {}", rpgStatusCount[NewRpgStatus::GO_INNKEEPER]);
LOG_INFO("playerbots", " NEAR_RANDOM: {}", rpgStatusCount[NewRpgStatus::NEAR_RANDOM]);
LOG_INFO("playerbots", " NEAR_NPC: {}", rpgStatusCount[NewRpgStatus::NEAR_NPC]);
}
LOG_INFO("playerbots", "Bots engine:", dead);
LOG_INFO("playerbots", " Non-combat: {}", engine_noncombat);
LOG_INFO("playerbots", " Combat: {}", engine_combat);
LOG_INFO("playerbots", " Dead: {}", engine_dead);
LOG_INFO("playerbots", "Bots questing:");
LOG_INFO("playerbots", " Picking quests: {}",
stateCount[TRAVEL_STATE_TRAVEL_PICK_UP_QUEST] + stateCount[TRAVEL_STATE_WORK_PICK_UP_QUEST]);
LOG_INFO("playerbots", " Doing quests: {}",
stateCount[TRAVEL_STATE_TRAVEL_DO_QUEST] + stateCount[TRAVEL_STATE_WORK_DO_QUEST]);
LOG_INFO("playerbots", " Completing quests: {}",
stateCount[TRAVEL_STATE_TRAVEL_HAND_IN_QUEST] + stateCount[TRAVEL_STATE_WORK_HAND_IN_QUEST]);
LOG_INFO("playerbots", " Idling: {}", stateCount[TRAVEL_STATE_IDLE]);
// LOG_INFO("playerbots", "Bots questing:");
// LOG_INFO("playerbots", " Picking quests: {}",
// stateCount[TRAVEL_STATE_TRAVEL_PICK_UP_QUEST] + stateCount[TRAVEL_STATE_WORK_PICK_UP_QUEST]);
// LOG_INFO("playerbots", " Doing quests: {}",
// stateCount[TRAVEL_STATE_TRAVEL_DO_QUEST] + stateCount[TRAVEL_STATE_WORK_DO_QUEST]);
// LOG_INFO("playerbots", " Completing quests: {}",
// stateCount[TRAVEL_STATE_TRAVEL_HAND_IN_QUEST] + stateCount[TRAVEL_STATE_WORK_HAND_IN_QUEST]);
// LOG_INFO("playerbots", " Idling: {}", stateCount[TRAVEL_STATE_IDLE]);
/*sort(questCount.begin(), questCount.end(), [](std::pair<Quest const*, int32> i, std::pair<Quest const*, int32> j)
{return i.second > j.second; });

View File

@@ -171,6 +171,10 @@ public:
void PrepareAddclassCache();
std::map<uint8, std::vector<ObjectGuid>> addclassCache;
std::map<uint8, std::vector<WorldLocation>> locsPerLevelCache;
std::map<uint8, std::vector<WorldLocation>> allianceStarterPerLevelCache;
std::map<uint8, std::vector<WorldLocation>> hordeStarterPerLevelCache;
std::map<uint8, std::vector<WorldLocation>> bankerLocsPerLevelCache;
protected:
void OnBotLoginInternal(Player* const bot) override;
@@ -188,6 +192,7 @@ private:
time_t BgCheckTimer;
time_t LfgCheckTimer;
time_t PlayersCheckTimer;
time_t printStatsTimer;
uint32 AddRandomBots();
bool ProcessBot(uint32 bot);
void ScheduleRandomize(uint32 bot, uint32 time);
@@ -199,8 +204,7 @@ private:
std::vector<Player*> players;
uint32 processTicks;
std::map<uint8, std::vector<WorldLocation>> locsPerLevelCache;
std::map<uint8, std::vector<WorldLocation>> bankerLocsPerLevelCache;
// std::map<uint32, std::vector<WorldLocation>> rpgLocsCache;
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel;

View File

@@ -207,7 +207,10 @@ void PlayerbotFactory::Randomize(bool incremental)
Prepare();
LOG_DEBUG("playerbots", "Resetting player...");
PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "PlayerbotFactory_Reset");
bot->resetTalents(true);
if (!sPlayerbotAIConfig->equipmentPersistence || level < sPlayerbotAIConfig->equipmentPersistenceLevel)
{
bot->resetTalents(true);
}
// bot->SaveToDB(false, false);
ClearSkills();
// bot->SaveToDB(false, false);
@@ -267,7 +270,7 @@ void PlayerbotFactory::Randomize(bool incremental)
pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "PlayerbotFactory_Talents");
LOG_DEBUG("playerbots", "Initializing talents...");
if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
if (!incremental || !sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
{
InitTalentsTree();
}
@@ -302,7 +305,7 @@ void PlayerbotFactory::Randomize(bool incremental)
pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "PlayerbotFactory_Equip");
LOG_DEBUG("playerbots", "Initializing equipmemt...");
if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
if (!incremental || !sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
{
InitEquipment(incremental, incremental ? false : sPlayerbotAIConfig->twoRoundsGearInit);
}
@@ -424,7 +427,7 @@ void PlayerbotFactory::Randomize(bool incremental)
bot->SetHealth(bot->GetMaxHealth());
bot->SetPower(POWER_MANA, bot->GetMaxPower(POWER_MANA));
bot->SaveToDB(false, false);
LOG_INFO("playerbots", "Initialization Done.");
// LOG_INFO("playerbots", "Initialization Done.");
if (pmo)
pmo->finish();
}

View File

@@ -24,6 +24,10 @@
#include "raids/blackwinglair/RaidBwlTriggerContext.h"
#include "raids/naxxramas/RaidNaxxActionContext.h"
#include "raids/naxxramas/RaidNaxxTriggerContext.h"
#include "raids/obsidiansanctum/RaidOsActionContext.h"
#include "raids/obsidiansanctum/RaidOsTriggerContext.h"
#include "raids/eyeofeternity/RaidEoEActionContext.h"
#include "raids/eyeofeternity/RaidEoETriggerContext.h"
#include "raids/moltencore/RaidMcActionContext.h"
#include "raids/moltencore/RaidMcTriggerContext.h"
#include "raids/aq20/RaidAq20ActionContext.h"
@@ -48,6 +52,8 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
actionContexts.Add(new RaidBwlActionContext());
actionContexts.Add(new RaidAq20ActionContext());
actionContexts.Add(new RaidNaxxActionContext());
actionContexts.Add(new RaidOsActionContext());
actionContexts.Add(new RaidEoEActionContext());
actionContexts.Add(new RaidUlduarActionContext());
actionContexts.Add(new RaidIccActionContext());
actionContexts.Add(new WotlkDungeonUKActionContext());
@@ -71,6 +77,8 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
triggerContexts.Add(new RaidBwlTriggerContext());
triggerContexts.Add(new RaidAq20TriggerContext());
triggerContexts.Add(new RaidNaxxTriggerContext());
triggerContexts.Add(new RaidOsTriggerContext());
triggerContexts.Add(new RaidEoETriggerContext());
triggerContexts.Add(new RaidUlduarTriggerContext());
triggerContexts.Add(new RaidIccTriggerContext());
triggerContexts.Add(new WotlkDungeonUKTriggerContext());

View File

@@ -31,6 +31,7 @@
#include "MeleeCombatStrategy.h"
#include "MoveFromGroupStrategy.h"
#include "NamedObjectContext.h"
#include "NewRpgStrategy.h"
#include "NonCombatStrategy.h"
#include "PassiveStrategy.h"
#include "PullStrategy.h"
@@ -82,6 +83,7 @@ public:
creators["reveal"] = &StrategyContext::reveal;
creators["collision"] = &StrategyContext::collision;
creators["rpg"] = &StrategyContext::rpg;
creators["new rpg"] = &StrategyContext::new_rpg;
creators["travel"] = &StrategyContext::travel;
creators["explore"] = &StrategyContext::explore;
creators["map"] = &StrategyContext::map;
@@ -152,6 +154,7 @@ private:
static Strategy* reveal(PlayerbotAI* botAI) { return new RevealStrategy(botAI); }
static Strategy* collision(PlayerbotAI* botAI) { return new CollisionStrategy(botAI); }
static Strategy* rpg(PlayerbotAI* botAI) { return new RpgStrategy(botAI); }
static Strategy* new_rpg(PlayerbotAI* botAI) { return new NewRpgStrategy(botAI); }
static Strategy* travel(PlayerbotAI* botAI) { return new TravelStrategy(botAI); }
static Strategy* explore(PlayerbotAI* botAI) { return new ExploreStrategy(botAI); }
static Strategy* map(PlayerbotAI* botAI) { return new MapStrategy(botAI); }

View File

@@ -62,6 +62,7 @@
#include "VehicleActions.h"
#include "WorldBuffAction.h"
#include "XpGainAction.h"
#include "NewRpgAction.h"
class PlayerbotAI;
@@ -240,6 +241,12 @@ public:
creators["toggle pet spell"] = &ActionContext::toggle_pet_spell;
creators["pet attack"] = &ActionContext::pet_attack;
creators["new rpg status update"] = &ActionContext::new_rpg_status_update;
creators["new rpg go grind"] = &ActionContext::new_rpg_go_grind;
creators["new rpg go innkeeper"] = &ActionContext::new_rpg_go_innkeeper;
creators["new rpg move random"] = &ActionContext::new_rpg_move_random;
creators["new rpg move npc"] = &ActionContext::new_rpg_move_npc;
}
private:
@@ -415,6 +422,12 @@ private:
static Action* toggle_pet_spell(PlayerbotAI* ai) { return new TogglePetSpellAutoCastAction(ai); }
static Action* pet_attack(PlayerbotAI* ai) { return new PetAttackAction(ai); }
static Action* new_rpg_status_update(PlayerbotAI* ai) { return new NewRpgStatusUpdateAction(ai); }
static Action* new_rpg_go_grind(PlayerbotAI* ai) { return new NewRpgGoGrindAction(ai); }
static Action* new_rpg_go_innkeeper(PlayerbotAI* ai) { return new NewRpgGoInnKeeperAction(ai); }
static Action* new_rpg_move_random(PlayerbotAI* ai) { return new NewRpgMoveRandomAction(ai); }
static Action* new_rpg_move_npc(PlayerbotAI* ai) { return new NewRpgMoveNpcAction(ai); }
};
#endif

View File

@@ -74,6 +74,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
{
return false;
}
std::ostringstream msg;
msg << target->GetName();
@@ -88,9 +89,11 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
if (!bot->IsWithinLOSInMap(target))
{
msg << " is not on my sight";
msg << " is not in my sight";
if (verbose)
botAI->TellError(msg.str());
return false;
}
if (target->isDead())
@@ -102,6 +105,15 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
return false;
}
if (sPlayerbotAIConfig->IsInPvpProhibitedZone(bot->GetZoneId())
&& (target->IsPlayer() || target->IsPet() || !bot->IsValidAttackTarget(target)))
{
if (verbose)
botAI->TellError("I cannot attack others in PvP prohibited zones");
return false;
}
// if (bot->IsMounted() && bot->IsWithinLOSInMap(target))
// {
// WorldPacket emptyPacket;

View File

@@ -38,6 +38,7 @@
#include "LootStrategyAction.h"
#include "MailAction.h"
#include "NamedObjectContext.h"
#include "NewRpgAction.h"
#include "PassLeadershipToMasterAction.h"
#include "PositionAction.h"
#include "QueryItemUsageAction.h"
@@ -88,6 +89,7 @@ public:
creators["reputation"] = &ChatActionContext::reputation;
creators["log"] = &ChatActionContext::log;
creators["los"] = &ChatActionContext::los;
creators["rpg status"] = &ChatActionContext::rpg_status;
creators["aura"] = &ChatActionContext::aura;
creators["drop"] = &ChatActionContext::drop;
creators["clean quest log"] = &ChatActionContext::clean_quest_log;
@@ -258,6 +260,7 @@ private:
static Action* reputation(PlayerbotAI* botAI) { return new TellReputationAction(botAI); }
static Action* log(PlayerbotAI* botAI) { return new LogLevelAction(botAI); }
static Action* los(PlayerbotAI* botAI) { return new TellLosAction(botAI); }
static Action* rpg_status(PlayerbotAI* botAI) { return new TellRpgStatusAction(botAI); }
static Action* aura(PlayerbotAI* ai) { return new TellAuraAction(ai); }
static Action* ll(PlayerbotAI* botAI) { return new LootStrategyAction(botAI); }
static Action* ss(PlayerbotAI* botAI) { return new SkipSpellsListAction(botAI); }

View File

@@ -10,8 +10,8 @@
#include "LootObjectStack.h"
#include "Playerbots.h"
#include "PossibleRpgTargetsValue.h"
#include "ServerFacade.h"
#include "PvpTriggers.h"
#include "ServerFacade.h"
bool AttackEnemyPlayerAction::isUseful()
{
@@ -49,8 +49,15 @@ bool AttackAnythingAction::isUseful()
return false;
std::string const name = std::string(target->GetName());
if (!name.empty() && name.find("Dummy") != std::string::npos) // Target is not a targetdummy
return false;
// Check for invalid targets: Dummy, Charge Target, Melee Target, Ranged Target
if (!name.empty() &&
(name.find("Dummy") != std::string::npos ||
name.find("Charge Target") != std::string::npos ||
name.find("Melee Target") != std::string::npos ||
name.find("Ranged Target") != std::string::npos))
{
return false; // Target is one of the disallowed types
}
// if (!ChooseRpgTargetAction::isFollowValid(bot, target)) //Do not grind mobs far
// away from master.
@@ -129,3 +136,33 @@ bool DpsAssistAction::isUseful()
return true;
}
bool AttackRtiTargetAction::Execute(Event event)
{
Unit* rtiTarget = AI_VALUE(Unit*, "rti target");
if (rtiTarget && rtiTarget->IsInWorld() && rtiTarget->GetMapId() == bot->GetMapId())
{
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({rtiTarget->GetGUID()});
bool result = Attack(botAI->GetUnit(rtiTarget->GetGUID()));
if (result)
{
context->GetValue<ObjectGuid>("pull target")->Set(rtiTarget->GetGUID());
return true;
}
}
else
{
botAI->TellError("I dont see my rti attack target");
}
return false;
}
bool AttackRtiTargetAction::isUseful()
{
if (botAI->ContainsStrategy(STRATEGY_TYPE_HEAL))
return false;
return true;
}

View File

@@ -69,6 +69,8 @@ public:
AttackRtiTargetAction(PlayerbotAI* botAI) : AttackAction(botAI, "attack rti target") {}
std::string const GetTargetName() override { return "rti target"; }
bool Execute(Event event) override;
bool isUseful() override;
};
class AttackEnemyFlagCarrierAction : public AttackAction

View File

@@ -68,6 +68,11 @@ bool CleanQuestLogAction::Execute(Event event)
return false;
}
if (!sPlayerbotAIConfig->dropObsoleteQuests)
{
return false;
}
// Only output this message if "debug rpg" strategy is enabled
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{

View File

@@ -65,88 +65,210 @@ void EquipAction::EquipItem(Item* item)
uint8 slot = item->GetSlot();
const ItemTemplate* itemProto = item->GetTemplate();
uint32 itemId = itemProto->ItemId;
uint8 invType = itemProto->InventoryType;
if (itemProto->InventoryType == INVTYPE_AMMO)
// Handle ammunition separately
if (invType == INVTYPE_AMMO)
{
bot->SetAmmo(itemId);
std::ostringstream out;
out << "equipping " << chat->FormatItem(itemProto);
botAI->TellMaster(out);
return;
}
else
// Handle bags first
bool equippedBag = false;
if (itemProto->Class == ITEM_CLASS_CONTAINER)
{
bool equippedBag = false;
if (itemProto->Class == ITEM_CLASS_CONTAINER)
// Attempt to equip as a bag
Bag* pBag = reinterpret_cast<Bag*>(item);
uint8 newBagSlot = GetSmallestBagSlot();
if (newBagSlot > 0)
{
Bag* pBag = (Bag*)&item;
uint8 newBagSlot = GetSmallestBagSlot();
if (newBagSlot > 0)
uint16 src = ((bagIndex << 8) | slot);
uint16 dst = ((INVENTORY_SLOT_BAG_0 << 8) | newBagSlot);
bot->SwapItem(src, dst);
equippedBag = true;
}
}
// If we didn't equip as a bag, try to equip as gear
if (!equippedBag)
{
uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true);
// Check if the item is a weapon and whether the bot can dual wield or use Titan Grip
bool isWeapon = (itemProto->Class == ITEM_CLASS_WEAPON);
bool canTitanGrip = bot->CanTitanGrip();
bool canDualWield = bot->CanDualWield();
bool isTwoHander = (invType == INVTYPE_2HWEAPON);
bool isValidTGWeapon = false;
if (canTitanGrip && isTwoHander)
{
// Titan Grip-valid 2H weapon subclasses: Axe2, Mace2, Sword2
isValidTGWeapon = (itemProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 ||
itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 ||
itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2);
}
// Check if the main hand currently has a 2H weapon equipped
Item* currentMHItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
bool have2HWeaponEquipped = (currentMHItem && currentMHItem->GetTemplate()->InventoryType == INVTYPE_2HWEAPON);
bool canDualWieldOrTG = (canDualWield || (canTitanGrip && isTwoHander));
// If this is a weapon and we can dual wield or Titan Grip, check if we can improve main/off-hand setup
if (isWeapon && canDualWieldOrTG)
{
// Fetch current main hand and offhand items
Item* mainHandItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
Item* offHandItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
// Set up the stats calculator once and reuse results for performance
StatsWeightCalculator calculator(bot);
calculator.SetItemSetBonus(false);
calculator.SetOverflowPenalty(false);
// Calculate item scores once and store them
float newItemScore = calculator.CalculateItem(itemId);
float mainHandScore = mainHandItem ? calculator.CalculateItem(mainHandItem->GetTemplate()->ItemId) : 0.0f;
float offHandScore = offHandItem ? calculator.CalculateItem(offHandItem->GetTemplate()->ItemId) : 0.0f;
// Determine where this weapon can go
bool canGoMain = (invType == INVTYPE_WEAPON ||
invType == INVTYPE_WEAPONMAINHAND ||
(canTitanGrip && isTwoHander));
bool canTGOff = false;
if (canTitanGrip && isTwoHander && isValidTGWeapon)
canTGOff = true;
bool canGoOff = (invType == INVTYPE_WEAPON ||
invType == INVTYPE_WEAPONOFFHAND ||
canTGOff);
// Check if the main hand item can go to offhand if needed
bool mainHandCanGoOff = false;
if (mainHandItem)
{
uint16 src = ((bagIndex << 8) | slot);
uint16 dst = ((INVENTORY_SLOT_BAG_0 << 8) | newBagSlot);
bot->SwapItem(src, dst);
equippedBag = true;
const ItemTemplate* mhProto = mainHandItem->GetTemplate();
bool mhIsValidTG = false;
if (canTitanGrip && mhProto->InventoryType == INVTYPE_2HWEAPON)
{
mhIsValidTG = (mhProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 ||
mhProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 ||
mhProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2);
}
mainHandCanGoOff = (mhProto->InventoryType == INVTYPE_WEAPON ||
mhProto->InventoryType == INVTYPE_WEAPONOFFHAND ||
(mhProto->InventoryType == INVTYPE_2HWEAPON && mhIsValidTG));
}
// Priority 1: Replace main hand if the new weapon is strictly better
// and if conditions allow (e.g. no conflicting 2H logic)
bool betterThanMH = (newItemScore > mainHandScore);
bool mhConditionOK = ((invType != INVTYPE_2HWEAPON && !have2HWeaponEquipped) ||
(canTitanGrip && isValidTGWeapon));
if (canGoMain && betterThanMH && mhConditionOK)
{
// Equip new weapon in main hand
{
WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
ObjectGuid newItemGuid = item->GetGUID();
eqPacket << newItemGuid << uint8(EQUIPMENT_SLOT_MAINHAND);
bot->GetSession()->HandleAutoEquipItemSlotOpcode(eqPacket);
}
// Try moving old main hand weapon to offhand if beneficial
if (mainHandItem && mainHandCanGoOff && (!offHandItem || mainHandScore > offHandScore))
{
const ItemTemplate* oldMHProto = mainHandItem->GetTemplate();
WorldPacket offhandPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
ObjectGuid oldMHGuid = mainHandItem->GetGUID();
offhandPacket << oldMHGuid << uint8(EQUIPMENT_SLOT_OFFHAND);
bot->GetSession()->HandleAutoEquipItemSlotOpcode(offhandPacket);
std::ostringstream moveMsg;
moveMsg << "Main hand upgrade found. Moving " << chat->FormatItem(oldMHProto) << " to offhand";
botAI->TellMaster(moveMsg);
}
std::ostringstream out;
out << "Equipping " << chat->FormatItem(itemProto) << " in main hand";
botAI->TellMaster(out);
return;
}
// Priority 2: If not better than main hand, check if better than offhand
else if (canGoOff && newItemScore > offHandScore)
{
// Equip in offhand
WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
ObjectGuid newItemGuid = item->GetGUID();
eqPacket << newItemGuid << uint8(EQUIPMENT_SLOT_OFFHAND);
bot->GetSession()->HandleAutoEquipItemSlotOpcode(eqPacket);
std::ostringstream out;
out << "Equipping " << chat->FormatItem(itemProto) << " in offhand";
botAI->TellMaster(out);
return;
}
else
{
// No improvement, do nothing
return;
}
}
if (!equippedBag)
// If not a special dual-wield/TG scenario or no improvement found, fall back to original logic
if (dstSlot == EQUIPMENT_SLOT_FINGER1 ||
dstSlot == EQUIPMENT_SLOT_TRINKET1 ||
(dstSlot == EQUIPMENT_SLOT_MAINHAND && canDualWield &&
((invType != INVTYPE_2HWEAPON && !have2HWeaponEquipped) || (canTitanGrip && isValidTGWeapon))))
{
uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true);
bool have2HWeapon = false;
bool isValidTGWeapon = false;
if (dstSlot == EQUIPMENT_SLOT_MAINHAND)
{
Item* currentWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
have2HWeapon = currentWeapon && currentWeapon->GetTemplate()->InventoryType == INVTYPE_2HWEAPON;
isValidTGWeapon = itemProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 ||
itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 ||
itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2;
}
if (dstSlot == EQUIPMENT_SLOT_FINGER1 ||
dstSlot == EQUIPMENT_SLOT_TRINKET1 ||
(dstSlot == EQUIPMENT_SLOT_MAINHAND && bot->CanDualWield() &&
((itemProto->InventoryType != INVTYPE_2HWEAPON && !have2HWeapon) || (bot->CanTitanGrip() && isValidTGWeapon))))
{
Item* const equippedItems[2] = {
bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot),
bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot + 1)
};
// Handle ring/trinket dual-slot logic
Item* const equippedItems[2] = {
bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot),
bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot + 1)
};
if (equippedItems[0])
if (equippedItems[0])
{
if (equippedItems[1])
{
if (equippedItems[1])
{
// Both slots are full - determine worst item to replace
StatsWeightCalculator calculator(bot);
calculator.SetItemSetBonus(false);
calculator.SetOverflowPenalty(false);
// float newItemScore = calculator.CalculateItem(itemId);
float equippedItemScore[2] = {
equippedItemScore[0] = calculator.CalculateItem(equippedItems[0]->GetTemplate()->ItemId),
equippedItemScore[1] = calculator.CalculateItem(equippedItems[1]->GetTemplate()->ItemId)
};
// Both slots are full - pick the worst item to replace
StatsWeightCalculator calc(bot);
calc.SetItemSetBonus(false);
calc.SetOverflowPenalty(false);
// Second item is worse than first, equip candidate item in second slot
if (equippedItemScore[0] > equippedItemScore[1])
{
dstSlot++;
}
}
else // No item equipped in slot 2, equip in that slot instead of replacing first item
float firstItemScore = calc.CalculateItem(equippedItems[0]->GetTemplate()->ItemId);
float secondItemScore = calc.CalculateItem(equippedItems[1]->GetTemplate()->ItemId);
// If the second slot is worse, place the new item there
if (firstItemScore > secondItemScore)
{
dstSlot++;
}
}
else
{
// Second slot empty, use it
dstSlot++;
}
}
}
// Equip the item in the chosen slot
{
WorldPacket packet(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
ObjectGuid itemguid = item->GetGUID();
packet << itemguid << dstSlot;
bot->GetSession()->HandleAutoEquipItemSlotOpcode(packet);
// WorldPacket packet(CMSG_AUTOEQUIP_ITEM, 2);
// packet << bagIndex << slot;
// bot->GetSession()->HandleAutoEquipItemOpcode(packet);
}
}
@@ -155,6 +277,7 @@ void EquipAction::EquipItem(Item* item)
botAI->TellMaster(out);
}
bool EquipUpgradesAction::Execute(Event event)
{
if (!sPlayerbotAIConfig->autoEquipUpgradeLoot && !sRandomPlayerbotMgr->IsRandomBot(bot))

View File

@@ -75,7 +75,7 @@ bool InventoryChangeFailureAction::Execute(Event event)
messages[EQUIP_ERR_BAG_FULL4] = messages[EQUIP_ERR_BAG_FULL];
messages[EQUIP_ERR_ITEM_SOLD_OUT] = messages[EQUIP_ERR_ITEM_IS_CURRENTLY_SOLD_OUT];
messages[EQUIP_ERR_OBJECT_IS_BUSY] = "This object is busy";
messages[EQUIP_ERR_NOT_IN_COMBAT] = "I am not in combat";
messages[EQUIP_ERR_NOT_IN_COMBAT] = "I am in combat";
messages[EQUIP_ERR_NOT_WHILE_DISARMED] = "Cannot do while disarmed";
messages[EQUIP_ERR_BAG_FULL6] = messages[EQUIP_ERR_BAG_FULL];
messages[EQUIP_ERR_CANT_EQUIP_RANK] = "Not enough rank";

View File

@@ -177,7 +177,7 @@ bool MovementAction::MoveToLOS(WorldObject* target, bool ranged)
}
bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, bool react, bool normal_only,
bool exact_waypoint, MovementPriority priority)
bool exact_waypoint, MovementPriority priority, bool lessDelay)
{
UpdateMovementState();
if (!IsMovingAllowed(mapId, x, y, z))
@@ -210,6 +210,10 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
mm.Clear();
mm.MovePoint(0, x, y, z, generatePath);
float delay = 1000.0f * (distance / vehicleBase->GetSpeed(MOVE_RUN));
if (lessDelay)
{
delay -= botAI->GetReactDelay();
}
delay = std::max(.0f, delay);
delay = std::min((float)sPlayerbotAIConfig->maxWaitForMove, delay);
AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), delay, priority);
@@ -233,6 +237,10 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
mm.Clear();
mm.MovePoint(0, x, y, z, generatePath);
float delay = 1000.0f * MoveDelay(distance);
if (lessDelay)
{
delay -= botAI->GetReactDelay();
}
delay = std::max(.0f, delay);
delay = std::min((float)sPlayerbotAIConfig->maxWaitForMove, delay);
AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), delay, priority);
@@ -264,6 +272,10 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
mm.Clear();
mm.MovePoint(0, endP.x, endP.y, endP.z, generatePath);
float delay = 1000.0f * MoveDelay(distance);
if (lessDelay)
{
delay -= botAI->GetReactDelay();
}
delay = std::max(.0f, delay);
delay = std::min((float)sPlayerbotAIConfig->maxWaitForMove, delay);
AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), delay, priority);
@@ -822,7 +834,7 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance)
PathGenerator path(bot);
path.CalculatePath(tx, ty, tz, false);
PathType type = path.GetPathType();
int typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE;
int typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_SHORTCUT;
if (!(type & typeOk))
return false;
float shortenTo = distance;
@@ -838,7 +850,7 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance)
path.ShortenPathUntilDist(G3D::Vector3(tx, ty, tz), shortenTo);
G3D::Vector3 endPos = path.GetPath().back();
return MoveTo(target->GetMapId(), endPos.x, endPos.y, endPos.z, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT);
MovementPriority::MOVEMENT_COMBAT, true);
}
float MovementAction::GetFollowAngle()
@@ -923,10 +935,10 @@ bool MovementAction::IsMovingAllowed()
if (botAI->IsInVehicle() && !botAI->IsInVehicle(true))
return false;
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) ||
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))
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() ||
bot->HasConfuseAura() || bot->IsCharmed() || bot->HasStunAura() ||
bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
return false;
if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE)
@@ -2013,8 +2025,15 @@ Position MovementAction::BestPositionForMeleeToFlee(Position pos, float radius)
}
bool strict = checkAngle.strict;
float fleeDis = std::min(radius + 1.0f, sPlayerbotAIConfig->fleeDistance);
Position fleePos{bot->GetPositionX() + cos(angle) * fleeDis, bot->GetPositionY() + sin(angle) * fleeDis,
bot->GetPositionZ()};
float dx = bot->GetPositionX() + cos(angle) * fleeDis;
float dy = bot->GetPositionY() + sin(angle) * fleeDis;
float dz = bot->GetPositionZ();
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), dx, dy, dz))
{
continue;
}
Position fleePos{dx, dy, dz};
if (strict && currentTarget &&
fleePos.GetExactDist(currentTarget) - currentTarget->GetCombatReach() >
sPlayerbotAIConfig->tooCloseDistance &&
@@ -2069,8 +2088,15 @@ Position MovementAction::BestPositionForRangedToFlee(Position pos, float radius)
}
bool strict = checkAngle.strict;
float fleeDis = std::min(radius + 1.0f, sPlayerbotAIConfig->fleeDistance);
Position fleePos{bot->GetPositionX() + cos(angle) * fleeDis, bot->GetPositionY() + sin(angle) * fleeDis,
bot->GetPositionZ()};
float dx = bot->GetPositionX() + cos(angle) * fleeDis;
float dy = bot->GetPositionY() + sin(angle) * fleeDis;
float dz = bot->GetPositionZ();
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), dx, dy, dz))
{
continue;
}
Position fleePos{dx, dy, dz};
if (strict && currentTarget &&
fleePos.GetExactDist(currentTarget) - currentTarget->GetCombatReach() > sPlayerbotAIConfig->spellDistance)
{
@@ -2082,6 +2108,7 @@ Position MovementAction::BestPositionForRangedToFlee(Position pos, float radius)
{
continue;
}
if (pos.GetExactDist(fleePos) > farestDis)
{
farestDis = pos.GetExactDist(fleePos);
@@ -2537,9 +2564,9 @@ bool SetFacingTargetAction::isUseful() { return !AI_VALUE2(bool, "facing", "curr
bool SetFacingTargetAction::isPossible()
{
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) ||
bot->IsBeingTeleported() || bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) || bot->IsCharmed() ||
bot->HasAuraType(SPELL_AURA_MOD_STUN) || bot->HasUnitState(UNIT_STATE_IN_FLIGHT) ||
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
bot->IsBeingTeleported() || bot->HasConfuseAura() || bot->IsCharmed() ||
bot->HasStunAura() || bot->IsInFlight() ||
bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
return false;

View File

@@ -32,7 +32,7 @@ protected:
bool MoveNear(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->contactDistance, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
bool MoveToLOS(WorldObject* target, bool ranged = false);
bool MoveTo(uint32 mapId, float x, float y, float z, bool idle = false, bool react = false,
bool normal_only = false, bool exact_waypoint = false, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
bool normal_only = false, bool exact_waypoint = false, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, bool lessDelay = false);
bool MoveTo(WorldObject* target, float distance = 0.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig->contactDistance, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
float GetFollowAngle();

View File

@@ -22,7 +22,7 @@ bool ReleaseSpiritAction::Execute(Event event)
return false;
}
if (bot->GetCorpse() && bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
if (bot->GetCorpse() && bot->HasPlayerFlag(PLAYER_FLAGS_GHOST))
{
botAI->TellMasterNoFacing("I am already a spirit");
return false;
@@ -149,7 +149,7 @@ bool AutoReleaseSpiritAction::isUseful()
if (bot->InBattleground())
return true;
if (bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
if (bot->HasPlayerFlag(PLAYER_FLAGS_GHOST))
return false;
if (!bot->GetGroup())

View File

@@ -188,7 +188,7 @@ bool FindCorpseAction::Execute(Event event)
if (!moved)
{
moved = botAI->DoSpecificAction("spirit healer");
moved = botAI->DoSpecificAction("spirit healer", Event(), true);
}
}
}
@@ -347,17 +347,17 @@ bool SpiritHealerAction::Execute(Event event)
if (moved)
return true;
if (!botAI->HasActivePlayerMaster())
{
context->GetValue<uint32>("death count")->Set(dCount + 1);
return bot->TeleportTo(ClosestGrave->Map, ClosestGrave->x, ClosestGrave->y, ClosestGrave->z, 0.f);
}
// if (!botAI->HasActivePlayerMaster())
// {
context->GetValue<uint32>("death count")->Set(dCount + 1);
return bot->TeleportTo(ClosestGrave->Map, ClosestGrave->x, ClosestGrave->y, ClosestGrave->z, 0.f);
// }
LOG_INFO("playerbots", "Bot {} {}:{} <{}> can't find a spirit healer", bot->GetGUID().ToString().c_str(),
bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", bot->GetLevel(), bot->GetName().c_str());
// LOG_INFO("playerbots", "Bot {} {}:{} <{}> can't find a spirit healer", bot->GetGUID().ToString().c_str(),
// bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", bot->GetLevel(), bot->GetName().c_str());
botAI->TellError("Cannot find any spirit healer nearby");
// botAI->TellError("Cannot find any spirit healer nearby");
return false;
}
bool SpiritHealerAction::isUseful() { return bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST); }
bool SpiritHealerAction::isUseful() { return bot->HasPlayerFlag(PLAYER_FLAGS_GHOST); }

View File

@@ -80,7 +80,7 @@ bool MarkRtiAction::Execute(Event event)
for (uint8 i = 0; i < 8; i++)
{
ObjectGuid iconGUID = group->GetTargetIcon(i);
if (guid == unit->GetGUID())
if (iconGUID == unit->GetGUID())
{
marked = true;
break;

View File

@@ -207,7 +207,7 @@ bool TradeStatusAction::CheckTrade()
for (uint32 slot = 0; slot < TRADE_SLOT_TRADED_COUNT; ++slot)
{
Item* item = bot->GetTradeData()->GetItem((TradeSlots)slot);
if (item && !item->GetTemplate()->SellPrice)
if (item && !item->GetTemplate()->SellPrice && !item->GetTemplate()->IsConjuredConsumable())
{
std::ostringstream out;
out << chat->FormatItem(item->GetTemplate()) << " - This is not for sale";

View File

@@ -202,6 +202,16 @@ bool AutoGearAction::Execute(Event event)
botAI->TellError("autogear command is not allowed, please check the configuration.");
return false;
}
if (!sPlayerbotAIConfig->autoGearCommandAltBots)
{
if (!sRandomPlayerbotMgr->IsRandomBot(bot))
{
botAI->TellError("You cannot use autogear on alt bots.");
return false;
}
}
botAI->TellMaster("I'm auto gearing");
uint32 gs = sPlayerbotAIConfig->autoGearScoreLimit == 0
? 0

View File

@@ -5,6 +5,7 @@
#include "WorldBuffAction.h"
#include "AiFactory.h"
#include "Event.h"
#include "Playerbots.h"
@@ -39,6 +40,11 @@ std::vector<uint32> WorldBuffAction::NeedWorldBuffs(Unit* unit)
if (wb.classId != 0 && wb.classId != unit->getClass())
continue;
uint8 tab = AiFactory::GetPlayerSpecTab(unit->ToPlayer());
if (wb.specId != tab)
continue;
if (wb.minLevel != 0 && wb.minLevel > unit->GetLevel())
continue;

View File

@@ -101,7 +101,7 @@ void FeralDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
// triggers.push_back(new TriggerNode("not facing target", NextAction::array(0, new NextAction("set facing",
// ACTION_NORMAL + 7), nullptr)));
triggers.push_back(new TriggerNode(
"enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_NORMAL + 8), nullptr)));
"enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_HIGH + 1), nullptr)));
// triggers.push_back(new TriggerNode("enemy too close for melee", NextAction::array(0, new NextAction("move out of
// enemy contact", ACTION_NORMAL + 8), nullptr)));
triggers.push_back(new TriggerNode(

View File

@@ -21,8 +21,8 @@ class WotlkDungeonOccActionContext : public NamedObjectContext<Action>
static Action* avoid_unstable_sphere(PlayerbotAI* ai) { return new AvoidUnstableSphereAction(ai); }
static Action* mount_drake(PlayerbotAI* ai) { return new MountDrakeAction(ai); }
static Action* dismount_drake(PlayerbotAI* ai) { return new DismountDrakeAction(ai); }
static Action* fly_drake(PlayerbotAI* ai) { return new FlyDrakeAction(ai); }
static Action* drake_attack(PlayerbotAI* ai) { return new DrakeAttackAction(ai); }
static Action* fly_drake(PlayerbotAI* ai) { return new OccFlyDrakeAction(ai); }
static Action* drake_attack(PlayerbotAI* ai) { return new OccDrakeAttackAction(ai); }
static Action* avoid_arcane_explosion(PlayerbotAI* ai) { return new AvoidArcaneExplosionAction(ai); }
static Action* time_bomb_spread(PlayerbotAI* ai) { return new TimeBombSpreadAction(ai); }
};

View File

@@ -111,7 +111,7 @@ bool DismountDrakeAction::Execute(Event event)
return false;
}
bool FlyDrakeAction::Execute(Event event)
bool OccFlyDrakeAction::Execute(Event event)
{
Player* master = botAI->GetMaster();
if (!master) { return false; }
@@ -152,7 +152,7 @@ bool FlyDrakeAction::Execute(Event event)
return false;
}
bool DrakeAttackAction::Execute(Event event)
bool OccDrakeAttackAction::Execute(Event event)
{
vehicleBase = bot->GetVehicleBase();
if (!vehicleBase) { return false; }
@@ -188,7 +188,7 @@ bool DrakeAttackAction::Execute(Event event)
return false;
}
bool DrakeAttackAction::CastDrakeSpellAction(Unit* target, uint32 spellId, uint32 cooldown)
bool OccDrakeAttackAction::CastDrakeSpellAction(Unit* target, uint32 spellId, uint32 cooldown)
{
if (botAI->CanCastVehicleSpell(spellId, target))
if (botAI->CastVehicleSpell(spellId, target))
@@ -199,7 +199,7 @@ bool DrakeAttackAction::CastDrakeSpellAction(Unit* target, uint32 spellId, uint3
return false;
}
bool DrakeAttackAction::AmberDrakeAction(Unit* target)
bool OccDrakeAttackAction::AmberDrakeAction(Unit* target)
{
Aura* shockCharges = target->GetAura(SPELL_SHOCK_CHARGE, vehicleBase->GetGUID());
if (shockCharges && shockCharges->GetStackAmount() > 8)
@@ -225,7 +225,7 @@ bool DrakeAttackAction::AmberDrakeAction(Unit* target)
return false;
}
bool DrakeAttackAction::EmeraldDrakeAction(Unit* target)
bool OccDrakeAttackAction::EmeraldDrakeAction(Unit* target)
{
Aura* poisonStacks = target->GetAura(SPELL_LEECHING_POISON, vehicleBase->GetGUID());
if (!poisonStacks || (poisonStacks->GetStackAmount() < 3 ||
@@ -286,7 +286,7 @@ bool DrakeAttackAction::EmeraldDrakeAction(Unit* target)
return false;
}
bool DrakeAttackAction::RubyDrakeAction(Unit* target)
bool OccDrakeAttackAction::RubyDrakeAction(Unit* target)
{
Aura* evasiveCharges = vehicleBase->GetAura(SPELL_EVASIVE_CHARGES);
Aura* evasiveManeuvers = vehicleBase->GetAura(SPELL_EVASIVE_MANEUVERS);

View File

@@ -38,17 +38,17 @@ public:
bool Execute(Event event) override;
};
class FlyDrakeAction : public MovementAction
class OccFlyDrakeAction : public MovementAction
{
public:
FlyDrakeAction(PlayerbotAI* ai) : MovementAction(ai, "fly drake") {}
OccFlyDrakeAction(PlayerbotAI* ai) : MovementAction(ai, "occ fly drake") {}
bool Execute(Event event) override;
};
class DrakeAttackAction : public Action
class OccDrakeAttackAction : public Action
{
public:
DrakeAttackAction(PlayerbotAI* botAI) : Action(botAI, "drake attack") {}
OccDrakeAttackAction(PlayerbotAI* botAI) : Action(botAI, "occ drake attack") {}
bool Execute(Event event) override;
protected:
@@ -57,7 +57,6 @@ protected:
bool AmberDrakeAction(Unit* target);
bool EmeraldDrakeAction(Unit* target);
bool RubyDrakeAction(Unit* target);
};
class AvoidArcaneExplosionAction : public MovementAction

View File

@@ -26,12 +26,12 @@ float MountingDrakeMultiplier::GetValue(Action* action)
return 1.0f;
}
float FlyingMultiplier::GetValue(Action* action)
float OccFlyingMultiplier::GetValue(Action* action)
{
if (bot->GetMapId() != OCULUS_MAP_ID || !bot->GetVehicleBase()) { return 1.0f; }
// Suppresses FollowAction as well as some attack-based movements
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<FlyDrakeAction*>(action))
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<OccFlyDrakeAction*>(action))
{
return 0.0f;
}
@@ -103,7 +103,7 @@ float EregosMultiplier::GetValue(Action* action)
Unit* boss = AI_VALUE2(Unit*, "find target", "ley-guardian eregos");
if (!boss) { return 1.0f; }
if (boss->HasAura(SPELL_PLANAR_SHIFT && dynamic_cast<DrakeAttackAction*>(action)))
if (boss->HasAura(SPELL_PLANAR_SHIFT && dynamic_cast<OccDrakeAttackAction*>(action)))
{
return 0.0f;
}

View File

@@ -21,10 +21,10 @@ class MountingDrakeMultiplier : public Multiplier
virtual float GetValue(Action* action);
};
class FlyingMultiplier : public Multiplier
class OccFlyingMultiplier : public Multiplier
{
public:
FlyingMultiplier(PlayerbotAI* ai) : Multiplier(ai, "flying drake") {}
OccFlyingMultiplier(PlayerbotAI* ai) : Multiplier(ai, "occ flying drake") {}
public:
virtual float GetValue(Action* action);

View File

@@ -36,7 +36,7 @@ void WotlkDungeonOccStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
void WotlkDungeonOccStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
multipliers.push_back(new MountingDrakeMultiplier(botAI));
multipliers.push_back(new FlyingMultiplier(botAI));
multipliers.push_back(new OccFlyingMultiplier(botAI));
multipliers.push_back(new UromMultiplier(botAI));
multipliers.push_back(new EregosMultiplier(botAI));
}

View File

@@ -106,6 +106,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
supported.push_back("reputation");
supported.push_back("log");
supported.push_back("los");
supported.push_back("rpg status");
supported.push_back("aura");
supported.push_back("drop");
supported.push_back("share");

View File

@@ -22,7 +22,7 @@ void CombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("not facing target",
NextAction::array(0, new NextAction("set facing", ACTION_MOVE + 7), nullptr)));
triggers.push_back(
new TriggerNode("pet attack", NextAction::array(0, new NextAction("pet attack", ACTION_NORMAL), nullptr)));
new TriggerNode("pet attack", NextAction::array(0, new NextAction("pet attack", 40.0f), nullptr)));
// triggers.push_back(new TriggerNode("combat long stuck", NextAction::array(0, new NextAction("hearthstone", 0.9f),
// new NextAction("repop", 0.8f), nullptr)));
}

View File

@@ -11,10 +11,10 @@ NextAction** GrindingStrategy::getDefaultActions() { return nullptr; }
void GrindingStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("timer", NextAction::array(0, new NextAction("drink", 4.2f), nullptr)));
triggers.push_back(new TriggerNode("timer", NextAction::array(0, new NextAction("food", 4.1f), nullptr)));
triggers.push_back(new TriggerNode("timer", NextAction::array(0, new NextAction("drink", 10.2f), nullptr)));
triggers.push_back(new TriggerNode("timer", NextAction::array(0, new NextAction("food", 10.1f), nullptr)));
triggers.push_back(
new TriggerNode("no target", NextAction::array(0, new NextAction("attack anything", 4.0f), nullptr)));
new TriggerNode("no target", NextAction::array(0, new NextAction("attack anything", 10.0f), nullptr)));
}
void MoveRandomStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)

View File

@@ -14,7 +14,7 @@ void MeleeCombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
// triggers.push_back(new TriggerNode("not facing target", NextAction::array(0, new NextAction("set facing",
// ACTION_MOVE + 7), nullptr)));
triggers.push_back(new TriggerNode(
"enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_NORMAL + 8), nullptr)));
"enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_HIGH + 1), nullptr)));
// triggers.push_back(new TriggerNode("enemy too close for melee", NextAction::array(0, new NextAction("move out of
// enemy contact", ACTION_NORMAL + 8), nullptr)));
}

View File

@@ -59,8 +59,9 @@ FrostMageStrategy::FrostMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(b
NextAction** FrostMageStrategy::getDefaultActions()
{
return NextAction::array(0, new NextAction("frostbolt", ACTION_DEFAULT + 0.1f),
new NextAction("shoot", ACTION_DEFAULT), nullptr);
return NextAction::array(0, new NextAction("frostbolt", ACTION_DEFAULT + 0.2f),
new NextAction("shoot", ACTION_DEFAULT + 0.1f),
new NextAction("fireball", ACTION_DEFAULT), nullptr);
}
void FrostMageStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)

View File

@@ -131,5 +131,5 @@ void DpsPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
// NextAction::array(0, new NextAction("set facing", ACTION_NORMAL + 7), NULL)));
triggers.push_back(new TriggerNode("enemy out of melee",
NextAction::array(0, new NextAction("reach melee", ACTION_NORMAL + 8), NULL)));
NextAction::array(0, new NextAction("reach melee", ACTION_HIGH + 1), NULL)));
}

View File

@@ -113,5 +113,5 @@ void TankPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("not facing target",
NextAction::array(0, new NextAction("set facing", ACTION_NORMAL + 7), nullptr)));
triggers.push_back(new TriggerNode(
"enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_NORMAL + 8), nullptr)));
"enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_HIGH + 1), nullptr)));
}

View File

@@ -5,6 +5,8 @@
#include "Strategy.h"
#include "RaidBwlStrategy.h"
#include "RaidNaxxStrategy.h"
#include "RaidOsStrategy.h"
#include "RaidEoEStrategy.h"
#include "RaidMcStrategy.h"
#include "RaidAq20Strategy.h"
#include "RaidIccStrategy.h"
@@ -21,6 +23,8 @@ public:
creators["bwl"] = &RaidStrategyContext::bwl;
creators["aq20"] = &RaidStrategyContext::aq20;
creators["naxx"] = &RaidStrategyContext::naxx;
creators["wotlk-os"] = &RaidStrategyContext::wotlk_os;
creators["wotlk-eoe"] = &RaidStrategyContext::wotlk_eoe;
creators["uld"] = &RaidStrategyContext::uld;
creators["icc"] = &RaidStrategyContext::icc;
}
@@ -30,6 +34,8 @@ private:
static Strategy* bwl(PlayerbotAI* botAI) { return new RaidBwlStrategy(botAI); }
static Strategy* aq20(PlayerbotAI* botAI) { return new RaidAq20Strategy(botAI); }
static Strategy* naxx(PlayerbotAI* botAI) { return new RaidNaxxStrategy(botAI); }
static Strategy* wotlk_os(PlayerbotAI* botAI) { return new RaidOsStrategy(botAI); }
static Strategy* wotlk_eoe(PlayerbotAI* botAI) { return new RaidEoEStrategy(botAI); }
static Strategy* uld(PlayerbotAI* botAI) { return new RaidUlduarStrategy(botAI); }
static Strategy* icc(PlayerbotAI* botAI) { return new RaidIccStrategy(botAI); }
};

View File

@@ -0,0 +1,30 @@
#ifndef _PLAYERBOT_RAIDEOEACTIONCONTEXT_H
#define _PLAYERBOT_RAIDEOEACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "RaidEoEActions.h"
class RaidEoEActionContext : public NamedObjectContext<Action>
{
public:
RaidEoEActionContext()
{
creators["malygos position"] = &RaidEoEActionContext::position;
creators["malygos target"] = &RaidEoEActionContext::target;
// creators["pull power spark"] = &RaidEoEActionContext::pull_power_spark;
// creators["kill power spark"] = &RaidEoEActionContext::kill_power_spark;
creators["fly drake"] = &RaidEoEActionContext::fly_drake;
creators["drake attack"] = &RaidEoEActionContext::drake_attack;
}
private:
static Action* position(PlayerbotAI* ai) { return new MalygosPositionAction(ai); }
static Action* target(PlayerbotAI* ai) { return new MalygosTargetAction(ai); }
// static Action* pull_power_spark(PlayerbotAI* ai) { return new PullPowerSparkAction(ai); }
// static Action* kill_power_spark(PlayerbotAI* ai) { return new KillPowerSparkAction(ai); }
static Action* fly_drake(PlayerbotAI* ai) { return new EoEFlyDrakeAction(ai); }
static Action* drake_attack(PlayerbotAI* ai) { return new EoEDrakeAttackAction(ai); }
};
#endif

View File

@@ -0,0 +1,391 @@
#include "RaidEoEActions.h"
#include "RaidEoETriggers.h"
#include "Playerbots.h"
bool MalygosPositionAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "malygos");
if (!boss) { return false; }
uint8 phase = MalygosTrigger::getPhase(bot, boss);
float distance = 5.0f;
if (phase == 1)
{
Unit* spark = nullptr;
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto& target : targets)
{
Unit* unit = botAI->GetUnit(target);
if (unit && unit->GetEntry() == NPC_POWER_SPARK)
{
spark = unit;
break;
}
}
// Position tank
if (botAI->IsMainTank(bot))
{
if (bot->GetDistance2d(MALYGOS_MAINTANK_POSITION.first, MALYGOS_MAINTANK_POSITION.second) > distance)
{
return MoveTo(EOE_MAP_ID, MALYGOS_MAINTANK_POSITION.first, MALYGOS_MAINTANK_POSITION.second, bot->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
// Position DK for spark pull
// else if (spark && bot->IsClass(CLASS_DEATH_KNIGHT))
// {
// if (bot->GetDistance2d(MALYGOS_STACK_POSITION.first, MALYGOS_STACK_POSITION.second) > distance)
// {
// bot->Yell("SPARK SPAWNED, MOVING TO STACK", LANG_UNIVERSAL);
// return MoveTo(EOE_MAP_ID, MALYGOS_STACK_POSITION.first, MALYGOS_STACK_POSITION.second, bot->GetPositionZ(),
// false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
// }
// return false;
// }
else if (spark)
{
return false;
}
else if (!bot->IsClass(CLASS_HUNTER))
{
if (bot->GetDistance2d(MALYGOS_STACK_POSITION.first, MALYGOS_STACK_POSITION.second) > (distance * 3.0f))
{
return MoveTo(EOE_MAP_ID, MALYGOS_STACK_POSITION.first, MALYGOS_STACK_POSITION.second, bot->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
}
return false;
}
bool MalygosTargetAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "malygos");
if (!boss) { return false; }
uint8 phase = MalygosTrigger::getPhase(bot, boss);
if (phase == 1)
{
if (botAI->IsHeal(bot)) { return false; }
// Init this as boss by default, if no better target is found just fall back to Malygos
Unit* newTarget = boss;
// Unit* spark = nullptr;
// GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
// for (auto& target : targets)
// {
// Unit* unit = botAI->GetUnit(target);
// if (unit && unit->GetEntry() == NPC_POWER_SPARK)
// {
// spark = unit;
// break;
// }
// }
// if (spark && botAI->IsRangedDps(bot))
// {
// newTarget = spark;
// }
Unit* currentTarget = AI_VALUE(Unit*, "current target");
if (!currentTarget || currentTarget->GetEntry() != newTarget->GetEntry())
{
return Attack(newTarget);
}
}
else if (phase == 2)
{
if (botAI->IsHeal(bot)) { return false; }
Unit* newTarget = nullptr;
Unit* nexusLord = nullptr;
Unit* scionOfEternity = nullptr;
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto& target : targets)
{
Unit* unit = botAI->GetUnit(target);
if (!unit) { continue; }
if (unit->GetEntry() == NPC_NEXUS_LORD)
{
nexusLord = unit;
}
else if (unit->GetEntry() == NPC_SCION_OF_ETERNITY)
{
scionOfEternity = unit;
}
}
if (botAI->IsRangedDps(bot) && scionOfEternity)
{
newTarget = scionOfEternity;
}
else
{
newTarget = nexusLord;
}
if (!newTarget) { return false; }
Unit* currentTarget = AI_VALUE(Unit*, "current target");
if (!currentTarget || currentTarget->GetEntry() != newTarget->GetEntry())
{
return Attack(newTarget);
}
}
// else if (phase == 3)
// {}
return false;
}
// bool PullPowerSparkAction::Execute(Event event)
// {
// Unit* spark = nullptr;
// GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
// for (auto& target : targets)
// {
// Unit* unit = botAI->GetUnit(target);
// if (unit && unit->GetEntry() == NPC_POWER_SPARK)
// {
// spark = unit;
// break;
// }
// }
// if (!spark) { return false; }
// if (spark->GetDistance2d(MALYGOS_STACK_POSITION.first, MALYGOS_STACK_POSITION.second) > 3.0f)
// {
// bot->Yell("GRIPPING SPARK", LANG_UNIVERSAL);
// return botAI->CastSpell("death grip", spark);
// }
// return false;
// }
// bool PullPowerSparkAction::isPossible()
// {
// Unit* spark = nullptr;
// GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
// for (auto& target : targets)
// {
// Unit* unit = botAI->GetUnit(target);
// if (unit && unit->GetEntry() == NPC_POWER_SPARK)
// {
// spark = unit;
// break;
// }
// }
// return botAI->CanCastSpell(spell, spark);
// }
// bool PullPowerSparkAction::isUseful()
// {
// Unit* spark = nullptr;
// GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
// for (auto& target : targets)
// {
// Unit* unit = botAI->GetUnit(target);
// if (unit && unit->GetEntry() == NPC_POWER_SPARK)
// {
// spark = unit;
// break;
// }
// }
// if (!spark)
// return false;
// if (!spark->IsInWorld() || spark->GetMapId() != bot->GetMapId())
// return false;
// return bot->GetDistance2d(MALYGOS_STACK_POSITION.first, MALYGOS_STACK_POSITION.second) < 3.0f;
// }
// bool KillPowerSparkAction::Execute(Event event)
// {
// return false;
// }
bool EoEFlyDrakeAction::isPossible()
{
Unit* vehicleBase = bot->GetVehicleBase();
return (vehicleBase && vehicleBase->GetEntry() == NPC_WYRMREST_SKYTALON);
}
bool EoEFlyDrakeAction::Execute(Event event)
{
Player* master = botAI->GetMaster();
if (!master) { return false; }
Unit* masterVehicle = master->GetVehicleBase();
Unit* vehicleBase = bot->GetVehicleBase();
if (!vehicleBase || !masterVehicle) { return false; }
MotionMaster* mm = vehicleBase->GetMotionMaster();
Unit* boss = AI_VALUE2(Unit*, "find target", "malygos");
if (boss && false)
{
// Handle as boss encounter instead of formation flight
mm->Clear(false);
float distance = vehicleBase->GetExactDist(boss);
float range = 55.0f; // Drake range is 60yd
if (distance > range)
{
mm->MoveForwards(boss, range - distance);
vehicleBase->SendMovementFlagUpdate();
return true;
}
vehicleBase->SetFacingToObject(boss);
mm->MoveIdle();
vehicleBase->SendMovementFlagUpdate();
return false;
}
if (vehicleBase->GetExactDist(masterVehicle) > 5.0f)
{
uint8 numPlayers;
bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL ? numPlayers = 25 : numPlayers = 10;
// 3/4 of a circle, with frontal cone 90 deg unobstructed
float angle = botAI->GetGroupSlotIndex(bot) * (2*M_PI - M_PI_2)/numPlayers + M_PI_2;
// float angle = M_PI;
vehicleBase->SetCanFly(true);
mm->MoveFollow(masterVehicle, 3.0f, angle);
vehicleBase->SendMovementFlagUpdate();
return true;
}
return false;
}
bool EoEDrakeAttackAction::isPossible()
{
Unit* vehicleBase = bot->GetVehicleBase();
return (vehicleBase && vehicleBase->GetEntry() == NPC_WYRMREST_SKYTALON);
}
bool EoEDrakeAttackAction::Execute(Event event)
{
vehicleBase = bot->GetVehicleBase();
if (!vehicleBase) { return false; }
// Unit* target = AI_VALUE(Unit*, "current target");
Unit* boss = AI_VALUE2(Unit*, "find target", "malygos");
// if (!boss) { return false; }
if (!boss)
{
GuidVector npcs = AI_VALUE(GuidVector, "possible targets");
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (!unit || unit->GetEntry() != NPC_MALYGOS) { continue; }
boss = unit;
break;
}
}
// Check this again to see if a target was assigned
if (!boss) { return false; }
if (botAI->IsHeal(bot))
{
return DrakeHealAction();
}
else
{
return DrakeDpsAction(boss);
}
return false;
}
bool EoEDrakeAttackAction::CastDrakeSpellAction(Unit* target, uint32 spellId, uint32 cooldown)
{
if (botAI->CanCastVehicleSpell(spellId, target))
if (botAI->CastVehicleSpell(spellId, target))
{
vehicleBase->AddSpellCooldown(spellId, 0, cooldown);
return true;
}
return false;
}
bool EoEDrakeAttackAction::DrakeDpsAction(Unit* target)
{
Unit* vehicleBase = bot->GetVehicleBase();
if (!vehicleBase) { return false; }
Vehicle* veh = bot->GetVehicle();
uint8 comboPoints = vehicleBase->GetComboPoints(target);
if (comboPoints >= 2)
{
return CastDrakeSpellAction(target, SPELL_ENGULF_IN_FLAMES, 0);
}
else
{
return CastDrakeSpellAction(target, SPELL_FLAME_SPIKE, 0);
}
}
bool EoEDrakeAttackAction::DrakeHealAction()
{
Unit* vehicleBase = bot->GetVehicleBase();
if (!vehicleBase) { return false; }
Unit* target = vehicleBase->GetComboTarget();
if (!target)
{
// Unit* newTarget = nullptr;
Unit* newTarget = vehicleBase;
GuidVector members = AI_VALUE(GuidVector, "group members");
for (auto& member : members)
{
Unit* unit = botAI->GetUnit(member);
if (!unit)
{
continue;
}
Unit* drake = unit->GetVehicleBase();
if (!drake || drake->IsFullHealth()) { continue; }
if (!newTarget || drake->GetHealthPct() < newTarget->GetHealthPct() - 5.0f)
{
newTarget = drake;
}
}
target = newTarget;
}
uint8 comboPoints = vehicleBase->GetComboPoints(target);
if (comboPoints >= 5)
{
return CastDrakeSpellAction(target, SPELL_LIFE_BURST, 0);
}
else
{
// "Revivify" may be bugged server-side:
// "botAI->CanCastVehicleSpell()" returns SPELL_FAILED_BAD_TARGETS when targeting drakes.
// Forcing the cast attempt seems to succeed, not sure what's going on here.
// return CastDrakeSpellAction(target, SPELL_REVIVIFY, 0);
return botAI->CastVehicleSpell(SPELL_REVIVIFY, target);
}
}

View File

@@ -0,0 +1,69 @@
#ifndef _PLAYERBOT_RAIDEOEACTIONS_H
#define _PLAYERBOT_RAIDEOEACTIONS_H
#include "MovementActions.h"
#include "AttackAction.h"
#include "GenericSpellActions.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
const std::pair<float, float> MALYGOS_MAINTANK_POSITION = {757.0f, 1337.0f};
const std::pair<float, float> MALYGOS_STACK_POSITION = {755.0f, 1301.0f};
class MalygosPositionAction : public MovementAction
{
public:
MalygosPositionAction(PlayerbotAI* botAI, std::string const name = "malygos position")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class MalygosTargetAction : public AttackAction
{
public:
MalygosTargetAction(PlayerbotAI* botAI, std::string const name = "malygos target")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class PullPowerSparkAction : public CastSpellAction
{
public:
PullPowerSparkAction(PlayerbotAI* botAI, std::string const name = "pull power spark")
: CastSpellAction(botAI, "death grip") {}
bool Execute(Event event) override;
bool isPossible() override;
bool isUseful() override;
};
class KillPowerSparkAction : public AttackAction
{
public:
KillPowerSparkAction(PlayerbotAI* botAI, std::string const name = "kill power spark")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class EoEFlyDrakeAction : public MovementAction
{
public:
EoEFlyDrakeAction(PlayerbotAI* ai) : MovementAction(ai, "eoe fly drake") {}
bool Execute(Event event) override;
bool isPossible() override;
};
class EoEDrakeAttackAction : public Action
{
public:
EoEDrakeAttackAction(PlayerbotAI* botAI) : Action(botAI, "eoe drake attack") {}
bool Execute(Event event) override;
bool isPossible() override;
protected:
Unit* vehicleBase;
bool CastDrakeSpellAction(Unit* target, uint32 spellId, uint32 cooldown);
bool DrakeDpsAction(Unit* target);
bool DrakeHealAction();
};
#endif

View File

@@ -0,0 +1,81 @@
#include "RaidEoEMultipliers.h"
#include "ChooseTargetActions.h"
#include "DKActions.h"
#include "DruidActions.h"
#include "DruidBearActions.h"
#include "FollowActions.h"
#include "GenericActions.h"
#include "GenericSpellActions.h"
#include "MovementActions.h"
#include "PaladinActions.h"
#include "RaidEoEActions.h"
#include "RaidEoETriggers.h"
#include "ReachTargetActions.h"
#include "ScriptedCreature.h"
#include "WarriorActions.h"
float MalygosMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "malygos");
uint8 phase = MalygosTrigger::getPhase(bot, boss);
if (phase == 0) { return 1.0f; }
if (phase == 1)
{
if (dynamic_cast<FollowAction*>(action))
{
return 0.0f;
}
if (botAI->IsDps(bot) && dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
if (botAI->IsRangedDps(bot) && dynamic_cast<DropTargetAction*>(action))
{
return 0.0f;
}
if (!botAI->IsMainTank(bot) && dynamic_cast<TankAssistAction*>(action))
{
return 0.0f;
}
// if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<MalygosPositionAction*>(action))
// {
// return 0.0f;
// }
}
else if (phase == 2)
{
if (botAI->IsDps(bot) && dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
if (dynamic_cast<FleeAction*>(action))
{
return 0.0f;
}
if (dynamic_cast<TankAssistAction*>(action))
{
Unit* target = action->GetTarget();
if (target && target->GetEntry() == NPC_SCION_OF_ETERNITY)
return 0.0f;
}
}
else if (phase == 3)
{
// Suppresses FollowAction as well as some attack-based movements
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<EoEFlyDrakeAction*>(action))
{
return 0.0f;
}
}
return 1.0f;
}

View File

@@ -0,0 +1,16 @@
#ifndef _PLAYERRBOT_RAIDEOEMULTIPLIERS_H
#define _PLAYERRBOT_RAIDEOEMULTIPLIERS_H
#include "Multiplier.h"
class MalygosMultiplier : public Multiplier
{
public:
MalygosMultiplier(PlayerbotAI* ai) : Multiplier(ai, "malygos") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,25 @@
#include "RaidEoEStrategy.h"
#include "RaidEoEMultipliers.h"
#include "Strategy.h"
void RaidEoEStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("malygos",
NextAction::array(0, new NextAction("malygos position", ACTION_MOVE), nullptr)));
triggers.push_back(new TriggerNode("malygos",
NextAction::array(0, new NextAction("malygos target", ACTION_RAID + 1), nullptr)));
// triggers.push_back(new TriggerNode("power spark",
// NextAction::array(0, new NextAction("pull power spark", ACTION_RAID + 2), nullptr)));
// triggers.push_back(new TriggerNode("power spark",
// NextAction::array(0, new NextAction("kill power spark", ACTION_RAID + 3), nullptr)));
triggers.push_back(new TriggerNode("group flying",
NextAction::array(0, new NextAction("fly drake", ACTION_NORMAL + 1), nullptr)));
triggers.push_back(new TriggerNode("drake combat",
NextAction::array(0, new NextAction("drake attack", ACTION_NORMAL + 5), nullptr)));
}
void RaidEoEStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
multipliers.push_back(new MalygosMultiplier(botAI));
}

View File

@@ -0,0 +1,17 @@
#ifndef _PLAYERBOT_RAIDEOESTRATEGY_H
#define _PLAYERBOT_RAIDEOESTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h"
class RaidEoEStrategy : public Strategy
{
public:
RaidEoEStrategy(PlayerbotAI* ai) : Strategy(ai) {}
virtual std::string const getName() override { return "wotlk-eoe"; }
virtual void InitTriggers(std::vector<TriggerNode*> &triggers) override;
virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
};
#endif

View File

@@ -0,0 +1,22 @@
#ifndef _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidEoETriggers.h"
class RaidEoETriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidEoETriggerContext()
{
creators["malygos"] = &RaidEoETriggerContext::malygos;
creators["power spark"] = &RaidEoETriggerContext::power_spark;
}
private:
static Trigger* power_spark(PlayerbotAI* ai) { return new PowerSparkTrigger(ai); }
static Trigger* malygos(PlayerbotAI* ai) { return new MalygosTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,53 @@
#include "RaidEoETriggers.h"
#include "SharedDefines.h"
uint8 MalygosTrigger::getPhase(Player* bot, Unit* boss)
{
uint8 phase = 0;
Unit* vehicle = bot->GetVehicleBase();
if (bot->GetMapId() != EOE_MAP_ID) { return phase; }
if (vehicle && vehicle->GetEntry() == NPC_WYRMREST_SKYTALON)
{
phase = 3;
}
else if (boss && boss->HealthAbovePct(50))
{
phase = 1;
}
else if (boss)
{
phase = 2;
}
return phase;
}
bool MalygosTrigger::IsActive()
{
return bool(AI_VALUE2(Unit*, "find target", "malygos"));
}
bool PowerSparkTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "malygos");
if (!boss) { return false; }
if (bot->getClass() != CLASS_DEATH_KNIGHT)
{
return false;
}
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto& target : targets)
{
Unit* unit = botAI->GetUnit(target);
if (unit && unit->GetEntry() == NPC_POWER_SPARK)
{
return true;
}
}
return false;
}

View File

@@ -0,0 +1,58 @@
#ifndef _PLAYERBOT_RAIDEOETRIGGERS_H
#define _PLAYERBOT_RAIDEOETRIGGERS_H
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "Trigger.h"
enum EyeOfEternityIDs
{
NPC_MALYGOS = 28859,
NPC_POWER_SPARK = 30084,
NPC_NEXUS_LORD = 30245,
NPC_SCION_OF_ETERNITY = 30249,
NPC_WYRMREST_SKYTALON = 30161,
SPELL_POWER_SPARK_VISUAL = 55845,
SPELL_POWER_SPARK_GROUND_BUFF = 55852,
SPELL_POWER_SPARK_MALYGOS_BUFF = 56152,
SPELL_TELEPORT_VISUAL = 52096,
SPELL_SCION_ARCANE_BARRAGE = 56397,
SPELL_ARCANE_SHOCK_N = 57058,
SPELL_ARCANE_SHOCK_H = 60073,
SPELL_HASTE = 57060,
SPELL_ALEXSTRASZA_GIFT = 61028,
// Drake Abilities:
// DPS
SPELL_FLAME_SPIKE = 56091,
SPELL_ENGULF_IN_FLAMES = 56092,
// Healing
SPELL_REVIVIFY = 57090,
SPELL_LIFE_BURST = 57143,
// Utility
SPELL_FLAME_SHIELD = 57108,
SPELL_BLAZING_SPEED = 57092,
};
const uint32 EOE_MAP_ID = 616;
class MalygosTrigger : public Trigger
{
public:
MalygosTrigger(PlayerbotAI* botAI) : Trigger(botAI, "malygos") {}
bool IsActive() override;
uint8 static getPhase(Player* bot, Unit* boss);
};
class PowerSparkTrigger : public Trigger
{
public:
PowerSparkTrigger(PlayerbotAI* botAI) : Trigger(botAI, "power spark") {}
bool IsActive() override;
};
#endif

View File

@@ -0,0 +1,30 @@
#ifndef _PLAYERBOT_RAIDOSACTIONCONTEXT_H
#define _PLAYERBOT_RAIDOSACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "RaidOsActions.h"
class RaidOsActionContext : public NamedObjectContext<Action>
{
public:
RaidOsActionContext()
{
creators["sartharion tank position"] = &RaidOsActionContext::tank_position;
creators["avoid twilight fissure"] = &RaidOsActionContext::avoid_twilight_fissure;
creators["avoid flame tsunami"] = &RaidOsActionContext::avoid_flame_tsunami;
creators["sartharion attack priority"] = &RaidOsActionContext::attack_priority;
creators["enter twilight portal"] = &RaidOsActionContext::enter_twilight_portal;
creators["exit twilight portal"] = &RaidOsActionContext::exit_twilight_portal;
}
private:
static Action* tank_position(PlayerbotAI* ai) { return new SartharionTankPositionAction(ai); }
static Action* avoid_twilight_fissure(PlayerbotAI* ai) { return new AvoidTwilightFissureAction(ai); }
static Action* avoid_flame_tsunami(PlayerbotAI* ai) { return new AvoidFlameTsunamiAction(ai); }
static Action* attack_priority(PlayerbotAI* ai) { return new SartharionAttackPriorityAction(ai); }
static Action* enter_twilight_portal(PlayerbotAI* ai) { return new EnterTwilightPortalAction(ai); }
static Action* exit_twilight_portal(PlayerbotAI* ai) { return new ExitTwilightPortalAction(ai); }
};
#endif

View File

@@ -0,0 +1,246 @@
#include "RaidOsActions.h"
#include "RaidOsTriggers.h"
#include "Playerbots.h"
bool SartharionTankPositionAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return false; }
// Unit* shadron = AI_VALUE2(Unit*, "find target", "shadron");
// Unit* tenebron = AI_VALUE2(Unit*, "find target", "tenebron");
// Unit* vesperon = AI_VALUE2(Unit*, "find target", "vesperon");
Unit* shadron = nullptr;
Unit* tenebron = nullptr;
Unit* vesperon = nullptr;
// Detect incoming drakes before they are on aggro table
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto& target : targets)
{
Unit* unit = botAI->GetUnit(target);
if (!unit) { continue; }
switch (unit->GetEntry())
{
case NPC_SHADRON:
shadron = unit;
continue;
case NPC_TENEBRON:
tenebron = unit;
continue;
case NPC_VESPERON:
vesperon = unit;
continue;
default:
continue;
}
}
Position currentPos = bot->GetPosition();
// Adjustable, this is the acceptable distance to stack point that will be accepted as "safe"
float looseDistance = 12.0f;
if (botAI->IsMainTank(bot))
{
if (bot->GetExactDist2d(SARTHARION_MAINTANK_POSITION.first, SARTHARION_MAINTANK_POSITION.second) > looseDistance)
{
return MoveTo(OS_MAP_ID, SARTHARION_MAINTANK_POSITION.first, SARTHARION_MAINTANK_POSITION.second, currentPos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
// Offtank grab drakes
else if (shadron || tenebron || vesperon)
{
float triggerDistance = 100.0f;
// Prioritise threat before positioning
if (tenebron && bot->GetExactDist2d(tenebron) < triggerDistance &&
tenebron->GetTarget() != bot->GetGUID() && AI_VALUE(Unit*, "current target") != tenebron)
{
return Attack(tenebron);
}
if (shadron && bot->GetExactDist2d(shadron) < triggerDistance &&
shadron->GetTarget() != bot->GetGUID() && AI_VALUE(Unit*, "current target") != shadron)
{
return Attack(shadron);
}
if (vesperon && bot->GetExactDist2d(vesperon) < triggerDistance &&
vesperon->GetTarget() != bot->GetGUID() && AI_VALUE(Unit*, "current target") != vesperon)
{
return Attack(vesperon);
}
bool drakeInCombat = (tenebron && bot->GetExactDist2d(tenebron) < triggerDistance) ||
(shadron && bot->GetExactDist2d(shadron) < triggerDistance) ||
(vesperon && bot->GetExactDist2d(vesperon) < triggerDistance);
// Offtank has threat on drakes, check positioning
if (drakeInCombat && bot->GetExactDist2d(SARTHARION_OFFTANK_POSITION.first, SARTHARION_OFFTANK_POSITION.second) > looseDistance)
{
return MoveTo(OS_MAP_ID, SARTHARION_OFFTANK_POSITION.first, SARTHARION_OFFTANK_POSITION.second, currentPos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
return false;
}
bool AvoidTwilightFissureAction::Execute(Event event)
{
const float radius = 5.0f;
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (unit && unit->GetEntry() == NPC_TWILIGHT_FISSURE)
{
float currentDistance = bot->GetDistance2d(unit);
if (currentDistance < radius)
{
return MoveAway(unit, radius - currentDistance);
}
}
}
return false;
}
bool AvoidFlameTsunamiAction::Execute(Event event)
{
// Adjustable, this is the acceptable distance to stack point that will be accepted as "safe"
float looseDistance = 4.0f;
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (unit && unit->GetEntry() == NPC_FLAME_TSUNAMI)
{
Position currentPos = bot->GetPosition();
// I think these are centrepoints for the wave segments. Either way they uniquely identify the wave
// direction as they have different coords for the left and right waves
// int casting is not a mistake, need to avoid FP errors somehow.
// I always saw these accurate to around 6 decimal places, but if there are issues,
// can switch this to abs comparison of floats which would technically be more robust.
int posY = (int) unit->GetPositionY();
if (posY == 505 || posY == 555) // RIGHT WAVE
{
bool wavePassed = currentPos.GetPositionX() > unit->GetPositionX();
if (wavePassed)
{
return false;
}
if (bot->GetExactDist2d(currentPos.GetPositionX(), TSUNAMI_RIGHT_SAFE_ALL) > looseDistance)
{
return MoveTo(OS_MAP_ID, currentPos.GetPositionX(), TSUNAMI_RIGHT_SAFE_ALL, currentPos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
else // LEFT WAVE
{
bool wavePassed = currentPos.GetPositionX() < unit->GetPositionX();
if (wavePassed)
{
return false;
}
if (botAI->IsMelee(bot))
{
if (bot->GetExactDist2d(currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_MELEE) > looseDistance)
{
return MoveTo(OS_MAP_ID, currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_MELEE, currentPos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
else // Ranged/healers
{
if (bot->GetExactDist2d(currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_RANGED) > looseDistance)
{
return MoveTo(OS_MAP_ID, currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_RANGED, currentPos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
}
}
}
return false;
}
bool SartharionAttackPriorityAction::Execute(Event event)
{
Unit* sartharion = AI_VALUE2(Unit*, "find target", "sartharion");
Unit* shadron = AI_VALUE2(Unit*, "find target", "shadron");
Unit* tenebron = AI_VALUE2(Unit*, "find target", "tenebron");
Unit* vesperon = AI_VALUE2(Unit*, "find target", "vesperon");
Unit* acolyte = AI_VALUE2(Unit*, "find target", "acolyte of shadron");
Unit* target = nullptr;
if (acolyte)
{
target = acolyte;
}
else if (vesperon)
{
target = vesperon;
}
else if (tenebron)
{
target = tenebron;
}
else if (shadron)
{
target = shadron;
}
else if (sartharion)
{
target = sartharion;
}
if (target && AI_VALUE(Unit*, "current target") != target)
{
return Attack(target);
}
return false;
}
bool EnterTwilightPortalAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss || !boss->HasAura(SPELL_GIFT_OF_TWILIGHT_FIRE)) { return false; }
GameObject* portal = bot->FindNearestGameObject(GO_TWILIGHT_PORTAL, 100.0f);
if (!portal) { return false; }
if (!portal->IsAtInteractDistance(bot))
{
return MoveTo(portal, fmaxf(portal->GetInteractionDistance() - 1.0f, 0.0f));
}
// Go through portal
WorldPacket data1(CMSG_GAMEOBJ_USE);
data1 << portal->GetGUID();
bot->GetSession()->HandleGameObjectUseOpcode(data1);
return true;
}
bool ExitTwilightPortalAction::Execute(Event event)
{
GameObject* portal = bot->FindNearestGameObject(GO_NORMAL_PORTAL, 100.0f);
if (!portal) { return false; }
if (!portal->IsAtInteractDistance(bot))
{
return MoveTo(portal, fmaxf(portal->GetInteractionDistance() - 1.0f, 0.0f));
}
// Go through portal
WorldPacket data1(CMSG_GAMEOBJ_USE);
data1 << portal->GetGUID();
bot->GetSession()->HandleGameObjectUseOpcode(data1);
return true;
}

View File

@@ -0,0 +1,64 @@
#ifndef _PLAYERBOT_RAIDOSACTIONS_H
#define _PLAYERBOT_RAIDOSACTIONS_H
#include "MovementActions.h"
#include "AttackAction.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
const float TSUNAMI_LEFT_SAFE_MELEE = 552.0f;
const float TSUNAMI_LEFT_SAFE_RANGED = 504.0f;
const float TSUNAMI_RIGHT_SAFE_ALL = 529.0f;
const std::pair<float, float> SARTHARION_MAINTANK_POSITION = {3258.5f, 532.5f};
const std::pair<float, float> SARTHARION_OFFTANK_POSITION = {3230.0f, 526.0f};
const std::pair<float, float> SARTHARION_RANGED_POSITION = {3248.0f, 507.0f};
class SartharionTankPositionAction : public AttackAction
{
public:
SartharionTankPositionAction(PlayerbotAI* botAI, std::string const name = "sartharion tank position")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class AvoidTwilightFissureAction : public MovementAction
{
public:
AvoidTwilightFissureAction(PlayerbotAI* botAI, std::string const name = "avoid twilight fissure")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class AvoidFlameTsunamiAction : public MovementAction
{
public:
AvoidFlameTsunamiAction(PlayerbotAI* botAI, std::string const name = "avoid flame tsunami")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class SartharionAttackPriorityAction : public AttackAction
{
public:
SartharionAttackPriorityAction(PlayerbotAI* botAI, std::string const name = "sartharion attack priority")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class EnterTwilightPortalAction : public MovementAction
{
public:
EnterTwilightPortalAction(PlayerbotAI* botAI, std::string const name = "enter twilight portal")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class ExitTwilightPortalAction : public MovementAction
{
public:
ExitTwilightPortalAction(PlayerbotAI* botAI, std::string const name = "exit twilight portal")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,49 @@
#include "RaidOsMultipliers.h"
#include "ChooseTargetActions.h"
#include "DKActions.h"
#include "DruidActions.h"
#include "DruidBearActions.h"
#include "FollowActions.h"
#include "GenericActions.h"
#include "GenericSpellActions.h"
#include "MovementActions.h"
#include "PaladinActions.h"
#include "RaidOsActions.h"
#include "RaidOsTriggers.h"
#include "ReachTargetActions.h"
#include "ScriptedCreature.h"
#include "WarriorActions.h"
float SartharionMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return 1.0f; }
Unit* target = action->GetTarget();
if (botAI->IsMainTank(bot) && dynamic_cast<TankFaceAction*>(action))
{
// return 0.0f;
}
if (botAI->IsDps(bot) && dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
if (botAI->IsMainTank(bot) && target && target != boss &&
(dynamic_cast<TankAssistAction*>(action) || dynamic_cast<CastTauntAction*>(action) || dynamic_cast<CastDarkCommandAction*>(action) ||
dynamic_cast<CastHandOfReckoningAction*>(action) || dynamic_cast<CastGrowlAction*>(action)))
{
return 0.0f;
}
if (botAI->IsAssistTank(bot) && target && target == boss &&
(dynamic_cast<CastTauntAction*>(action) || dynamic_cast<CastDarkCommandAction*>(action) ||
dynamic_cast<CastHandOfReckoningAction*>(action) || dynamic_cast<CastGrowlAction*>(action)))
{
return 0.0f;
}
return 1.0f;
}

View File

@@ -0,0 +1,16 @@
#ifndef _PLAYERRBOT_RAIDOSMULTIPLIERS_H
#define _PLAYERRBOT_RAIDOSMULTIPLIERS_H
#include "Multiplier.h"
class SartharionMultiplier : public Multiplier
{
public:
SartharionMultiplier(PlayerbotAI* ai) : Multiplier(ai, "sartharion") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,32 @@
#include "RaidOsStrategy.h"
#include "RaidOsMultipliers.h"
#include "Strategy.h"
void RaidOsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("sartharion tank",
NextAction::array(0, new NextAction("sartharion tank position", ACTION_MOVE), nullptr)));
triggers.push_back(
new TriggerNode("twilight fissure",
NextAction::array(0, new NextAction("avoid twilight fissure", ACTION_RAID + 2), nullptr)));
triggers.push_back(
new TriggerNode("flame tsunami",
NextAction::array(0, new NextAction("avoid flame tsunami", ACTION_RAID + 1), nullptr)));
triggers.push_back(
new TriggerNode("sartharion dps",
NextAction::array(0, new NextAction("sartharion attack priority", ACTION_RAID), nullptr)));
// Flank dragon positioning
triggers.push_back(new TriggerNode("sartharion melee positioning",
NextAction::array(0, new NextAction("rear flank", ACTION_MOVE + 4), nullptr)));
triggers.push_back(new TriggerNode("twilight portal enter",
NextAction::array(0, new NextAction("enter twilight portal", ACTION_RAID + 1), nullptr)));
triggers.push_back(new TriggerNode("twilight portal exit",
NextAction::array(0, new NextAction("exit twilight portal", ACTION_RAID + 1), nullptr)));
}
void RaidOsStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
multipliers.push_back(new SartharionMultiplier(botAI));
}

View File

@@ -0,0 +1,17 @@
#ifndef _PLAYERBOT_RAIDOSSTRATEGY_H
#define _PLAYERBOT_RAIDOSSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h"
class RaidOsStrategy : public Strategy
{
public:
RaidOsStrategy(PlayerbotAI* ai) : Strategy(ai) {}
virtual std::string const getName() override { return "wotlk-os"; }
virtual void InitTriggers(std::vector<TriggerNode*> &triggers) override;
virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
};
#endif

View File

@@ -0,0 +1,32 @@
#ifndef _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidOsTriggers.h"
class RaidOsTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidOsTriggerContext()
{
creators["sartharion tank"] = &RaidOsTriggerContext::sartharion_tank;
creators["flame tsunami"] = &RaidOsTriggerContext::flame_tsunami;
creators["twilight fissure"] = &RaidOsTriggerContext::twilight_fissure;
creators["sartharion dps"] = &RaidOsTriggerContext::sartharion_dps;
creators["sartharion melee positioning"] = &RaidOsTriggerContext::sartharion_melee;
creators["twilight portal enter"] = &RaidOsTriggerContext::twilight_portal_enter;
creators["twilight portal exit"] = &RaidOsTriggerContext::twilight_portal_exit;
}
private:
static Trigger* sartharion_tank(PlayerbotAI* ai) { return new SartharionTankTrigger(ai); }
static Trigger* flame_tsunami(PlayerbotAI* ai) { return new FlameTsunamiTrigger(ai); }
static Trigger* twilight_fissure(PlayerbotAI* ai) { return new TwilightFissureTrigger(ai); }
static Trigger* sartharion_dps(PlayerbotAI* ai) { return new SartharionDpsTrigger(ai); }
static Trigger* sartharion_melee(PlayerbotAI* ai) { return new SartharionMeleePositioningTrigger(ai); }
static Trigger* twilight_portal_enter(PlayerbotAI* ai) { return new TwilightPortalEnterTrigger(ai); }
static Trigger* twilight_portal_exit(PlayerbotAI* ai) { return new TwilightPortalExitTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,128 @@
#include "RaidOsTriggers.h"
#include "SharedDefines.h"
bool SartharionTankTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return false; }
return botAI->IsTank(bot);
}
bool FlameTsunamiTrigger::IsActive()
{
if (botAI->IsTank(bot)) { return false; }
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return false; }
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (unit)
{
if (unit->GetEntry() == NPC_FLAME_TSUNAMI)
{
return true;
}
}
}
return false;
}
bool TwilightFissureTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return false; }
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (unit)
{
if (unit->GetEntry() == NPC_TWILIGHT_FISSURE)
{
return true;
}
}
}
return false;
}
bool SartharionDpsTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return false; }
return botAI->IsDps(bot);
}
bool SartharionMeleePositioningTrigger::IsActive()
{
if (!botAI->IsMelee(bot) || !botAI->IsDps(bot)) { return false; }
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return false; }
Unit* shadron = AI_VALUE2(Unit*, "find target", "shadron");
Unit* tenebron = AI_VALUE2(Unit*, "find target", "tenebron");
Unit* vesperon = AI_VALUE2(Unit*, "find target", "vesperon");
return !(shadron || tenebron || vesperon);
}
bool TwilightPortalEnterTrigger::IsActive()
{
if (botAI->IsMainTank(bot) || botAI->IsHealAssistantOfIndex(bot, 0)) { return false; }
// In 25-man, take two healers in. Otherwise just take one
// if (bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL)
// {
// if (botAI->IsHealAssistantOfIndex(bot, 0) || botAI->IsHealAssistantOfIndex(bot, 1))
// {
// return false;
// }
// }
// else
// {
// if (botAI->IsHealAssistantOfIndex(bot, 0))
// {
// return false;
// }
// }
// Don't enter portal until drakes are dead
if (bot->HasAura(SPELL_POWER_OF_SHADRON) ||
bot->HasAura(SPELL_POWER_OF_TENEBRON) ||
bot->HasAura(SPELL_POWER_OF_VESPERON))
{
return false;
}
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return false; }
// GuidVector objects = AI_VALUE(GuidVector, "nearest game objects no los");
// for (auto& object : objects)
// {
// GameObject* go = botAI->GetGameObject(object);
// if (go && go->GetEntry() == GO_TWILIGHT_PORTAL)
// {
// return true;
// }
// }
return bool(bot->FindNearestGameObject(GO_TWILIGHT_PORTAL, 100.0f));
}
bool TwilightPortalExitTrigger::IsActive()
{
return bot->HasAura(SPELL_TWILIGHT_SHIFT) && !AI_VALUE2(Unit*, "find target", "acolyte of shadron");
}

View File

@@ -0,0 +1,120 @@
#ifndef _PLAYERBOT_RAIDOSTRIGGERS_H
#define _PLAYERBOT_RAIDOSTRIGGERS_H
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "Trigger.h"
enum ObsidianSanctumIDs
{
// Bosses
NPC_SARTHARION = 28860,
NPC_SHADRON = 30451,
NPC_TENEBRON = 30452,
NPC_VESPERON = 30449,
// Mini-boss shared
SPELL_SHADOW_BREATH = 57570,
SPELL_SHADOW_FISSURE = 57579,
SPELL_SUMMON_TWILIGHT_WHELP = 58035,
SPELL_GIFT_OF_TWILIGHT_SHADOW = 57835,
SPELL_TWILIGHT_TORMENT_VESPERON = 57935,
// Sartharion
SPELL_SARTHARION_CLEAVE = 56909,
SPELL_SARTHARION_FLAME_BREATH = 56908,
SPELL_SARTHARION_TAIL_LASH = 56910,
SPELL_CYCLONE_AURA_PERIODIC = 57598,
SPELL_LAVA_STRIKE_DUMMY = 57578,
SPELL_LAVA_STRIKE_DUMMY_TRIGGER = 57697,
SPELL_LAVA_STRIKE_SUMMON = 57572,
SPELL_SARTHARION_PYROBUFFET = 56916,
SPELL_SARTHARION_BERSERK = 61632,
SPELL_SARTHARION_TWILIGHT_REVENGE = 60639,
// Sartharion with drakes
SPELL_WILL_OF_SARTHARION = 61254,
SPELL_POWER_OF_TENEBRON = 61248,
SPELL_POWER_OF_VESPERON = 61251,
SPELL_POWER_OF_SHADRON = 58105,
SPELL_GIFT_OF_TWILIGHT_FIRE = 58766,
// Visuals
SPELL_EGG_MARKER_VISUAL = 58547,
SPELL_FLAME_TSUNAMI_VISUAL = 57494,
// Misc
SPELL_FADE_ARMOR = 60708,
SPELL_FLAME_TSUNAMI_DAMAGE_AURA = 57492,
SPELL_FLAME_TSUNAMI_LEAP = 60241,
SPELL_SARTHARION_PYROBUFFET_TRIGGER = 57557,
NPC_TWILIGHT_EGG = 30882,
NPC_TWILIGHT_WHELP = 30890,
NPC_DISCIPLE_OF_SHADRON = 30688,
NPC_DISCIPLE_OF_VESPERON = 30858,
NPC_ACOLYTE_OF_SHADRON = 31218,
NPC_ACOLYTE_OF_VESPERON = 31219,
// Sartharion fight
NPC_LAVA_BLAZE = 30643,
NPC_FLAME_TSUNAMI = 30616,
NPC_SAFE_AREA_TRIGGER = 30494,
NPC_TWILIGHT_FISSURE = 30641,
GO_TWILIGHT_PORTAL = 193988,
GO_NORMAL_PORTAL = 193989,
SPELL_TWILIGHT_SHIFT = 57874,
};
const uint32 OS_MAP_ID = 615;
class SartharionTankTrigger : public Trigger
{
public:
SartharionTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "sartharion tank") {}
bool IsActive() override;
};
class FlameTsunamiTrigger : public Trigger
{
public:
FlameTsunamiTrigger(PlayerbotAI* botAI) : Trigger(botAI, "flame tsunami") {}
bool IsActive() override;
};
class TwilightFissureTrigger : public Trigger
{
public:
TwilightFissureTrigger(PlayerbotAI* botAI) : Trigger(botAI, "twilight fissure") {}
bool IsActive() override;
};
class SartharionDpsTrigger : public Trigger
{
public:
SartharionDpsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "sartharion dps") {}
bool IsActive() override;
};
class SartharionMeleePositioningTrigger : public Trigger
{
public:
SartharionMeleePositioningTrigger(PlayerbotAI* botAI) : Trigger(botAI, "sartharion melee positioning") {}
bool IsActive() override;
};
class TwilightPortalEnterTrigger : public Trigger
{
public:
TwilightPortalEnterTrigger(PlayerbotAI* botAI) : Trigger(botAI, "twilight portal enter") {}
bool IsActive() override;
};
class TwilightPortalExitTrigger : public Trigger
{
public:
TwilightPortalExitTrigger(PlayerbotAI* botAI) : Trigger(botAI, "twilight portal exit") {}
bool IsActive() override;
};
#endif

View File

@@ -88,6 +88,6 @@ void AssassinationRogueStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
triggers.push_back(new TriggerNode(
"enemy out of melee",
NextAction::array(0, new NextAction("stealth", ACTION_NORMAL + 9), new NextAction("sprint", ACTION_NORMAL + 8),
new NextAction("reach melee", ACTION_NORMAL + 7), NULL)));
NextAction::array(0, new NextAction("stealth", ACTION_HIGH + 3), new NextAction("sprint", ACTION_HIGH + 2),
new NextAction("reach melee", ACTION_HIGH + 1), NULL)));
}

View File

@@ -134,8 +134,8 @@ void DpsRogueStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode(
"enemy out of melee",
NextAction::array(0, new NextAction("stealth", ACTION_NORMAL + 9), new NextAction("sprint", ACTION_NORMAL + 8),
new NextAction("reach melee", ACTION_NORMAL + 7), nullptr)));
NextAction::array(0, new NextAction("stealth", ACTION_HIGH + 3), new NextAction("sprint", ACTION_HIGH + 2),
new NextAction("reach melee", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode("expose armor",
NextAction::array(0, new NextAction("expose armor", ACTION_HIGH + 3), nullptr)));

View File

@@ -0,0 +1,380 @@
#include "NewRpgAction.h"
#include <cmath>
#include <cstdint>
#include "NewRpgStrategy.h"
#include "ObjectDefines.h"
#include "ObjectGuid.h"
#include "PathGenerator.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "Random.h"
#include "RandomPlayerbotMgr.h"
#include "Timer.h"
#include "TravelMgr.h"
#include "World.h"
bool TellRpgStatusAction::Execute(Event event)
{
std::string out = botAI->rpgInfo.ToString();
botAI->TellMasterNoFacing(out);
return true;
}
bool NewRpgStatusUpdateAction::Execute(Event event)
{
NewRpgInfo& info = botAI->rpgInfo;
switch (info.status)
{
case NewRpgStatus::IDLE:
{
uint32 roll = urand(1, 100);
// IDLE -> NEAR_NPC
// if ((!info.lastNearNpc || info.lastNearNpc + setNpcInterval < getMSTime()) && roll <= 30)
if (roll <= 30)
{
info.lastNearNpc = getMSTime();
GuidVector possibleTargets = AI_VALUE(GuidVector, "possible rpg targets");
if (!possibleTargets.empty())
{
info.status = NewRpgStatus::NEAR_NPC;
return true;
}
}
// IDLE -> GO_INNKEEPER
else if (bot->GetLevel() >= 6 && roll <= 40)
{
WorldPosition pos = SelectRandomInnKeeperPos();
if (pos != WorldPosition() && bot->GetExactDist(pos) > 50.0f)
{
info.lastGoInnKeeper = getMSTime();
info.status = NewRpgStatus::GO_INNKEEPER;
info.innKeeperPos = pos;
return true;
}
}
// IDLE -> GO_GRIND
else if (roll <= 90)
{
WorldPosition pos = SelectRandomGrindPos();
if (pos != WorldPosition())
{
info.lastGoGrind = getMSTime();
info.status = NewRpgStatus::GO_GRIND;
info.grindPos = pos;
return true;
}
}
// IDLE -> REST
info.status = NewRpgStatus::REST;
info.lastRest = getMSTime();
bot->SetStandState(UNIT_STAND_STATE_SIT);
return true;
}
case NewRpgStatus::GO_GRIND:
{
WorldPosition& originalPos = info.grindPos;
assert(info.grindPos != WorldPosition());
// GO_GRIND -> NEAR_RANDOM
if (bot->GetExactDist(originalPos) < 10.0f)
{
info.status = NewRpgStatus::NEAR_RANDOM;
info.lastNearRandom = getMSTime();
info.grindPos = WorldPosition();
return true;
}
// // just choose another grindPos
// if (!info.lastGoGrind || info.lastGoGrind + setGrindInterval < getMSTime())
// {
// WorldPosition pos = SelectRandomGrindPos();
// if (pos == WorldPosition())
// break;
// info.status = NewRpgStatus::GO_GRIND;
// info.lastGoGrind = getMSTime();
// info.grindPos = pos;
// return true;
// }
break;
}
case NewRpgStatus::GO_INNKEEPER:
{
WorldPosition& originalPos = info.innKeeperPos;
assert(info.innKeeperPos != WorldPosition());
// GO_INNKEEPER -> NEAR_NPC
if (bot->GetExactDist(originalPos) < 10.0f)
{
info.lastNearNpc = getMSTime();
info.status = NewRpgStatus::NEAR_NPC;
info.innKeeperPos = WorldPosition();
return true;
}
break;
}
case NewRpgStatus::NEAR_RANDOM:
{
// NEAR_RANDOM -> IDLE
if (info.lastNearRandom + statusNearRandomDuration < getMSTime())
{
info.status = NewRpgStatus::IDLE;
return true;
}
break;
}
case NewRpgStatus::NEAR_NPC:
{
if (info.lastNearNpc + statusNearNpcDuration < getMSTime())
{
info.status = NewRpgStatus::IDLE;
return true;
}
break;
}
case NewRpgStatus::REST:
{
// REST -> IDLE
if (info.lastRest + statusRestDuration < getMSTime())
{
info.status = NewRpgStatus::IDLE;
return true;
}
break;
}
default:
break;
}
return false;
}
WorldPosition NewRpgStatusUpdateAction::SelectRandomGrindPos()
{
const std::vector<WorldLocation>& locs = sRandomPlayerbotMgr->locsPerLevelCache[bot->GetLevel()];
std::vector<WorldLocation> lo_prepared_locs, hi_prepared_locs;
for (auto& loc : locs)
{
if (bot->GetMapId() != loc.GetMapId())
continue;
if (bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) !=
bot->GetZoneId())
continue;
if (bot->GetExactDist(loc) < 500.0f)
{
hi_prepared_locs.push_back(loc);
}
if (bot->GetExactDist(loc) < 2500.0f)
{
lo_prepared_locs.push_back(loc);
}
}
WorldPosition dest;
if (urand(1, 100) <= 50 && !hi_prepared_locs.empty())
{
uint32 idx = urand(0, hi_prepared_locs.size() - 1);
dest = hi_prepared_locs[idx];
}
else if (!lo_prepared_locs.empty())
{
uint32 idx = urand(0, lo_prepared_locs.size() - 1);
dest = lo_prepared_locs[idx];
}
LOG_DEBUG("playerbots", "[New Rpg] Bot {} select random grind pos Map:{} X:{} Y:{} Z:{} ({}+{} available in {})",
bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(),
hi_prepared_locs.size(), lo_prepared_locs.size() - hi_prepared_locs.size(), locs.size());
return dest;
}
WorldPosition NewRpgStatusUpdateAction::SelectRandomInnKeeperPos()
{
const std::vector<WorldLocation>& locs = IsAlliance(bot->getRace())
? sRandomPlayerbotMgr->allianceStarterPerLevelCache[bot->GetLevel()]
: sRandomPlayerbotMgr->hordeStarterPerLevelCache[bot->GetLevel()];
std::vector<WorldLocation> prepared_locs;
for (auto& loc : locs)
{
if (bot->GetMapId() != loc.GetMapId())
continue;
float range = bot->GetLevel() <= 5 ? 500.0f : 2500.0f;
if (bot->GetExactDist(loc) < range)
{
prepared_locs.push_back(loc);
}
}
WorldPosition dest;
if (!prepared_locs.empty())
{
uint32 idx = urand(0, prepared_locs.size() - 1);
dest = prepared_locs[idx];
}
LOG_DEBUG("playerbots", "[New Rpg] Bot {} select random inn keeper pos Map:{} X:{} Y:{} Z:{} ({} available in {})",
bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(),
prepared_locs.size(), locs.size());
return dest;
}
bool NewRpgGoFarAwayPosAction::MoveFarTo(WorldPosition dest)
{
float dis = bot->GetExactDist(dest);
if (dis < pathFinderDis)
{
return MoveTo(dest.getMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), false, false,
false, true);
}
// performance optimization
if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL))
{
return false;
}
float minDelta = M_PI;
const float x = bot->GetPositionX();
const float y = bot->GetPositionY();
const float z = bot->GetPositionZ();
float rx, ry, rz;
bool found = false;
int attempt = 10;
while (--attempt)
{
float angle = bot->GetAngle(&dest);
float delta = (rand_norm() - 0.5) * M_PI * 2;
angle += delta;
float dis = rand_norm() * pathFinderDis;
float dx = x + cos(angle) * dis;
float dy = y + sin(angle) * dis;
float dz = z + 5.0f;
bot->UpdateAllowedPositionZ(dx, dy, dz);
PathGenerator path(bot);
path.CalculatePath(dx, dy, dz);
PathType type = path.GetPathType();
bool canReach = type == PATHFIND_INCOMPLETE || type == PATHFIND_NORMAL;
if (canReach && fabs(delta) <= minDelta)
{
found = true;
const G3D::Vector3& endPos = path.GetActualEndPosition();
rx = endPos.x;
ry = endPos.y;
rz = endPos.z;
minDelta = fabs(delta);
}
}
if (found)
{
return MoveTo(bot->GetMapId(), rx, ry, rz, false, false, false, true);
}
// don't fallback to direct move
// float angle = bot->GetAngle(&dest);
// return MoveTo(bot->GetMapId(), x + cos(angle) * pathFinderDis, y + sin(angle) * pathFinderDis, z);
return false;
}
bool NewRpgGoGrindAction::Execute(Event event) { return MoveFarTo(botAI->rpgInfo.grindPos); }
bool NewRpgGoInnKeeperAction::Execute(Event event) { return MoveFarTo(botAI->rpgInfo.innKeeperPos); }
bool NewRpgMoveRandomAction::Execute(Event event)
{
float distance = rand_norm() * moveStep;
Map* map = bot->GetMap();
const float x = bot->GetPositionX();
const float y = bot->GetPositionY();
const float z = bot->GetPositionZ();
int attempts = 5;
while (--attempts)
{
float angle = (float)rand_norm() * 2 * static_cast<float>(M_PI);
float dx = x + distance * cos(angle);
float dy = y + distance * sin(angle);
float dz = z;
if (!map->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
dx, dy, dz))
continue;
if (map->IsInWater(bot->GetPhaseMask(), dx, dy, dz, bot->GetCollisionHeight()))
continue;
bool moved = MoveTo(bot->GetMapId(), dx, dy, dz, false, false, false, true);
if (moved)
return true;
}
return false;
}
bool NewRpgMoveNpcAction::Execute(Event event)
{
NewRpgInfo& info = botAI->rpgInfo;
if (!info.npcPos)
{
GuidVector possibleTargets = AI_VALUE(GuidVector, "possible rpg targets");
if (possibleTargets.empty())
return false;
int idx = urand(0, possibleTargets.size() - 1);
ObjectGuid guid = possibleTargets[idx];
Unit* unit = botAI->GetUnit(guid);
if (unit)
{
info.npcPos = GuidPosition(unit);
info.lastReachNpc = 0;
}
else
return false;
}
if (bot->GetDistance(info.npcPos) <= INTERACTION_DISTANCE)
{
if (!info.lastReachNpc)
{
info.lastReachNpc = getMSTime();
return true;
}
if (info.lastReachNpc && info.lastReachNpc + stayTime > getMSTime())
return false;
info.npcPos = GuidPosition();
info.lastReachNpc = 0;
}
else
{
assert(info.npcPos);
Unit* unit = botAI->GetUnit(info.npcPos);
if (!unit)
return false;
float x = unit->GetPositionX();
float y = unit->GetPositionY();
float z = unit->GetPositionZ();
float mapId = unit->GetMapId();
float angle = 0.f;
if (bot->IsWithinLOS(x, y, z))
{
if (!unit->isMoving())
angle = unit->GetAngle(bot) + (M_PI * irand(-25, 25) / 100.0); // Closest 45 degrees towards the target
else
angle = unit->GetOrientation() +
(M_PI * irand(-25, 25) / 100.0); // 45 degrees infront of target (leading it's movement)
}
else
angle = 2 * M_PI * rand_norm(); // A circle around the target.
float rnd = rand_norm();
x += cos(angle) * INTERACTION_DISTANCE * rnd;
y += sin(angle) * INTERACTION_DISTANCE * rnd;
// bool exact = true;
if (!unit->GetMap()->CheckCollisionAndGetValidCoords(unit, unit->GetPositionX(), unit->GetPositionY(),
unit->GetPositionZ(), x, y, z))
{
x = unit->GetPositionX();
y = unit->GetPositionY();
z = unit->GetPositionZ();
// exact = false;
}
return MoveTo(mapId, x, y, z, false, false, false, true);
}
return true;
}

View File

@@ -0,0 +1,78 @@
#ifndef _PLAYERBOT_NEWRPGACTION_H
#define _PLAYERBOT_NEWRPGACTION_H
#include "Duration.h"
#include "MovementActions.h"
#include "NewRpgStrategy.h"
#include "TravelMgr.h"
#include "PlayerbotAI.h"
class TellRpgStatusAction : public Action
{
public:
TellRpgStatusAction(PlayerbotAI* botAI) : Action(botAI, "rpg status") {}
bool Execute(Event event) override;
};
class NewRpgStatusUpdateAction : public Action
{
public:
NewRpgStatusUpdateAction(PlayerbotAI* botAI) : Action(botAI, "new rpg status update") {}
bool Execute(Event event) override;
protected:
// const int32 setGrindInterval = 5 * 60 * 1000;
// const int32 setNpcInterval = 1 * 60 * 1000;
const int32 statusNearNpcDuration = 2 * 60 * 1000;
const int32 statusNearRandomDuration = 2 * 60 * 1000;
const int32 statusRestDuration = 30 * 1000;
WorldPosition SelectRandomGrindPos();
WorldPosition SelectRandomInnKeeperPos();
};
class NewRpgGoFarAwayPosAction : public MovementAction
{
public:
NewRpgGoFarAwayPosAction(PlayerbotAI* botAI, std::string name) : MovementAction(botAI, name) {}
// bool Execute(Event event) override;
bool MoveFarTo(WorldPosition dest);
protected:
// WorldPosition dest;
float pathFinderDis = 70.0f; // path finder
};
class NewRpgGoGrindAction : public NewRpgGoFarAwayPosAction
{
public:
NewRpgGoGrindAction(PlayerbotAI* botAI) : NewRpgGoFarAwayPosAction(botAI, "new rpg go grind") {}
bool Execute(Event event) override;
};
class NewRpgGoInnKeeperAction : public NewRpgGoFarAwayPosAction
{
public:
NewRpgGoInnKeeperAction(PlayerbotAI* botAI) : NewRpgGoFarAwayPosAction(botAI, "new rpg go innkeeper") {}
bool Execute(Event event) override;
};
class NewRpgMoveRandomAction : public MovementAction
{
public:
NewRpgMoveRandomAction(PlayerbotAI* botAI) : MovementAction(botAI, "new rpg move random") {}
bool Execute(Event event) override;
protected:
const float moveStep = 50.0f;
};
class NewRpgMoveNpcAction : public MovementAction
{
public:
NewRpgMoveNpcAction(PlayerbotAI* botAI) : MovementAction(botAI, "new rpg move npcs") {}
bool Execute(Event event) override;
protected:
const uint32 stayTime = 8 * 1000;
};
#endif

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "NewRpgStrategy.h"
#include "Playerbots.h"
NewRpgStrategy::NewRpgStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
NextAction** NewRpgStrategy::getDefaultActions()
{
return NextAction::array(0, new NextAction("new rpg status update", 5.0f), nullptr);
}
void NewRpgStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("go grind status", NextAction::array(0, new NextAction("new rpg go grind", 1.0f), nullptr)));
triggers.push_back(
new TriggerNode("go innkeeper status", NextAction::array(0, new NextAction("new rpg go innkeeper", 1.0f), nullptr)));
triggers.push_back(
new TriggerNode("near random status", NextAction::array(0, new NextAction("new rpg move random", 1.0f), nullptr)));
triggers.push_back(
new TriggerNode("near npc status", NextAction::array(0, new NextAction("new rpg move npc", 1.0f), nullptr)));
}
void NewRpgStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
// multipliers.push_back(new RpgActionMultiplier(botAI));
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_NEWRPGSTRATEGY_H
#define _PLAYERBOT_NEWRPGSTRATEGY_H
#include <cstdint>
#include "Strategy.h"
#include "TravelMgr.h"
class PlayerbotAI;
enum class NewRpgStatus
{
// Going to far away place
GO_GRIND,
GO_INNKEEPER,
// Exploring nearby
NEAR_RANDOM,
NEAR_NPC,
// Taking a break
REST,
// Initial status
IDLE
};
struct NewRpgInfo
{
NewRpgStatus status{NewRpgStatus::IDLE};
// NewRpgStatus::GO_GRIND
WorldPosition grindPos{};
uint32 lastGoGrind{0};
// NewRpgStatus::GO_INNKEEPER
WorldPosition innKeeperPos{};
uint32 lastGoInnKeeper{0};
// NewRpgStatus::NEAR_NPC
GuidPosition npcPos{};
uint32 lastNearNpc{0};
uint32 lastReachNpc{0};
// NewRpgStatus::NEAR_RANDOM
uint32 lastNearRandom{0};
// NewRpgStatus::REST
uint32 lastRest{0};
std::string ToString()
{
std::stringstream out;
out << "Status: ";
switch (status)
{
case NewRpgStatus::GO_GRIND:
out << "GO_GRIND";
out << "\nGrindPos: " << grindPos.GetMapId() << " " << grindPos.GetPositionX() << " " << grindPos.GetPositionY() << " " << grindPos.GetPositionZ();
out << "\nlastGoGrind: " << lastGoGrind;
break;
case NewRpgStatus::GO_INNKEEPER:
out << "GO_INNKEEPER";
out << "\nInnKeeperPos: " << innKeeperPos.GetMapId() << " " << innKeeperPos.GetPositionX() << " " << innKeeperPos.GetPositionY() << " " << innKeeperPos.GetPositionZ();
out << "\nlastGoInnKeeper: " << lastGoInnKeeper;
break;
case NewRpgStatus::NEAR_NPC:
out << "NEAR_NPC";
out << "\nNpcPos: " << npcPos.GetMapId() << " " << npcPos.GetPositionX() << " " << npcPos.GetPositionY() << " " << npcPos.GetPositionZ();
out << "\nlastNearNpc: " << lastNearNpc;
out << "\nlastReachNpc: " << lastReachNpc;
break;
case NewRpgStatus::NEAR_RANDOM:
out << "NEAR_RANDOM";
out << "\nlastNearRandom: " << lastNearRandom;
break;
case NewRpgStatus::IDLE:
out << "IDLE";
break;
case NewRpgStatus::REST:
out << "REST";
out << "\nlastRest: " << lastRest;
break;
default:
out << "UNKNOWN";
}
return out.str();
}
};
class NewRpgStrategy : public Strategy
{
public:
NewRpgStrategy(PlayerbotAI* botAI);
std::string const getName() override { return "new rpg"; }
NextAction** getDefaultActions() override;
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
void InitMultipliers(std::vector<Multiplier*>& multipliers) override;
};
#endif

View File

@@ -0,0 +1,4 @@
#include "NewRpgTriggers.h"
#include "PlayerbotAI.h"
bool NewRpgStatusTrigger::IsActive() { return status == botAI->rpgInfo.status; }

View File

@@ -0,0 +1,20 @@
#ifndef _PLAYERBOT_NEWRPGTRIGGERS_H
#define _PLAYERBOT_NEWRPGTRIGGERS_H
#include "NewRpgStrategy.h"
#include "Trigger.h"
class NewRpgStatusTrigger : public Trigger
{
public:
NewRpgStatusTrigger(PlayerbotAI* botAI, NewRpgStatus status = NewRpgStatus::IDLE)
: Trigger(botAI, "new rpg status"), status(status)
{
}
bool IsActive() override;
protected:
NewRpgStatus status;
};
#endif

View File

@@ -50,7 +50,7 @@ void HealShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
NextAction::array(0, new NextAction("earthliving weapon", 22.0f), nullptr)));
triggers.push_back(new TriggerNode(
"group heal setting",
NextAction::array(0, new NextAction("riptide on party", 23.0f), new NextAction("chain heal on party", 22.0f), NULL)));
NextAction::array(0, new NextAction("riptide on party", 27.0f), new NextAction("chain heal on party", 26.0f), NULL)));
triggers.push_back(new TriggerNode(
"party member critical health",

View File

@@ -77,7 +77,7 @@ void MeleeShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode(
"medium aoe", NextAction::array(0, new NextAction("strength of earth totem", ACTION_LIGHT_HEAL), nullptr)));
triggers.push_back(new TriggerNode(
"enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_NORMAL + 8), nullptr)));
"enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode(
"no fire totem",

View File

@@ -24,6 +24,7 @@ public:
creators["reputation"] = &ChatTriggerContext::reputation;
creators["log"] = &ChatTriggerContext::log;
creators["los"] = &ChatTriggerContext::los;
creators["rpg status"] = &ChatTriggerContext::rpg_status;
creators["aura"] = &ChatTriggerContext::aura;
creators["drop"] = &ChatTriggerContext::drop;
creators["share"] = &ChatTriggerContext::share;
@@ -211,6 +212,7 @@ private:
static Trigger* reputation(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "reputation"); }
static Trigger* log(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "log"); }
static Trigger* los(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "los"); }
static Trigger* rpg_status(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "rpg status"); }
static Trigger* aura(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "aura"); }
static Trigger* loot_all(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "add all loot"); }
static Trigger* release(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "release"); }

View File

@@ -25,4 +25,8 @@ bool PartyMemberNeedCureTrigger::IsActive()
return target && target->IsInWorld();
}
bool NeedWorldBuffTrigger::IsActive() { return !WorldBuffAction::NeedWorldBuffs(bot).empty(); }
bool NeedWorldBuffTrigger::IsActive()
{
std::any_of(WorldBuffAction::NeedWorldBuffs(bot).begin(), WorldBuffAction::NeedWorldBuffs(bot).end(),
[](const auto& wb) { return true; });
}

View File

@@ -12,6 +12,8 @@
#include "LfgTriggers.h"
#include "LootTriggers.h"
#include "NamedObjectContext.h"
#include "NewRpgStrategy.h"
#include "NewRpgTriggers.h"
#include "PvpTriggers.h"
#include "RaidNaxxTriggers.h"
#include "RpgTriggers.h"
@@ -213,6 +215,10 @@ public:
creators["rpg craft"] = &TriggerContext::rpg_craft;
creators["rpg trade useful"] = &TriggerContext::rpg_trade_useful;
creators["rpg duel"] = &TriggerContext::rpg_duel;
creators["go grind status"] = &TriggerContext::go_grind_status;
creators["go innkeeper status"] = &TriggerContext::go_innkeeper_status;
creators["near random status"] = &TriggerContext::near_random_status;
creators["near npc status"] = &TriggerContext::near_npc_status;
}
private:
@@ -402,6 +408,10 @@ private:
static Trigger* rpg_craft(PlayerbotAI* botAI) { return new RpgCraftTrigger(botAI); }
static Trigger* rpg_trade_useful(PlayerbotAI* botAI) { return new RpgTradeUsefulTrigger(botAI); }
static Trigger* rpg_duel(PlayerbotAI* botAI) { return new RpgDuelTrigger(botAI); }
static Trigger* go_grind_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, NewRpgStatus::GO_GRIND); }
static Trigger* go_innkeeper_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, NewRpgStatus::GO_INNKEEPER); }
static Trigger* near_random_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, NewRpgStatus::NEAR_RANDOM); }
static Trigger* near_npc_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, NewRpgStatus::NEAR_NPC); }
};
#endif

View File

@@ -175,7 +175,7 @@ bool AttackersValue::IsPossibleTarget(Unit* attacker, Player* bot, float range)
// !((attacker->IsPolymorphed() || botAI->HasAura("sap", attacker) || /*attacker->IsCharmed() ||*/
// attacker->isFeared()) && !rti) &&
/*!sServerFacade->IsInRoots(attacker) &&*/
!attacker->IsFriendlyTo(bot) && !attacker->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION) &&
!attacker->IsFriendlyTo(bot) && !attacker->HasSpiritOfRedemptionAura() &&
// !(attacker->GetGUID().IsPet() && enemy) &&
!(attacker->GetCreatureType() == CREATURE_TYPE_CRITTER && !attacker->IsInCombat()) &&
!attacker->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC) && !attacker->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE) &&

View File

@@ -298,8 +298,9 @@ Unit* DpsTargetValue::Calculate()
return rti;
// FindLeastHpTargetStrategy strategy(botAI);
Group* group = bot->GetGroup();
float dps = AI_VALUE(float, "estimated group dps");
if (botAI->IsCaster(bot))
if (group && botAI->IsCaster(bot))
{
CasterFindTargetSmartStrategy strategy(botAI, dps);
return TargetValue::FindTarget(&strategy);

View File

@@ -18,7 +18,7 @@ bool NearestEnemyPlayersValue::AcceptUnit(Unit* unit)
!enemy->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NON_ATTACKABLE_2) &&
((inCannon || !enemy->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE))) &&
/*!enemy->HasStealthAura() && !enemy->HasInvisibilityAura()*/ enemy->CanSeeOrDetect(bot) &&
!(enemy->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION)))
!(enemy->HasSpiritOfRedemptionAura()))
return true;
return false;

View File

@@ -52,7 +52,7 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
float distance = 0;
Unit* result = nullptr;
std::unordered_map<uint32, bool> needForQuestMap;
// std::unordered_map<uint32, bool> needForQuestMap;
for (ObjectGuid const guid : targets)
{
@@ -99,18 +99,18 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
if (!bot->InBattleground() && (int)unit->GetLevel() - (int)bot->GetLevel() > 4 && !unit->GetGUID().IsPlayer())
continue;
if (needForQuestMap.find(unit->GetEntry()) == needForQuestMap.end())
needForQuestMap[unit->GetEntry()] = needForQuest(unit);
// if (needForQuestMap.find(unit->GetEntry()) == needForQuestMap.end())
// needForQuestMap[unit->GetEntry()] = needForQuest(unit);
if (!needForQuestMap[unit->GetEntry()])
{
Creature* creature = dynamic_cast<Creature*>(unit);
if ((urand(0, 100) < 60 || (context->GetValue<TravelTarget*>("travel target")->Get()->isWorking() &&
context->GetValue<TravelTarget*>("travel target")->Get()->getDestination()->getName() != "GrindTravelDestination")))
{
continue;
}
}
// if (!needForQuestMap[unit->GetEntry()])
// {
// Creature* creature = dynamic_cast<Creature*>(unit);
// if ((urand(0, 100) < 60 || (context->GetValue<TravelTarget*>("travel target")->Get()->isWorking() &&
// context->GetValue<TravelTarget*>("travel target")->Get()->getDestination()->getName() != "GrindTravelDestination")))
// {
// continue;
// }
// }
if (Creature* creature = unit->ToCreature())
if (CreatureTemplate const* CreatureTemplate = creature->GetCreatureTemplate())
@@ -142,8 +142,7 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
else
{
float newdistance = bot->GetDistance(unit);
if (!result || (newdistance < distance &&
urand(0, abs(distance - newdistance)) > sPlayerbotAIConfig->sightDistance * 0.1))
if (!result || (newdistance < distance))
{
distance = newdistance;
result = unit;

View File

@@ -21,7 +21,7 @@ bool InvalidTargetValue::Calculate()
return target->GetMapId() != bot->GetMapId() || target->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE) ||
target->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE) || target->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE_2) ||
!target->IsVisible() || !target->IsAlive() || target->IsPolymorphed() || target->IsCharmed() ||
target->isFeared() || target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsFriendlyTo(bot) ||
target->HasFearAura() || target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsFriendlyTo(bot) ||
!AttackersValue::IsValidTarget(target, bot);
}

View File

@@ -256,20 +256,42 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto)
// Check weapon case separately to keep things a bit cleaner
bool have2HWeapon = false;
bool isValidTGWeapon = false;
if (dstSlot == EQUIPMENT_SLOT_MAINHAND)
{
Item* currentWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
have2HWeapon = currentWeapon && currentWeapon->GetTemplate()->InventoryType == INVTYPE_2HWEAPON;
isValidTGWeapon = itemProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 ||
itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 ||
itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2;
if (bot->CanDualWield() && ((itemProto->InventoryType != INVTYPE_2HWEAPON && !have2HWeapon) || (bot->CanTitanGrip() && isValidTGWeapon)))
// Determine if the new weapon is a valid Titan Grip weapon
isValidTGWeapon = (itemProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 ||
itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 ||
itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2);
// If the bot can Titan Grip, ignore any 2H weapon that isn't a 2H sword, mace, or axe.
if (bot->CanTitanGrip())
{
// If this weapon is 2H but not one of the valid TG weapon types, do not equip it at all.
if (itemProto->InventoryType == INVTYPE_2HWEAPON && !isValidTGWeapon)
{
return ITEM_USAGE_NONE;
}
}
// Now handle the logic for equipping and possible offhand slots
// If the bot can Dual Wield and:
// - The weapon is not 2H and we currently don't have a 2H weapon equipped
// OR
// - The bot can Titan Grip and it is a valid TG weapon
// Then we can consider the offhand slot as well.
if (bot->CanDualWield() &&
((itemProto->InventoryType != INVTYPE_2HWEAPON && !have2HWeapon) ||
(bot->CanTitanGrip() && isValidTGWeapon)))
{
possibleSlots = 2;
}
}
for (uint8 i = 0; i < possibleSlots; i++)
{
bool shouldEquipInSlot = shouldEquip;

View File

@@ -14,7 +14,7 @@ class PlayerbotAI;
class PossibleRpgTargetsValue : public NearestUnitsValue
{
public:
PossibleRpgTargetsValue(PlayerbotAI* botAI, float range = sPlayerbotAIConfig->rpgDistance);
PossibleRpgTargetsValue(PlayerbotAI* botAI, float range = 70.0f);
static std::vector<uint32> allowedNpcFlags;

View File

@@ -48,9 +48,6 @@ Unit* RtiTargetValue::Calculate()
if (!guid)
return nullptr;
if (!bot->IsInCombat())
return nullptr;
//////////////////////////////////////////////////////begin: delete below check
// Some units that need to be killed in battle are not on the list of attackers,
// such as the Kor'kron Battle-Mage in Icecrown Citadel.
@@ -62,7 +59,7 @@ Unit* RtiTargetValue::Calculate()
//////////////////////////////////////////////////////end: delete below check
Unit* unit = botAI->GetUnit(guid);
if (!unit || unit->isDead() || !bot->IsWithinLOSInMap(unit) || !AttackersValue::IsValidTarget(unit, bot) ||
if (!unit || unit->isDead() || !bot->IsWithinLOSInMap(unit) || !AttackersValue::IsValidTarget(unit, bot) ||
sServerFacade->IsDistanceGreaterThan(sServerFacade->GetDistance2d(bot, unit),
sPlayerbotAIConfig->sightDistance))
return nullptr;

View File

@@ -48,7 +48,7 @@ private:
{
return new ActionNode("summon succubus",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("summon imp"), nullptr),
/*A*/ NextAction::array(0, new NextAction("summon voidwalker"), nullptr),
/*C*/ nullptr);
}
static ActionNode* summon_felhunter([[maybe_unused]] PlayerbotAI* botAI)

View File

@@ -23,3 +23,5 @@ bool CastFearOnCcAction::isPossible() { return botAI->CanCastSpell("fear", GetTa
bool CastFearOnCcAction::isUseful() { return true; }
bool CastLifeTapAction::isUseful() { return AI_VALUE2(uint8, "health", "self target") > sPlayerbotAIConfig->lowHealth; }
Unit* UseSoulstoneAction::GetTarget() { return botAI->GetMaster(); }

View File

@@ -7,6 +7,7 @@
#define _PLAYERBOT_WARLOCKACTIONS_H
#include "GenericSpellActions.h"
#include "UseItemAction.h"
class PlayerbotAI;
class Unit;
@@ -302,4 +303,12 @@ class CastIncinerateAction : public CastSpellAction
public:
CastIncinerateAction(PlayerbotAI* ai) : CastSpellAction(ai, "incinerate") {}
};
class UseSoulstoneAction : public UseSpellItemAction
{
public:
UseSoulstoneAction(PlayerbotAI* ai) : UseSpellItemAction(ai, "soulstone") {}
Unit* GetTarget() override;
};
#endif

View File

@@ -184,6 +184,7 @@ public:
creators["metamorphosis"] = &WarlockAiObjectContextInternal::metamorphosis;
creators["soul fire"] = &WarlockAiObjectContextInternal::soul_fire;
creators["incinerate"] = &WarlockAiObjectContextInternal::incinerate;
creators["soulstone"] = &WarlockAiObjectContextInternal::soulstone;
}
private:
@@ -239,6 +240,7 @@ private:
static Action* metamorphosis(PlayerbotAI* ai) { return new CastMetamorphosisAction(ai); }
static Action* soul_fire(PlayerbotAI* ai) { return new CastSoulFireAction(ai); }
static Action* incinerate(PlayerbotAI* ai) { return new CastIncinerateAction(ai); }
static Action* soulstone(PlayerbotAI* ai) { return new UseSoulstoneAction(ai); }
};
WarlockAiObjectContext::WarlockAiObjectContext(PlayerbotAI* botAI) : AiObjectContext(botAI)

View File

@@ -16,7 +16,7 @@ void GenericWarriorStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
CombatStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode(
"enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_NORMAL + 8), nullptr)));
"enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_HIGH + 1), nullptr)));
/*triggers.push_back(new TriggerNode("bloodrage", NextAction::array(0, new NextAction("bloodrage", ACTION_HIGH + 1),
nullptr))); triggers.push_back(new TriggerNode("shield bash", NextAction::array(0, new NextAction("shield bash",
ACTION_INTERRUPT + 4), nullptr))); triggers.push_back(new TriggerNode("shield bash on enemy healer",