Merge branch 'liyunfan1223:master' into armor_token_usage

This commit is contained in:
avirar
2024-10-21 10:22:55 +11:00
committed by GitHub
24 changed files with 838 additions and 47 deletions

View File

@@ -464,7 +464,7 @@ INSERT INTO `ai_playerbot_texts` (`name`, `text`, `say_type`, `reply_type`, `tex
-- %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', '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', '%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...', '', 'Ищу друга.'),
@@ -1457,19 +1457,3 @@ INSERT INTO `ai_playerbot_texts` (`name`, `text`, `say_type`, `reply_type`, `tex
('dummy_end', 'dummy', 0, 0, '', '', '', '', '', '', '', '');
DROP TABLE IF EXISTS `ai_playerbot_texts_chance`;
CREATE TABLE IF NOT EXISTS `ai_playerbot_texts_chance` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`probability` bigint(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=UTF8;
/*!40000 ALTER TABLE `ai_playerbot_texts_chance` DISABLE KEYS */;
INSERT INTO `ai_playerbot_texts_chance` (`id`, `name`, `probability`) VALUES
(1, 'taunt', 30),
(2, 'aoe', 75),
(3, 'loot', 20);
/*!40000 ALTER TABLE `ai_playerbot_texts_chance` ENABLE KEYS */;

View File

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

View File

@@ -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;

View File

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

View File

@@ -100,6 +100,8 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
Player* bot = botSession->GetPlayer();
if (!bot)
{
// Debug log
LOG_DEBUG("mod-playerbots", "Bot player could not be loaded for account ID: {}", botAccountId);
botSession->LogoutPlayer(true);
delete botSession;
botLoading.erase(holder.GetGuid());
@@ -108,6 +110,14 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
uint32 masterAccount = holder.GetMasterAccountId();
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;
bool allowed = false;
if (botAccountId == masterAccount)
@@ -115,7 +125,7 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
allowed = true;
}
else if (masterSession && sPlayerbotAIConfig->allowGuildBots && bot->GetGuildId() != 0 &&
bot->GetGuildId() == masterSession->GetPlayer()->GetGuildId())
bot->GetGuildId() == masterPlayer->GetGuildId())
{
allowed = true;
}
@@ -129,10 +139,14 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
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(player);
PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(masterPlayer);
if (!mgr)
{
LOG_DEBUG("mod-playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue());
}
uint32 count = mgr->GetPlayerbotsCount();
uint32 cls_count = mgr->GetPlayerbotsCountByClass(bot->getClass());
if (count >= sPlayerbotAIConfig->maxAddedBots)
@@ -428,14 +442,17 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
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;
}
Player* master = botAI->GetMaster();
if (master)
if (!master)
{
ObjectGuid masterGuid = master->GetGUID();
if (master->GetGroup() && !master->GetGroup()->IsLeader(masterGuid))
master->GetGroup()->ChangeLeader(masterGuid);
// Log a warning to indicate that the master is null
LOG_DEBUG("mod-playerbots", "Master is null for bot with GUID: {}", bot->GetGUID().GetRawValue());
return;
}
Group* group = bot->GetGroup();

View File

@@ -58,6 +58,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
actionContexts.Add(new WotlkDungeonVHActionContext());
actionContexts.Add(new WotlkDungeonGDActionContext());
actionContexts.Add(new WotlkDungeonHoSActionContext());
actionContexts.Add(new WotlkDungeonHoLActionContext());
triggerContexts.Add(new TriggerContext());
triggerContexts.Add(new ChatTriggerContext());
@@ -76,6 +77,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
triggerContexts.Add(new WotlkDungeonVHTriggerContext());
triggerContexts.Add(new WotlkDungeonGDTriggerContext());
triggerContexts.Add(new WotlkDungeonHoSTriggerContext());
triggerContexts.Add(new WotlkDungeonHoLTriggerContext());
valueContexts.Add(new ValueContext());

View File

@@ -239,9 +239,16 @@ bool CheckMountStateAction::Mount()
// continue;
uint32 index = (spellInfo->Effects[1].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED ||
spellInfo->Effects[2].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED)
? 1
: 0;
spellInfo->Effects[2].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED ||
// Winged Steed of the Ebon Blade
// 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 &&
std::max(spellInfo->Effects[EFFECT_1].BasePoints, spellInfo->Effects[EFFECT_2].BasePoints) > 59)
@@ -263,6 +270,42 @@ bool CheckMountStateAction::Mount()
: 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];
if (hasSwiftMount)
{

View File

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

View File

@@ -10,12 +10,11 @@
#include "wotlk/violethold/VioletHoldStrategy.h"
#include "wotlk/gundrak/GundrakStrategy.h"
#include "wotlk/hallsofstone/HallsOfStoneStrategy.h"
#include "wotlk/hallsoflightning/HallsOfLightningStrategy.h"
/*
Full list/TODO:
Halls of Lightning - HoL
General Bjarngrim, Volkhan, Ionar, Loken
The Oculus - Occ
Drakos the Interrogator, Varos Cloudstrider, Mage-Lord Urom, Ley-Guardian Eregos
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_gd(PlayerbotAI* botAI) { return new WotlkDungeonGDStrategy(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_up(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_cos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }

View File

@@ -9,7 +9,7 @@
#include "violethold/VioletHoldActionContext.h"
#include "gundrak/GundrakActionContext.h"
#include "hallsofstone/HallsOfStoneActionContext.h"
// #include "hallsoflightning/HallsOfLightningActionContext.h"
#include "hallsoflightning/HallsOfLightningActionContext.h"
// #include "oculus/OculusActionContext.h"
// #include "utgardepinnacle/UtgardePinnacleActionContext.h"
// #include "cullingofstratholme/CullingOfStratholmeActionContext.h"

View File

@@ -9,7 +9,7 @@
#include "violethold/VioletHoldTriggerContext.h"
#include "gundrak/GundrakTriggerContext.h"
#include "hallsofstone/HallsOfStoneTriggerContext.h"
// #include "hallsoflightning/HallsOfLightningTriggerContext.h"
#include "hallsoflightning/HallsOfLightningTriggerContext.h"
// #include "oculus/OculusTriggerContext.h"
// #include "utgardepinnacle/UtgardePinnacleTriggerContext.h"
// #include "cullingofstratholme/CullingOfStratholmeTriggerContext.h"

View File

@@ -38,19 +38,15 @@ float KrikthirMultiplier::GetValue(Action* action)
if (boss && watcher)
{
// Do not target swap
// TODO: Need to suppress AoE actions but unsure how to identify them
// TODO: TEST AOE Avoid
if (dynamic_cast<DpsAssistAction*>(action)
|| dynamic_cast<DpsAoeAction*>(action))
if (dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
if (action->getThreatType() == Action::ActionThreatType::Aoe)
{
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;
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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));
}

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -54,9 +54,16 @@ bool FirebombSpreadAction::Execute(Event event)
{
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;