mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Merge branch 'liyunfan1223:master' into armor_token_usage
This commit is contained in:
@@ -464,7 +464,7 @@ INSERT INTO `ai_playerbot_texts` (`name`, `text`, `say_type`, `reply_type`, `tex
|
|||||||
-- %my_level
|
-- %my_level
|
||||||
('suggest_something', 'Wanna party in %zone_name.', 0, 0, '', 'Je veux faire la fête dans %zone_name.', '', '', '', '¡Vamos a perrear a %zone_name!', '', 'Ищу группу в %zone_name.'),
|
('suggest_something', 'Wanna party in %zone_name.', 0, 0, '', 'Je veux faire la fête dans %zone_name.', '', '', '', '¡Vamos a perrear a %zone_name!', '', 'Ищу группу в %zone_name.'),
|
||||||
('suggest_something', 'Anyone is looking for %my_role?', 0, 0, '', 'Quelqu\'un cherche un %my_role ?', '', '', '', '¿Alguien está buscando %my_role?', '', 'Кто-нибудь ищет %my_role?'),
|
('suggest_something', 'Anyone is looking for %my_role?', 0, 0, '', 'Quelqu\'un cherche un %my_role ?', '', '', '', '¿Alguien está buscando %my_role?', '', 'Кто-нибудь ищет %my_role?'),
|
||||||
('suggest_something', '%my_role is looking for quild.', 0, 0, '', '%my_role recherche une guilde.', '', '', '', '%my_role está buscando hermandad.', '', '%my_role ищу гильдию.'),
|
('suggest_something', '%my_role is looking for guild.', 0, 0, '', '%my_role recherche une guilde.', '', '', '', '%my_role está buscando hermandad.', '', '%my_role ищу гильдию.'),
|
||||||
('suggest_something', 'Looking for gold.', 0, 0, '', 'A la recherche de l\'or.', '', '', '', 'Buscando oro.', '', 'Дайте голды'),
|
('suggest_something', 'Looking for gold.', 0, 0, '', 'A la recherche de l\'or.', '', '', '', 'Buscando oro.', '', 'Дайте голды'),
|
||||||
('suggest_something', '%my_role wants to join a good guild.', 0, 0, '', '%my_role veut rejoindre une bonne guilde.', '', '', '', '%my_role quiere unirse a una buen hermandad.', '', '%my_role хочу в хорошую гильдию.'),
|
('suggest_something', '%my_role wants to join a good guild.', 0, 0, '', '%my_role veut rejoindre une bonne guilde.', '', '', '', '%my_role quiere unirse a una buen hermandad.', '', '%my_role хочу в хорошую гильдию.'),
|
||||||
('suggest_something', 'Need a friend.', 0, 0, '', 'Besoin d\'un ami.', '', '', '', 'Necesito un amigo...', '', 'Ищу друга.'),
|
('suggest_something', 'Need a friend.', 0, 0, '', 'Besoin d\'un ami.', '', '', '', 'Necesito un amigo...', '', 'Ищу друга.'),
|
||||||
@@ -1457,19 +1457,3 @@ INSERT INTO `ai_playerbot_texts` (`name`, `text`, `say_type`, `reply_type`, `tex
|
|||||||
|
|
||||||
|
|
||||||
('dummy_end', 'dummy', 0, 0, '', '', '', '', '', '', '', '');
|
('dummy_end', 'dummy', 0, 0, '', '', '', '', '', '', '', '');
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `ai_playerbot_texts_chance`;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `ai_playerbot_texts_chance` (
|
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
|
||||||
`name` varchar(255) NOT NULL,
|
|
||||||
`probability` bigint(20) NOT NULL,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=UTF8;
|
|
||||||
|
|
||||||
/*!40000 ALTER TABLE `ai_playerbot_texts_chance` DISABLE KEYS */;
|
|
||||||
INSERT INTO `ai_playerbot_texts_chance` (`id`, `name`, `probability`) VALUES
|
|
||||||
(1, 'taunt', 30),
|
|
||||||
(2, 'aoe', 75),
|
|
||||||
(3, 'loot', 20);
|
|
||||||
/*!40000 ALTER TABLE `ai_playerbot_texts_chance` ENABLE KEYS */;
|
|
||||||
14
sql/playerbots/base/ai_playerbot_texts_chance.sql
Normal file
14
sql/playerbots/base/ai_playerbot_texts_chance.sql
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
DROP TABLE IF EXISTS `ai_playerbot_texts_chance`;
|
||||||
|
CREATE TABLE IF NOT EXISTS `ai_playerbot_texts_chance` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(255) NOT NULL,
|
||||||
|
`probability` bigint(20) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=UTF8;
|
||||||
|
|
||||||
|
/*!40000 ALTER TABLE `ai_playerbot_texts_chance` DISABLE KEYS */;
|
||||||
|
INSERT INTO `ai_playerbot_texts_chance` (`id`, `name`, `probability`) VALUES
|
||||||
|
(1, 'taunt', 30),
|
||||||
|
(2, 'aoe', 75),
|
||||||
|
(3, 'loot', 20);
|
||||||
|
/*!40000 ALTER TABLE `ai_playerbot_texts_chance` ENABLE KEYS */;
|
||||||
10
sql/playerbots/base/playerbots_preferred_mounts.sql
Normal file
10
sql/playerbots/base/playerbots_preferred_mounts.sql
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
DROP TABLE IF EXISTS `playerbots_preferred_mounts`;
|
||||||
|
CREATE TABLE `playerbots_preferred_mounts` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`guid` INT(11) NOT NULL,
|
||||||
|
`type` TINYINT(3) NOT NULL COMMENT '0: Ground, 1: Flying',
|
||||||
|
`spellid` INT(11) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `guid` (`guid`),
|
||||||
|
KEY `type` (`type`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
@@ -304,9 +304,13 @@ ItemIds ChatHelper::parseItems(std::string const text)
|
|||||||
|
|
||||||
std::string const ChatHelper::FormatQuest(Quest const* quest)
|
std::string const ChatHelper::FormatQuest(Quest const* quest)
|
||||||
{
|
{
|
||||||
|
if (!quest)
|
||||||
|
{
|
||||||
|
return "Invalid quest";
|
||||||
|
}
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "|cFFFFFF00|Hquest:" << quest->GetQuestId() << ':' << quest->GetQuestLevel() << "|h[" << quest->GetTitle()
|
out << "|cFFFFFF00|Hquest:" << quest->GetQuestId() << ':' << quest->GetQuestLevel() << "|h[" << quest->GetTitle() << "]|h|r";
|
||||||
<< "]|h|r";
|
|
||||||
return out.str();
|
return out.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
|||||||
Player* bot = botSession->GetPlayer();
|
Player* bot = botSession->GetPlayer();
|
||||||
if (!bot)
|
if (!bot)
|
||||||
{
|
{
|
||||||
|
// Debug log
|
||||||
|
LOG_DEBUG("mod-playerbots", "Bot player could not be loaded for account ID: {}", botAccountId);
|
||||||
botSession->LogoutPlayer(true);
|
botSession->LogoutPlayer(true);
|
||||||
delete botSession;
|
delete botSession;
|
||||||
botLoading.erase(holder.GetGuid());
|
botLoading.erase(holder.GetGuid());
|
||||||
@@ -108,6 +110,14 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
|||||||
|
|
||||||
uint32 masterAccount = holder.GetMasterAccountId();
|
uint32 masterAccount = holder.GetMasterAccountId();
|
||||||
WorldSession* masterSession = masterAccount ? sWorld->FindSession(masterAccount) : nullptr;
|
WorldSession* masterSession = masterAccount ? sWorld->FindSession(masterAccount) : nullptr;
|
||||||
|
|
||||||
|
// Check if masterSession->GetPlayer() is valid
|
||||||
|
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
|
||||||
|
if (masterSession && !masterPlayer)
|
||||||
|
{
|
||||||
|
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}", masterAccount);
|
||||||
|
}
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
bool allowed = false;
|
bool allowed = false;
|
||||||
if (botAccountId == masterAccount)
|
if (botAccountId == masterAccount)
|
||||||
@@ -115,7 +125,7 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
|||||||
allowed = true;
|
allowed = true;
|
||||||
}
|
}
|
||||||
else if (masterSession && sPlayerbotAIConfig->allowGuildBots && bot->GetGuildId() != 0 &&
|
else if (masterSession && sPlayerbotAIConfig->allowGuildBots && bot->GetGuildId() != 0 &&
|
||||||
bot->GetGuildId() == masterSession->GetPlayer()->GetGuildId())
|
bot->GetGuildId() == masterPlayer->GetGuildId())
|
||||||
{
|
{
|
||||||
allowed = true;
|
allowed = true;
|
||||||
}
|
}
|
||||||
@@ -129,10 +139,14 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
|||||||
out << "Failure: You are not allowed to control bot " << bot->GetName().c_str();
|
out << "Failure: You are not allowed to control bot " << bot->GetName().c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allowed && masterSession)
|
if (allowed && masterSession && masterPlayer)
|
||||||
{
|
{
|
||||||
Player* player = masterSession->GetPlayer();
|
PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(masterPlayer);
|
||||||
PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(player);
|
if (!mgr)
|
||||||
|
{
|
||||||
|
LOG_DEBUG("mod-playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue());
|
||||||
|
}
|
||||||
|
|
||||||
uint32 count = mgr->GetPlayerbotsCount();
|
uint32 count = mgr->GetPlayerbotsCount();
|
||||||
uint32 cls_count = mgr->GetPlayerbotsCountByClass(bot->getClass());
|
uint32 cls_count = mgr->GetPlayerbotsCountByClass(bot->getClass());
|
||||||
if (count >= sPlayerbotAIConfig->maxAddedBots)
|
if (count >= sPlayerbotAIConfig->maxAddedBots)
|
||||||
@@ -428,14 +442,17 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
|||||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||||
if (!botAI)
|
if (!botAI)
|
||||||
{
|
{
|
||||||
|
// Log a warning here to indicate that the botAI is null
|
||||||
|
LOG_DEBUG("mod-playerbots", "PlayerbotAI is null for bot with GUID: {}", bot->GetGUID().GetRawValue());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Player* master = botAI->GetMaster();
|
Player* master = botAI->GetMaster();
|
||||||
if (master)
|
if (!master)
|
||||||
{
|
{
|
||||||
ObjectGuid masterGuid = master->GetGUID();
|
// Log a warning to indicate that the master is null
|
||||||
if (master->GetGroup() && !master->GetGroup()->IsLeader(masterGuid))
|
LOG_DEBUG("mod-playerbots", "Master is null for bot with GUID: {}", bot->GetGUID().GetRawValue());
|
||||||
master->GetGroup()->ChangeLeader(masterGuid);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Group* group = bot->GetGroup();
|
Group* group = bot->GetGroup();
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
|
|||||||
actionContexts.Add(new WotlkDungeonVHActionContext());
|
actionContexts.Add(new WotlkDungeonVHActionContext());
|
||||||
actionContexts.Add(new WotlkDungeonGDActionContext());
|
actionContexts.Add(new WotlkDungeonGDActionContext());
|
||||||
actionContexts.Add(new WotlkDungeonHoSActionContext());
|
actionContexts.Add(new WotlkDungeonHoSActionContext());
|
||||||
|
actionContexts.Add(new WotlkDungeonHoLActionContext());
|
||||||
|
|
||||||
triggerContexts.Add(new TriggerContext());
|
triggerContexts.Add(new TriggerContext());
|
||||||
triggerContexts.Add(new ChatTriggerContext());
|
triggerContexts.Add(new ChatTriggerContext());
|
||||||
@@ -76,6 +77,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
|
|||||||
triggerContexts.Add(new WotlkDungeonVHTriggerContext());
|
triggerContexts.Add(new WotlkDungeonVHTriggerContext());
|
||||||
triggerContexts.Add(new WotlkDungeonGDTriggerContext());
|
triggerContexts.Add(new WotlkDungeonGDTriggerContext());
|
||||||
triggerContexts.Add(new WotlkDungeonHoSTriggerContext());
|
triggerContexts.Add(new WotlkDungeonHoSTriggerContext());
|
||||||
|
triggerContexts.Add(new WotlkDungeonHoLTriggerContext());
|
||||||
|
|
||||||
valueContexts.Add(new ValueContext());
|
valueContexts.Add(new ValueContext());
|
||||||
|
|
||||||
|
|||||||
@@ -239,9 +239,16 @@ bool CheckMountStateAction::Mount()
|
|||||||
// continue;
|
// continue;
|
||||||
|
|
||||||
uint32 index = (spellInfo->Effects[1].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED ||
|
uint32 index = (spellInfo->Effects[1].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED ||
|
||||||
spellInfo->Effects[2].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED)
|
spellInfo->Effects[2].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED ||
|
||||||
? 1
|
// Winged Steed of the Ebon Blade
|
||||||
: 0;
|
// This mount is meant to autoscale from a 150% flyer
|
||||||
|
// up to a 280% as you train your flying skill up.
|
||||||
|
// This incorrectly gets categorised as a ground mount, force this to flyer only.
|
||||||
|
// TODO: Add other scaling mounts here if they have the same issue, or adjust above
|
||||||
|
// checks so that they are all correctly detected.
|
||||||
|
spellInfo->Id == 54729)
|
||||||
|
? 1 // Flying Mount
|
||||||
|
: 0; // Ground Mount
|
||||||
|
|
||||||
if (index == 0 &&
|
if (index == 0 &&
|
||||||
std::max(spellInfo->Effects[EFFECT_1].BasePoints, spellInfo->Effects[EFFECT_2].BasePoints) > 59)
|
std::max(spellInfo->Effects[EFFECT_1].BasePoints, spellInfo->Effects[EFFECT_2].BasePoints) > 59)
|
||||||
@@ -263,6 +270,42 @@ bool CheckMountStateAction::Mount()
|
|||||||
: 0;
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for preferred mounts table in db
|
||||||
|
QueryResult checkTable = PlayerbotsDatabase.Query(
|
||||||
|
"SELECT EXISTS(SELECT * FROM information_schema.tables WHERE table_schema = 'acore_playerbots' AND table_name = 'playerbots_preferred_mounts')");
|
||||||
|
|
||||||
|
if (checkTable)
|
||||||
|
{
|
||||||
|
uint32 tableExists = checkTable->Fetch()[0].Get<uint32>();
|
||||||
|
if (tableExists == 1)
|
||||||
|
{
|
||||||
|
// Check for preferred mount entry
|
||||||
|
QueryResult result = PlayerbotsDatabase.Query(
|
||||||
|
"SELECT spellid FROM playerbots_preferred_mounts WHERE guid = {} AND type = {}",
|
||||||
|
bot->GetGUID().GetCounter(), masterMountType);
|
||||||
|
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
std::vector<uint32> mounts;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
Field* fields = result->Fetch();
|
||||||
|
uint32 spellId = fields[0].Get<uint32>();
|
||||||
|
mounts.push_back(spellId);
|
||||||
|
} while (result->NextRow());
|
||||||
|
|
||||||
|
uint32 index = urand(0, mounts.size() - 1);
|
||||||
|
// Validate spell ID
|
||||||
|
if (index < mounts.size() && sSpellMgr->GetSpellInfo(mounts[index]))
|
||||||
|
{
|
||||||
|
// TODO: May want to do checks for 'bot riding skill > skill required to ride the mount'
|
||||||
|
return botAI->CastSpell(mounts[index], bot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No preferred mount found (or invalid), continue with random mount selection
|
||||||
std::map<int32, std::vector<uint32>>& spells = allSpells[masterMountType];
|
std::map<int32, std::vector<uint32>>& spells = allSpells[masterMountType];
|
||||||
if (hasSwiftMount)
|
if (hasSwiftMount)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ bool PartyCommandAction::Execute(Event event)
|
|||||||
Player* master = GetMaster();
|
Player* master = GetMaster();
|
||||||
if (master && member == master->GetName())
|
if (master && member == master->GetName())
|
||||||
return Leave(bot);
|
return Leave(bot);
|
||||||
|
|
||||||
|
botAI->Reset();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -62,6 +64,8 @@ bool UninviteAction::Execute(Event event)
|
|||||||
if (bot->GetGUID() == guid)
|
if (bot->GetGUID() == guid)
|
||||||
return Leave(bot);
|
return Leave(bot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
botAI->Reset();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -160,6 +164,8 @@ bool LeaveFarAwayAction::isUseful()
|
|||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
botAI->Reset();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,11 @@
|
|||||||
#include "wotlk/violethold/VioletHoldStrategy.h"
|
#include "wotlk/violethold/VioletHoldStrategy.h"
|
||||||
#include "wotlk/gundrak/GundrakStrategy.h"
|
#include "wotlk/gundrak/GundrakStrategy.h"
|
||||||
#include "wotlk/hallsofstone/HallsOfStoneStrategy.h"
|
#include "wotlk/hallsofstone/HallsOfStoneStrategy.h"
|
||||||
|
#include "wotlk/hallsoflightning/HallsOfLightningStrategy.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Full list/TODO:
|
Full list/TODO:
|
||||||
|
|
||||||
Halls of Lightning - HoL
|
|
||||||
General Bjarngrim, Volkhan, Ionar, Loken
|
|
||||||
The Oculus - Occ
|
The Oculus - Occ
|
||||||
Drakos the Interrogator, Varos Cloudstrider, Mage-Lord Urom, Ley-Guardian Eregos
|
Drakos the Interrogator, Varos Cloudstrider, Mage-Lord Urom, Ley-Guardian Eregos
|
||||||
Utgarde Pinnacle - UP
|
Utgarde Pinnacle - UP
|
||||||
@@ -76,8 +75,8 @@ class DungeonStrategyContext : public NamedObjectContext<Strategy>
|
|||||||
static Strategy* wotlk_vh(PlayerbotAI* botAI) { return new WotlkDungeonVHStrategy(botAI); }
|
static Strategy* wotlk_vh(PlayerbotAI* botAI) { return new WotlkDungeonVHStrategy(botAI); }
|
||||||
static Strategy* wotlk_gd(PlayerbotAI* botAI) { return new WotlkDungeonGDStrategy(botAI); }
|
static Strategy* wotlk_gd(PlayerbotAI* botAI) { return new WotlkDungeonGDStrategy(botAI); }
|
||||||
static Strategy* wotlk_hos(PlayerbotAI* botAI) { return new WotlkDungeonHoSStrategy(botAI); }
|
static Strategy* wotlk_hos(PlayerbotAI* botAI) { return new WotlkDungeonHoSStrategy(botAI); }
|
||||||
|
static Strategy* wotlk_hol(PlayerbotAI* botAI) { return new WotlkDungeonHoLStrategy(botAI); }
|
||||||
|
|
||||||
static Strategy* wotlk_hol(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
|
|
||||||
static Strategy* wotlk_occ(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
|
static Strategy* wotlk_occ(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
|
||||||
static Strategy* wotlk_up(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
|
static Strategy* wotlk_up(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
|
||||||
static Strategy* wotlk_cos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
|
static Strategy* wotlk_cos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include "violethold/VioletHoldActionContext.h"
|
#include "violethold/VioletHoldActionContext.h"
|
||||||
#include "gundrak/GundrakActionContext.h"
|
#include "gundrak/GundrakActionContext.h"
|
||||||
#include "hallsofstone/HallsOfStoneActionContext.h"
|
#include "hallsofstone/HallsOfStoneActionContext.h"
|
||||||
// #include "hallsoflightning/HallsOfLightningActionContext.h"
|
#include "hallsoflightning/HallsOfLightningActionContext.h"
|
||||||
// #include "oculus/OculusActionContext.h"
|
// #include "oculus/OculusActionContext.h"
|
||||||
// #include "utgardepinnacle/UtgardePinnacleActionContext.h"
|
// #include "utgardepinnacle/UtgardePinnacleActionContext.h"
|
||||||
// #include "cullingofstratholme/CullingOfStratholmeActionContext.h"
|
// #include "cullingofstratholme/CullingOfStratholmeActionContext.h"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include "violethold/VioletHoldTriggerContext.h"
|
#include "violethold/VioletHoldTriggerContext.h"
|
||||||
#include "gundrak/GundrakTriggerContext.h"
|
#include "gundrak/GundrakTriggerContext.h"
|
||||||
#include "hallsofstone/HallsOfStoneTriggerContext.h"
|
#include "hallsofstone/HallsOfStoneTriggerContext.h"
|
||||||
// #include "hallsoflightning/HallsOfLightningTriggerContext.h"
|
#include "hallsoflightning/HallsOfLightningTriggerContext.h"
|
||||||
// #include "oculus/OculusTriggerContext.h"
|
// #include "oculus/OculusTriggerContext.h"
|
||||||
// #include "utgardepinnacle/UtgardePinnacleTriggerContext.h"
|
// #include "utgardepinnacle/UtgardePinnacleTriggerContext.h"
|
||||||
// #include "cullingofstratholme/CullingOfStratholmeTriggerContext.h"
|
// #include "cullingofstratholme/CullingOfStratholmeTriggerContext.h"
|
||||||
|
|||||||
@@ -38,19 +38,15 @@ float KrikthirMultiplier::GetValue(Action* action)
|
|||||||
if (boss && watcher)
|
if (boss && watcher)
|
||||||
{
|
{
|
||||||
// Do not target swap
|
// Do not target swap
|
||||||
// TODO: Need to suppress AoE actions but unsure how to identify them
|
if (dynamic_cast<DpsAssistAction*>(action))
|
||||||
// TODO: TEST AOE Avoid
|
{
|
||||||
if (dynamic_cast<DpsAssistAction*>(action)
|
return 0.0f;
|
||||||
|| dynamic_cast<DpsAoeAction*>(action))
|
}
|
||||||
|
|
||||||
|
if (action->getThreatType() == Action::ActionThreatType::Aoe)
|
||||||
{
|
{
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
// Doesn't seem to work
|
|
||||||
// if (action->getThreatType() == Action::ActionThreatType::Aoe)
|
|
||||||
// {
|
|
||||||
// bot->Yell("Suppressed AoE", LANG_UNIVERSAL);
|
|
||||||
// return 0.0f;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
return 1.0f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef _PLAYERBOT_WOTLKDUNGEONHOLACTIONCONTEXT_H
|
||||||
|
#define _PLAYERBOT_WOTLKDUNGEONHOLACTIONCONTEXT_H
|
||||||
|
|
||||||
|
#include "Action.h"
|
||||||
|
#include "NamedObjectContext.h"
|
||||||
|
#include "HallsOfLightningActions.h"
|
||||||
|
|
||||||
|
class WotlkDungeonHoLActionContext : public NamedObjectContext<Action>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WotlkDungeonHoLActionContext() {
|
||||||
|
creators["bjarngrim target"] = &WotlkDungeonHoLActionContext::bjarngrim_target;
|
||||||
|
creators["avoid whirlwind"] = &WotlkDungeonHoLActionContext::avoid_whirlwind;
|
||||||
|
creators["volkhan target"] = &WotlkDungeonHoLActionContext::volkhan_target;
|
||||||
|
creators["static overload spread"] = &WotlkDungeonHoLActionContext::static_overload_spread;
|
||||||
|
creators["ball lightning spread"] = &WotlkDungeonHoLActionContext::ball_lightning_spread;
|
||||||
|
creators["ionar tank position"] = &WotlkDungeonHoLActionContext::ionar_tank_position;
|
||||||
|
creators["disperse position"] = &WotlkDungeonHoLActionContext::disperse_position;
|
||||||
|
creators["loken stack"] = &WotlkDungeonHoLActionContext::loken_stack;
|
||||||
|
creators["avoid lightning nova"] = &WotlkDungeonHoLActionContext::avoid_lightning_nova;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
static Action* bjarngrim_target(PlayerbotAI* ai) { return new BjarngrimTargetAction(ai); }
|
||||||
|
static Action* avoid_whirlwind(PlayerbotAI* ai) { return new AvoidWhirlwindAction(ai); }
|
||||||
|
static Action* volkhan_target(PlayerbotAI* ai) { return new VolkhanTargetAction(ai); }
|
||||||
|
static Action* static_overload_spread(PlayerbotAI* ai) { return new StaticOverloadSpreadAction(ai); }
|
||||||
|
static Action* ball_lightning_spread(PlayerbotAI* ai) { return new BallLightningSpreadAction(ai); }
|
||||||
|
static Action* ionar_tank_position(PlayerbotAI* ai) { return new IonarTankPositionAction(ai); }
|
||||||
|
static Action* disperse_position(PlayerbotAI* ai) { return new DispersePositionAction(ai); }
|
||||||
|
static Action* loken_stack(PlayerbotAI* ai) { return new LokenStackAction(ai); }
|
||||||
|
static Action* avoid_lightning_nova(PlayerbotAI* ai) { return new AvoidLightningNovaAction(ai); }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
#include "Playerbots.h"
|
||||||
|
#include "HallsOfLightningActions.h"
|
||||||
|
#include "HallsOfLightningStrategy.h"
|
||||||
|
|
||||||
|
bool BjarngrimTargetAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
Unit* target = nullptr;
|
||||||
|
|
||||||
|
// Target is not findable from threat table using AI_VALUE2(),
|
||||||
|
// therefore need to search manually for the unit name
|
||||||
|
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
|
||||||
|
|
||||||
|
for (auto i = targets.begin(); i != targets.end(); ++i)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(*i);
|
||||||
|
if (unit && unit->GetEntry() == NPC_STORMFORGED_LIEUTENANT)
|
||||||
|
{
|
||||||
|
target = unit;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Unit* currentTarget = AI_VALUE(Unit*, "current target");
|
||||||
|
// There are two, we don't want to ping-pong between them if we're attacking one already
|
||||||
|
if (target && currentTarget && currentTarget->GetEntry() == NPC_STORMFORGED_LIEUTENANT)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AI_VALUE(Unit*, "current target") == target)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Attack(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AvoidWhirlwindAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "general bjarngrim");
|
||||||
|
if (!boss) { return false; }
|
||||||
|
|
||||||
|
float distance = bot->GetExactDist2d(boss->GetPosition());
|
||||||
|
float radius = 8.0f;
|
||||||
|
float distanceExtra = 2.0f;
|
||||||
|
|
||||||
|
if (distance < radius + distanceExtra)
|
||||||
|
{
|
||||||
|
return MoveAway(boss, radius + distanceExtra - distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VolkhanTargetAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "volkhan");
|
||||||
|
if (!boss || AI_VALUE(Unit*, "current target") == boss)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Attack(boss);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StaticOverloadSpreadAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
float radius = 8.0f;
|
||||||
|
float distanceExtra = 2.0f;
|
||||||
|
|
||||||
|
GuidVector members = AI_VALUE(GuidVector, "group members");
|
||||||
|
for (auto& member : members)
|
||||||
|
{
|
||||||
|
if (bot->GetGUID() == member)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Unit* unit = botAI->GetUnit(member);
|
||||||
|
if (unit && unit->HasAura(SPELL_STATIC_OVERLOAD)
|
||||||
|
&& bot->GetExactDist2d(unit) < radius)
|
||||||
|
{
|
||||||
|
return MoveAway(unit, radius + distanceExtra - bot->GetExactDist2d(unit));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BallLightningSpreadAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
float radius = 6.0f;
|
||||||
|
float distanceExtra = 1.0f;
|
||||||
|
|
||||||
|
GuidVector members = AI_VALUE(GuidVector, "group members");
|
||||||
|
for (auto& member : members)
|
||||||
|
{
|
||||||
|
if (bot->GetGUID() == member)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Unit* unit = botAI->GetUnit(member);
|
||||||
|
if (unit && bot->GetExactDist2d(unit) < radius + distanceExtra)
|
||||||
|
{
|
||||||
|
return MoveAway(unit, radius + distanceExtra - bot->GetExactDist2d(unit));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IonarTankPositionAction::isUseful() { return bot->GetExactDist2d(IONAR_TANK_POSITION) > 10.0f; }
|
||||||
|
bool IonarTankPositionAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
return MoveTo(bot->GetMapId(), IONAR_TANK_POSITION.GetPositionX(), IONAR_TANK_POSITION.GetPositionY(), IONAR_TANK_POSITION.GetPositionZ(),
|
||||||
|
false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DispersePositionAction::isUseful() { return bot->GetExactDist2d(DISPERSE_POSITION) > 8.0f; }
|
||||||
|
bool DispersePositionAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
return MoveTo(bot->GetMapId(), DISPERSE_POSITION.GetPositionX(), DISPERSE_POSITION.GetPositionY(), DISPERSE_POSITION.GetPositionZ(),
|
||||||
|
false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LokenStackAction::isUseful()
|
||||||
|
{
|
||||||
|
// Minimum hunter range is 5, but values too close to this seem to cause issues..
|
||||||
|
// Hunter bots will try and melee in between ranged attacks, or just melee entirely at 5 as they are in range.
|
||||||
|
// 6.5 or 7.0 solves this.
|
||||||
|
if(bot->getClass() == CLASS_HUNTER)
|
||||||
|
{
|
||||||
|
return AI_VALUE2(float, "distance", "current target") > 6.5f;
|
||||||
|
}
|
||||||
|
// else
|
||||||
|
return AI_VALUE2(float, "distance", "current target") > 2.0f;
|
||||||
|
}
|
||||||
|
bool LokenStackAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "loken");
|
||||||
|
if (!boss) { return false; }
|
||||||
|
|
||||||
|
if (!boss->HasUnitState(UNIT_STATE_CASTING))
|
||||||
|
{
|
||||||
|
if(bot->getClass() == CLASS_HUNTER)
|
||||||
|
{
|
||||||
|
return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss) - 6.5f, 10.0f));
|
||||||
|
}
|
||||||
|
// else
|
||||||
|
return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss), 10.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool AvoidLightningNovaAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "loken");
|
||||||
|
if (!boss) { return false; }
|
||||||
|
|
||||||
|
float distance = bot->GetExactDist2d(boss->GetPosition());
|
||||||
|
float radius = 20.0f;
|
||||||
|
float distanceExtra = 2.0f;
|
||||||
|
|
||||||
|
if (distance < radius + distanceExtra)
|
||||||
|
{
|
||||||
|
return MoveAway(boss, radius + distanceExtra - distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
#ifndef _PLAYERBOT_WOTLKDUNGEONHOLACTIONS_H
|
||||||
|
#define _PLAYERBOT_WOTLKDUNGEONHOLACTIONS_H
|
||||||
|
|
||||||
|
#include "Action.h"
|
||||||
|
#include "AttackAction.h"
|
||||||
|
#include "PlayerbotAI.h"
|
||||||
|
#include "Playerbots.h"
|
||||||
|
#include "HallsOfLightningTriggers.h"
|
||||||
|
|
||||||
|
const Position IONAR_TANK_POSITION = Position(1078.860f, -261.928f, 61.226f);
|
||||||
|
const Position DISPERSE_POSITION = Position(1161.152f, -261.584f, 53.223f);
|
||||||
|
|
||||||
|
class BjarngrimTargetAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BjarngrimTargetAction(PlayerbotAI* ai) : AttackAction(ai, "bjarngrim target") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AvoidWhirlwindAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AvoidWhirlwindAction(PlayerbotAI* ai) : MovementAction(ai, "avoid whirlwind") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class VolkhanTargetAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VolkhanTargetAction(PlayerbotAI* ai) : AttackAction(ai, "volkhan target") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class StaticOverloadSpreadAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
StaticOverloadSpreadAction(PlayerbotAI* ai) : MovementAction(ai, "static overload spread") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BallLightningSpreadAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BallLightningSpreadAction(PlayerbotAI* ai) : MovementAction(ai, "ball lightning spread") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IonarTankPositionAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IonarTankPositionAction(PlayerbotAI* ai) : MovementAction(ai, "ionar tank position") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
bool isUseful() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DispersePositionAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DispersePositionAction(PlayerbotAI* ai) : MovementAction(ai, "disperse position") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
bool isUseful() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LokenStackAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LokenStackAction(PlayerbotAI* ai) : MovementAction(ai, "loken stack") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
bool isUseful() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AvoidLightningNovaAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AvoidLightningNovaAction(PlayerbotAI* ai) : MovementAction(ai, "avoid lightning nova") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
#include "HallsOfLightningMultipliers.h"
|
||||||
|
#include "HallsOfLightningActions.h"
|
||||||
|
#include "GenericSpellActions.h"
|
||||||
|
#include "ChooseTargetActions.h"
|
||||||
|
#include "MovementActions.h"
|
||||||
|
#include "HallsOfLightningTriggers.h"
|
||||||
|
#include "Action.h"
|
||||||
|
|
||||||
|
float BjarngrimMultiplier::GetValue(Action* action)
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "general bjarngrim");
|
||||||
|
if (!boss || botAI->IsHeal(bot)) { return 1.0f; }
|
||||||
|
|
||||||
|
if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_WHIRLWIND_BJARNGRIM))
|
||||||
|
{
|
||||||
|
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<AvoidWhirlwindAction*>(action))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect boss adds this way as sometimes they don't get added to threat table on dps bots,
|
||||||
|
// and some dps just stand at range and don't engage the boss at all as they can't find the adds
|
||||||
|
// Unit* boss_add = AI_VALUE2(Unit*, "find target", "stormforged lieutenant");
|
||||||
|
Unit* boss_add = nullptr;
|
||||||
|
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
|
||||||
|
|
||||||
|
for (auto i = targets.begin(); i != targets.end(); ++i)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(*i);
|
||||||
|
if (unit && unit->GetEntry() == NPC_STORMFORGED_LIEUTENANT)
|
||||||
|
{
|
||||||
|
boss_add = unit;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!boss_add || botAI->IsTank(bot)) { return 1.0f; }
|
||||||
|
|
||||||
|
if (dynamic_cast<DpsAssistAction*>(action))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action->getThreatType() == Action::ActionThreatType::Aoe)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float VolkhanMultiplier::GetValue(Action* action)
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "volkhan");
|
||||||
|
if (!boss || botAI->IsTank(bot) || botAI->IsHeal(bot)) { return 1.0f; }
|
||||||
|
|
||||||
|
if (dynamic_cast<DpsAssistAction*>(action))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action->getThreatType() == Action::ActionThreatType::Aoe)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float IonarMultiplier::GetValue(Action* action)
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "ionar");
|
||||||
|
if (!boss) { return 1.0f; }
|
||||||
|
|
||||||
|
if(!bot->CanSeeOrDetect(boss))
|
||||||
|
{
|
||||||
|
if (dynamic_cast<MovementAction*>(action)
|
||||||
|
&& !dynamic_cast<DispersePositionAction*>(action)
|
||||||
|
&& !dynamic_cast<StaticOverloadSpreadAction*>(action))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float LokenMultiplier::GetValue(Action* action)
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "loken");
|
||||||
|
if (!boss) { return 1.0f; }
|
||||||
|
|
||||||
|
if (dynamic_cast<FleeAction*>(action)) { return 0.0f; }
|
||||||
|
|
||||||
|
if (boss->FindCurrentSpellBySpellId(SPELL_LIGHTNING_NOVA)
|
||||||
|
&& dynamic_cast<MovementAction*>(action)
|
||||||
|
&& !dynamic_cast<AvoidLightningNovaAction*>(action))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
#ifndef _PLAYERBOT_WOTLKDUNGEONHOLMULTIPLIERS_H
|
||||||
|
#define _PLAYERBOT_WOTLKDUNGEONHOLMULTIPLIERS_H
|
||||||
|
|
||||||
|
#include "Multiplier.h"
|
||||||
|
|
||||||
|
class BjarngrimMultiplier : public Multiplier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BjarngrimMultiplier(PlayerbotAI* ai) : Multiplier(ai, "general bjarngrim") {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual float GetValue(Action* action);
|
||||||
|
};
|
||||||
|
|
||||||
|
class VolkhanMultiplier : public Multiplier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VolkhanMultiplier(PlayerbotAI* ai) : Multiplier(ai, "volkhan") {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual float GetValue(Action* action);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IonarMultiplier : public Multiplier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IonarMultiplier(PlayerbotAI* ai) : Multiplier(ai, "ionar") {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual float GetValue(Action* action);
|
||||||
|
};
|
||||||
|
|
||||||
|
class LokenMultiplier : public Multiplier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LokenMultiplier(PlayerbotAI* ai) : Multiplier(ai, "loken") {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual float GetValue(Action* action);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#include "HallsOfLightningStrategy.h"
|
||||||
|
#include "HallsOfLightningMultipliers.h"
|
||||||
|
|
||||||
|
|
||||||
|
void WotlkDungeonHoLStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
|
||||||
|
{
|
||||||
|
// General Bjarngrim
|
||||||
|
triggers.push_back(new TriggerNode("stormforged lieutenant",
|
||||||
|
NextAction::array(0, new NextAction("bjarngrim target", ACTION_RAID + 5), nullptr)));
|
||||||
|
triggers.push_back(new TriggerNode("whirlwind",
|
||||||
|
NextAction::array(0, new NextAction("avoid whirlwind", ACTION_RAID + 4), nullptr)));
|
||||||
|
|
||||||
|
// Volkhan
|
||||||
|
triggers.push_back(new TriggerNode("volkhan",
|
||||||
|
NextAction::array(0, new NextAction("volkhan target", ACTION_RAID + 5), nullptr)));
|
||||||
|
|
||||||
|
// Ionar
|
||||||
|
triggers.push_back(new TriggerNode("ionar disperse",
|
||||||
|
NextAction::array(0, new NextAction("disperse position", ACTION_MOVE + 5), nullptr)));
|
||||||
|
triggers.push_back(new TriggerNode("ionar tank aggro",
|
||||||
|
NextAction::array(0, new NextAction("ionar tank position", ACTION_MOVE + 4), nullptr)));
|
||||||
|
triggers.push_back(new TriggerNode("static overload",
|
||||||
|
NextAction::array(0, new NextAction("static overload spread", ACTION_MOVE + 3), nullptr)));
|
||||||
|
// TODO: Targeted player can dodge the ball, but a single player soaking it isn't too bad to heal
|
||||||
|
triggers.push_back(new TriggerNode("ball lightning",
|
||||||
|
NextAction::array(0, new NextAction("ball lightning spread", ACTION_MOVE + 2), nullptr)));
|
||||||
|
|
||||||
|
// Loken
|
||||||
|
triggers.push_back(new TriggerNode("lightning nova",
|
||||||
|
NextAction::array(0, new NextAction("avoid lightning nova", ACTION_MOVE + 5), nullptr)));
|
||||||
|
triggers.push_back(new TriggerNode("loken ranged",
|
||||||
|
NextAction::array(0, new NextAction("loken stack", ACTION_MOVE + 4), nullptr)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WotlkDungeonHoLStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
|
||||||
|
{
|
||||||
|
multipliers.push_back(new BjarngrimMultiplier(botAI));
|
||||||
|
multipliers.push_back(new VolkhanMultiplier(botAI));
|
||||||
|
multipliers.push_back(new IonarMultiplier(botAI));
|
||||||
|
multipliers.push_back(new LokenMultiplier(botAI));
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef _PLAYERBOT_WOTLKDUNGEONHOLSTRATEGY_H
|
||||||
|
#define _PLAYERBOT_WOTLKDUNGEONHOLSTRATEGY_H
|
||||||
|
|
||||||
|
#include "Multiplier.h"
|
||||||
|
#include "AiObjectContext.h"
|
||||||
|
#include "Strategy.h"
|
||||||
|
|
||||||
|
|
||||||
|
class WotlkDungeonHoLStrategy : public Strategy
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WotlkDungeonHoLStrategy(PlayerbotAI* ai) : Strategy(ai) {}
|
||||||
|
virtual std::string const getName() override { return "halls of lightning"; }
|
||||||
|
virtual void InitTriggers(std::vector<TriggerNode*> &triggers) override;
|
||||||
|
virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef _PLAYERBOT_WOTLKDUNGEONHOLTRIGGERCONTEXT_H
|
||||||
|
#define _PLAYERBOT_WOTLKDUNGEONHOLTRIGGERCONTEXT_H
|
||||||
|
|
||||||
|
#include "NamedObjectContext.h"
|
||||||
|
#include "AiObjectContext.h"
|
||||||
|
#include "HallsOfLightningTriggers.h"
|
||||||
|
|
||||||
|
class WotlkDungeonHoLTriggerContext : public NamedObjectContext<Trigger>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WotlkDungeonHoLTriggerContext()
|
||||||
|
{
|
||||||
|
creators["stormforged lieutenant"] = &WotlkDungeonHoLTriggerContext::stormforged_lieutenant;
|
||||||
|
creators["whirlwind"] = &WotlkDungeonHoLTriggerContext::bjarngrim_whirlwind;
|
||||||
|
creators["volkhan"] = &WotlkDungeonHoLTriggerContext::volkhan;
|
||||||
|
creators["static overload"] = &WotlkDungeonHoLTriggerContext::static_overload;
|
||||||
|
creators["ball lightning"] = &WotlkDungeonHoLTriggerContext::ball_lightning;
|
||||||
|
creators["ionar tank aggro"] = &WotlkDungeonHoLTriggerContext::ionar_tank_aggro;
|
||||||
|
creators["ionar disperse"] = &WotlkDungeonHoLTriggerContext::ionar_disperse;
|
||||||
|
creators["loken ranged"] = &WotlkDungeonHoLTriggerContext::loken_ranged;
|
||||||
|
creators["lightning nova"] = &WotlkDungeonHoLTriggerContext::lightning_nova;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
static Trigger* stormforged_lieutenant(PlayerbotAI* ai) { return new StormforgedLieutenantTrigger(ai); }
|
||||||
|
static Trigger* bjarngrim_whirlwind(PlayerbotAI* ai) { return new BjarngrimWhirlwindTrigger(ai); }
|
||||||
|
static Trigger* volkhan(PlayerbotAI* ai) { return new VolkhanTrigger(ai); }
|
||||||
|
static Trigger* static_overload(PlayerbotAI* ai) { return new IonarStaticOverloadTrigger(ai); }
|
||||||
|
static Trigger* ball_lightning(PlayerbotAI* ai) { return new IonarBallLightningTrigger(ai); }
|
||||||
|
static Trigger* ionar_tank_aggro(PlayerbotAI* ai) { return new IonarTankAggroTrigger(ai); }
|
||||||
|
static Trigger* ionar_disperse(PlayerbotAI* ai) { return new IonarDisperseTrigger(ai); }
|
||||||
|
static Trigger* loken_ranged(PlayerbotAI* ai) { return new LokenRangedTrigger(ai); }
|
||||||
|
static Trigger* lightning_nova(PlayerbotAI* ai) { return new LokenLightningNovaTrigger(ai); }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#include "Playerbots.h"
|
||||||
|
#include "HallsOfLightningTriggers.h"
|
||||||
|
#include "AiObject.h"
|
||||||
|
#include "AiObjectContext.h"
|
||||||
|
|
||||||
|
bool StormforgedLieutenantTrigger::IsActive()
|
||||||
|
{
|
||||||
|
if (botAI->IsTank(bot) || botAI->IsHeal(bot)) { return false; }
|
||||||
|
|
||||||
|
// Target is not findable from threat table using AI_VALUE2(),
|
||||||
|
// therefore need to search manually for the unit name
|
||||||
|
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
|
||||||
|
|
||||||
|
for (auto i = targets.begin(); i != targets.end(); ++i)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(*i);
|
||||||
|
if (unit && unit->GetEntry() == NPC_STORMFORGED_LIEUTENANT)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BjarngrimWhirlwindTrigger::IsActive()
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "general bjarngrim");
|
||||||
|
if (!boss) { return false; }
|
||||||
|
|
||||||
|
return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_WHIRLWIND_BJARNGRIM);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VolkhanTrigger::IsActive()
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "volkhan");
|
||||||
|
return boss && !botAI->IsTank(bot) && !botAI->IsHeal(bot);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IonarStaticOverloadTrigger::IsActive()
|
||||||
|
{
|
||||||
|
GuidVector members = AI_VALUE(GuidVector, "group members");
|
||||||
|
for (auto& member : members)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(member);
|
||||||
|
if (unit && unit->HasAura(SPELL_STATIC_OVERLOAD))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IonarBallLightningTrigger::IsActive()
|
||||||
|
{
|
||||||
|
if (botAI->IsMelee(bot)) { return false; }
|
||||||
|
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "ionar");
|
||||||
|
if (!boss) { return false; }
|
||||||
|
|
||||||
|
return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_BALL_LIGHTNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IonarTankAggroTrigger::IsActive()
|
||||||
|
{
|
||||||
|
if (!botAI->IsTank(bot)) { return false; }
|
||||||
|
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "ionar");
|
||||||
|
if (!boss) { return false; }
|
||||||
|
|
||||||
|
return AI_VALUE2(bool, "has aggro", "current target");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IonarDisperseTrigger::IsActive()
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "ionar");
|
||||||
|
if (!boss) { return false; }
|
||||||
|
|
||||||
|
return !bot->CanSeeOrDetect(boss) || boss->FindCurrentSpellBySpellId(SPELL_DISPERSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LokenRangedTrigger::IsActive()
|
||||||
|
{
|
||||||
|
return !botAI->IsMelee(bot) && AI_VALUE2(Unit*, "find target", "loken");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LokenLightningNovaTrigger::IsActive()
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "loken");
|
||||||
|
if (!boss) { return false; }
|
||||||
|
|
||||||
|
return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_LIGHTNING_NOVA);
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
#ifndef _PLAYERBOT_WOTLKDUNGEONHOLTRIGGERS_H
|
||||||
|
#define _PLAYERBOT_WOTLKDUNGEONHOLTRIGGERS_H
|
||||||
|
|
||||||
|
#include "Trigger.h"
|
||||||
|
#include "PlayerbotAIConfig.h"
|
||||||
|
#include "GenericTriggers.h"
|
||||||
|
#include "DungeonStrategyUtils.h"
|
||||||
|
|
||||||
|
enum HallsOfLightningIDs
|
||||||
|
{
|
||||||
|
// General Bjarngrim
|
||||||
|
NPC_STORMFORGED_LIEUTENANT = 29240,
|
||||||
|
SPELL_WHIRLWIND_BJARNGRIM = 52027,
|
||||||
|
|
||||||
|
// Ionar
|
||||||
|
SPELL_STATIC_OVERLOAD_N = 52658,
|
||||||
|
SPELL_STATIC_OVERLOAD_H = 59795,
|
||||||
|
SPELL_BALL_LIGHTNING_N = 52780,
|
||||||
|
SPELL_BALL_LIGHTNING_H = 59800,
|
||||||
|
SPELL_DISPERSE = 52770,
|
||||||
|
NPC_SPARK_OF_IONAR = 28926,
|
||||||
|
|
||||||
|
// Loken
|
||||||
|
SPELL_LIGHTNING_NOVA_N = 52960,
|
||||||
|
SPELL_LIGHTNING_NOVA_H = 59835,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SPELL_STATIC_OVERLOAD DUNGEON_MODE(bot, SPELL_STATIC_OVERLOAD_N, SPELL_STATIC_OVERLOAD_H)
|
||||||
|
#define SPELL_BALL_LIGHTNING DUNGEON_MODE(bot, SPELL_BALL_LIGHTNING_N, SPELL_BALL_LIGHTNING_H)
|
||||||
|
#define SPELL_LIGHTNING_NOVA DUNGEON_MODE(bot, SPELL_LIGHTNING_NOVA_N, SPELL_LIGHTNING_NOVA_H)
|
||||||
|
|
||||||
|
class StormforgedLieutenantTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
StormforgedLieutenantTrigger(PlayerbotAI* ai) : Trigger(ai, "stormforged lieutenant") {}
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BjarngrimWhirlwindTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BjarngrimWhirlwindTrigger(PlayerbotAI* ai) : Trigger(ai, "bjarngrim whirlwind") {}
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class VolkhanTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VolkhanTrigger(PlayerbotAI* ai) : Trigger(ai, "volkhan") {}
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IonarStaticOverloadTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IonarStaticOverloadTrigger(PlayerbotAI* ai) : Trigger(ai, "ionar static overload") {}
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IonarBallLightningTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IonarBallLightningTrigger(PlayerbotAI* ai) : Trigger(ai, "ionar ball lightning spread") {}
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IonarTankAggroTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IonarTankAggroTrigger(PlayerbotAI* ai) : Trigger(ai, "ionar tank aggro") {}
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IonarDisperseTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IonarDisperseTrigger(PlayerbotAI* ai) : Trigger(ai, "ionar disperse") {}
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LokenRangedTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LokenRangedTrigger(PlayerbotAI* ai) : Trigger(ai, "loken ranged") {}
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LokenLightningNovaTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LokenLightningNovaTrigger(PlayerbotAI* ai) : Trigger(ai, "lightning nova") {}
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -54,9 +54,16 @@ bool FirebombSpreadAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (bot->GetExactDist2d(botAI->GetUnit(member)) < targetDist)
|
|
||||||
|
Unit* unit = botAI->GetUnit(member);
|
||||||
|
if (!unit)
|
||||||
{
|
{
|
||||||
return MoveAway(botAI->GetUnit(member), targetDist);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot->GetExactDist2d(unit) < targetDist)
|
||||||
|
{
|
||||||
|
return MoveAway(unit, targetDist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user