mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
Compare commits
No commits in common. "c8dce882d621f6250a96370bfd3c1bf79e9ebc4e" and "4b7b0958dd9ab8d0b84ebe17b46ac9ad3264f05d" have entirely different histories.
c8dce882d6
...
4b7b0958dd
@ -67,7 +67,7 @@ Bot messages have to be translatable, but you don't need to do the translations
|
||||
the message is in a translatable format, and list in the table the message_key and the default English message.
|
||||
Search for GetBotTextOrDefault in the codebase for examples.
|
||||
-->
|
||||
- Does this change add bot messages to translate?
|
||||
Does this change add bot messages to translate?
|
||||
- - [ ] No
|
||||
- - [ ] Yes (**list messages in the table**)
|
||||
|
||||
@ -81,7 +81,7 @@ Search for GetBotTextOrDefault in the codebase for examples.
|
||||
AI assistance is allowed, but all submitted code must be fully understood, reviewed, and owned by the contributor.
|
||||
We expect contributors to be honest about what they do and do not understand.
|
||||
-->
|
||||
- Was AI assistance used while working on this change?
|
||||
Was AI assistance used while working on this change?
|
||||
- - [ ] No
|
||||
- - [ ] Yes (**explain below**)
|
||||
<!--
|
||||
|
||||
48
README.md
48
README.md
@ -34,9 +34,11 @@ We also have a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can
|
||||
|
||||
Supported platforms are Ubuntu, Windows, and macOS. Other Linux distributions may work, but may not receive support.
|
||||
|
||||
> **Important:** All `mod-playerbots` installations require a custom fork of AzerothCore: [mod-playerbots/azerothcore-wotlk (Playerbot branch)](https://github.com/mod-playerbots/azerothcore-wotlk/tree/Playerbot). The standard AzerothCore repository will **not** work.
|
||||
**All `mod-playerbots` installations require a custom branch of AzerothCore: [mod-playerbots/azerothcore-wotlk/tree/Playerbot](https://github.com/mod-playerbots/azerothcore-wotlk/tree/Playerbot).** This branch allows the `mod-playerbots` module to build and function. Updates from the upstream are implemented regularly to this branch. Instructions for installing this required branch and this module are provided below.
|
||||
|
||||
### Quick Start
|
||||
### Cloning the Repositories
|
||||
|
||||
To install both the required branch of AzerothCore and the `mod-playerbots` module from source, run the following:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mod-playerbots/azerothcore-wotlk.git --branch=Playerbot
|
||||
@ -44,18 +46,44 @@ cd azerothcore-wotlk/modules
|
||||
git clone https://github.com/mod-playerbots/mod-playerbots.git --branch=master
|
||||
```
|
||||
|
||||
Then build the server following the platform-specific instructions in our **[Installation Guide](https://github.com/mod-playerbots/mod-playerbots/wiki/Installation-Guide)**.
|
||||
For more information, refer to the [AzerothCore Installation Guide](https://www.azerothcore.org/wiki/installation) and [Installing a Module](https://www.azerothcore.org/wiki/installing-a-module) pages.
|
||||
|
||||
> **Testing branch:** A `test-staging` branch is available with the latest features and fixes before they are merged into `master`. To use it, clone with `--branch=test-staging` instead. Note that this branch may contain unstable or breaking changes — use it at your own risk and only if you are comfortable troubleshooting issues.
|
||||
### Docker Installation
|
||||
|
||||
### Detailed Guides
|
||||
Docker installations are considered experimental (unofficial with limited support), and previous Docker experience is recommended. To install `mod-playerbots` on Docker, first clone the required branch of AzerothCore and this module:
|
||||
|
||||
| Guide | Description |
|
||||
|---|---|
|
||||
| **[Installation Guide](https://github.com/mod-playerbots/mod-playerbots/wiki/Installation-Guide)** | Full step-by-step instructions for clean installs, migrating from existing AzerothCore, Docker setup, adding modules, and updating |
|
||||
| **[Troubleshooting](https://github.com/mod-playerbots/mod-playerbots/wiki/Troubleshooting)** | Solutions to the most common build errors, database issues, configuration mistakes, crashes, and platform-specific problems |
|
||||
```bash
|
||||
git clone https://github.com/mod-playerbots/azerothcore-wotlk.git --branch=Playerbot
|
||||
cd azerothcore-wotlk/modules
|
||||
git clone https://github.com/mod-playerbots/mod-playerbots.git --branch=master
|
||||
```
|
||||
|
||||
For additional references, see the [AzerothCore Installation Guide](https://www.azerothcore.org/wiki/installation) and [Installing a Module](https://www.azerothcore.org/wiki/installing-a-module) pages.
|
||||
Afterwards, create a `docker-compose.override.yml` file in the `azerothcore-wotlk` directory. This override file allows for mounting the modules directory to the `ac-worldserver` service which is required for it to run. Put the following inside and save:
|
||||
|
||||
```yml
|
||||
services:
|
||||
ac-worldserver:
|
||||
volumes:
|
||||
- ./modules:/azerothcore/modules:ro
|
||||
```
|
||||
|
||||
Additionally, this override file can be used to set custom configuration settings for `ac-worldserver` and any modules you install as environment variables:
|
||||
|
||||
```yml
|
||||
services:
|
||||
ac-worldserver:
|
||||
environment:
|
||||
AC_RATE_XP_KILL: "1"
|
||||
AC_AI_PLAYERBOT_RANDOM_BOT_AUTOLOGIN: "1"
|
||||
volumes:
|
||||
- ./modules:/azerothcore/modules:ro
|
||||
```
|
||||
|
||||
For example, to double the experience gain rate per kill, take the setting `Rate.XP.Kill = 1` from [woldserver.conf](https://github.com/mod-playerbots/azerothcore-wotlk/blob/Playerbot/src/server/apps/worldserver/worldserver.conf.dist), convert it to an environment variable, and change it to the desired setting in the override file to get `AC_RATE_XP_KILL: "2"`. If you wanted to disable random bots from logging in automatically, take the `AiPlayerbot.RandomBotAutologin = 1` setting from [playerbots.conf](https://github.com/mod-playerbots/mod-playerbots/blob/master/conf/playerbots.conf.dist) and do the same to get `AC_AI_PLAYERBOT_RANDOM_BOT_AUTOLOGIN: "0"`. For more information on how to configure Azerothcore, Playerbots, and other module settings as environment variables in Docker Compose, see the "Configuring AzerothCore in Containers" section in the [Install With Docker](https://www.azerothcore.org/wiki/install-with-docker) guide.
|
||||
|
||||
Before building, consider setting the database password. One way to do this is to create a `.env` file in the root `azerothcore-wotlk` directory using the [template](https://github.com/mod-playerbots/azerothcore-wotlk/blob/Playerbot/conf/dist/env.docker). This file also allows you to set the user and group Docker uses for the services in case you run into any permissions issues, which are the most common cause for Docker installation problems.
|
||||
|
||||
Use `docker compose up -d --build` to build and run the server. For more information, including how to create an account and taking backups, refer to the [Install With Docker](https://www.azerothcore.org/wiki/install-with-docker) page.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@ -298,24 +298,9 @@ AiPlayerbot.TwoRoundsGearInit = 0
|
||||
# Default: 0 (disabled)
|
||||
AiPlayerbot.FreeMethodLoot = 0
|
||||
|
||||
# Bots' Roll level bots will use for items they Need (0 = pass, 1 = greed, 2 = need)
|
||||
# Bots' loot roll level (0 = pass, 1 = greed, 2 = need)
|
||||
# Default: 1 (greed)
|
||||
AiPlayerbot.LootNeedRollLevel = 1
|
||||
|
||||
# Enable bots to roll GREED on items (global toggle)
|
||||
# If disabled, bots will PASS instead of GREED on all items
|
||||
# Default: 0 (disabled - bots only NEED or PASS)
|
||||
AiPlayerbot.LootGreedRollLevel = 0
|
||||
|
||||
# Enable bots to roll on recipes. Will NEED on learnable profession recipes they don't already know
|
||||
# Bots will roll GREED on BoE recipes they can't learn if LootRollGreed is enabled.
|
||||
# Default: 0 (disabled)
|
||||
AiPlayerbot.LootRollRecipe = 0
|
||||
|
||||
# Bots with enchanting will roll DISENCHANT instead of GREED on disenchantable items
|
||||
# If disabled, bots will GREED on disenchantable items instead
|
||||
# Default: 0 (disabled)
|
||||
AiPlayerbot.LootRollDisenchant = 0
|
||||
AiPlayerbot.LootRollLevel = 1
|
||||
|
||||
#
|
||||
#
|
||||
@ -1406,28 +1391,28 @@ AiPlayerbot.PremadeSpecLink.1.5.80 = 0502300123-3-250031220223012521332113321
|
||||
#
|
||||
|
||||
AiPlayerbot.PremadeSpecName.2.0 = holy pve
|
||||
AiPlayerbot.PremadeSpecGlyph.2.0 = 41106,43367,45741,43368,43365,41109
|
||||
AiPlayerbot.PremadeSpecGlyph.2.0 = 41106,43367,45741,43369,43365,41109
|
||||
AiPlayerbot.PremadeSpecLink.2.0.60 = 50350151020013053100515221
|
||||
AiPlayerbot.PremadeSpecLink.2.0.80 = 50350152220013053100515221-503201312
|
||||
AiPlayerbot.PremadeSpecName.2.1 = prot pve
|
||||
AiPlayerbot.PremadeSpecGlyph.2.1 = 41099,43367,43869,43368,43369,45745
|
||||
AiPlayerbot.PremadeSpecGlyph.2.1 = 41099,43367,43869,43369,43365,45745
|
||||
AiPlayerbot.PremadeSpecLink.2.1.60 = -05005135203102311333112321
|
||||
AiPlayerbot.PremadeSpecLink.2.1.80 = -05005135203102311333312321-502302012003
|
||||
AiPlayerbot.PremadeSpecName.2.2 = ret pve
|
||||
AiPlayerbot.PremadeSpecGlyph.2.2 = 41092,43367,41099,43368,43369,43869
|
||||
AiPlayerbot.PremadeSpecGlyph.2.2 = 41092,43367,41099,43369,43365,43869
|
||||
AiPlayerbot.PremadeSpecLink.2.2.60 = --05230051203331302133231131
|
||||
AiPlayerbot.PremadeSpecLink.2.2.65 = -05-05230051203331302133231131
|
||||
AiPlayerbot.PremadeSpecLink.2.2.80 = 050501-05-05232051203331302133231331
|
||||
AiPlayerbot.PremadeSpecName.2.3 = holy pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.2.3 = 41110,43367,45746,43369,43365,45747
|
||||
AiPlayerbot.PremadeSpecGlyph.2.3 = 41110,43367,45746,43366,43365,45747
|
||||
AiPlayerbot.PremadeSpecLink.2.3.60 = 50332150300013050133215221
|
||||
AiPlayerbot.PremadeSpecLink.2.3.80 = 50332150300013050133315221-5032013122
|
||||
AiPlayerbot.PremadeSpecName.2.4 = prot pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.2.4 = 41092,43367,41101,43369,43365,45745
|
||||
AiPlayerbot.PremadeSpecGlyph.2.4 = 41092,43369,41101,43368,43365,45745
|
||||
AiPlayerbot.PremadeSpecLink.2.4.60 = -15320130223122311323311321
|
||||
AiPlayerbot.PremadeSpecLink.2.4.80 = -15320130223122321333312321-052300502
|
||||
AiPlayerbot.PremadeSpecName.2.5 = ret pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.2.5 = 41095,43367,41102,43369,43365,45747
|
||||
AiPlayerbot.PremadeSpecGlyph.2.5 = 41095,43369,41102,43368,43365,45747
|
||||
AiPlayerbot.PremadeSpecLink.2.5.60 = --05230250203331222133201321
|
||||
AiPlayerbot.PremadeSpecLink.2.5.80 = -1532013022-05230250203331322133201321
|
||||
|
||||
|
||||
@ -163,7 +163,6 @@ public:
|
||||
creators["war stomp"] = &ActionContext::war_stomp;
|
||||
creators["blood fury"] = &ActionContext::blood_fury;
|
||||
creators["berserking"] = &ActionContext::berserking;
|
||||
creators["every man for himself"] = &ActionContext::every_man_for_himself;
|
||||
creators["use trinket"] = &ActionContext::use_trinket;
|
||||
creators["auto talents"] = &ActionContext::auto_talents;
|
||||
creators["auto share quest"] = &ActionContext::auto_share_quest;
|
||||
@ -358,7 +357,6 @@ private:
|
||||
static Action* war_stomp(PlayerbotAI* botAI) { return new CastWarStompAction(botAI); }
|
||||
static Action* blood_fury(PlayerbotAI* botAI) { return new CastBloodFuryAction(botAI); }
|
||||
static Action* berserking(PlayerbotAI* botAI) { return new CastBerserkingAction(botAI); }
|
||||
static Action* every_man_for_himself(PlayerbotAI* botAI) { return new CastEveryManForHimselfAction(botAI); }
|
||||
static Action* use_trinket(PlayerbotAI* botAI) { return new UseTrinketAction(botAI); }
|
||||
static Action* auto_talents(PlayerbotAI* botAI) { return new AutoSetTalentsAction(botAI); }
|
||||
static Action* auto_share_quest(PlayerbotAI* ai) { return new AutoShareQuestAction(ai); }
|
||||
|
||||
@ -311,30 +311,6 @@ bool CastVehicleSpellAction::Execute(Event /*event*/)
|
||||
return botAI->CastVehicleSpell(spellId, GetTarget());
|
||||
}
|
||||
|
||||
bool CastEveryManForHimselfAction::isPossible()
|
||||
{
|
||||
uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
|
||||
if (!spellId)
|
||||
return false;
|
||||
|
||||
if (!bot->HasSpell(spellId))
|
||||
return false;
|
||||
|
||||
if (bot->HasSpellCooldown(spellId))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CastEveryManForHimselfAction::isUseful()
|
||||
{
|
||||
return bot->HasAuraType(SPELL_AURA_MOD_STUN) ||
|
||||
bot->HasAuraType(SPELL_AURA_MOD_FEAR) ||
|
||||
bot->HasAuraType(SPELL_AURA_MOD_ROOT) ||
|
||||
bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) ||
|
||||
bot->HasAuraType(SPELL_AURA_MOD_CHARM);
|
||||
}
|
||||
|
||||
bool UseTrinketAction::Execute(Event /*event*/)
|
||||
{
|
||||
Item* trinket1 = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_TRINKET1);
|
||||
|
||||
@ -284,16 +284,6 @@ public:
|
||||
CastBerserkingAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "berserking") {}
|
||||
};
|
||||
|
||||
class CastEveryManForHimselfAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
CastEveryManForHimselfAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "every man for himself") {}
|
||||
|
||||
std::string const GetTargetName() override { return "self target"; }
|
||||
bool isPossible() override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class UseTrinketAction : public Action
|
||||
{
|
||||
public:
|
||||
|
||||
@ -22,10 +22,10 @@ bool LootRollAction::Execute(Event /*event*/)
|
||||
std::vector<Roll*> rolls = group->GetRolls();
|
||||
for (Roll*& roll : rolls)
|
||||
{
|
||||
auto voteItr = roll->playerVote.find(bot->GetGUID());
|
||||
if (voteItr == roll->playerVote.end() || voteItr->second != NOT_EMITED_YET)
|
||||
if (roll->playerVote.find(bot->GetGUID())->second != NOT_EMITED_YET)
|
||||
{
|
||||
continue;
|
||||
|
||||
}
|
||||
ObjectGuid guid = roll->itemGUID;
|
||||
uint32 itemId = roll->itemid;
|
||||
int32 randomProperty = 0;
|
||||
@ -41,22 +41,27 @@ bool LootRollAction::Execute(Event /*event*/)
|
||||
|
||||
std::string itemUsageParam;
|
||||
if (randomProperty != 0)
|
||||
{
|
||||
itemUsageParam = std::to_string(itemId) + "," + std::to_string(randomProperty);
|
||||
}
|
||||
else
|
||||
{
|
||||
itemUsageParam = std::to_string(itemId);
|
||||
|
||||
}
|
||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemUsageParam);
|
||||
|
||||
// Armor Tokens are classed as MISC JUNK (Class 15, Subclass 0), luckily no other items I found have class bits and epic quality.
|
||||
if (proto->Class == ITEM_CLASS_MISC && proto->SubClass == ITEM_SUBCLASS_JUNK && proto->Quality == ITEM_QUALITY_EPIC)
|
||||
{
|
||||
if (CanBotUseToken(proto, bot))
|
||||
{
|
||||
vote = NEED; // Eligible for "Need"
|
||||
}
|
||||
else
|
||||
{
|
||||
vote = GREED; // Not eligible, so "Greed"
|
||||
}
|
||||
}
|
||||
else if (usage == ITEM_USAGE_DISENCHANT)
|
||||
vote = sPlayerbotAIConfig.lootRollDisenchant ? DISENCHANT : GREED;
|
||||
else
|
||||
{
|
||||
switch (proto->Class)
|
||||
@ -64,34 +69,40 @@ bool LootRollAction::Execute(Event /*event*/)
|
||||
case ITEM_CLASS_WEAPON:
|
||||
case ITEM_CLASS_ARMOR:
|
||||
if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_BAD_EQUIP)
|
||||
{
|
||||
vote = NEED;
|
||||
}
|
||||
else if (usage != ITEM_USAGE_NONE)
|
||||
{
|
||||
vote = GREED;
|
||||
break;
|
||||
case ITEM_CLASS_RECIPE:
|
||||
if (!sPlayerbotAIConfig.lootRollRecipe)
|
||||
vote = PASS;
|
||||
else if (usage == ITEM_USAGE_SKILL)
|
||||
vote = NEED; // Bot can learn this recipe
|
||||
else if (proto->Bonding != BIND_WHEN_PICKED_UP)
|
||||
vote = GREED; // BoE recipe bot can't learn - GREED for AH/trade
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (StoreLootAction::IsLootAllowed(itemId, botAI))
|
||||
vote = CalculateRollVote(proto, usage);
|
||||
vote = CalculateRollVote(proto); // Ensure correct Need/Greed behavior
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (vote == NEED)
|
||||
if (sPlayerbotAIConfig.lootRollLevel == 0)
|
||||
{
|
||||
if (sPlayerbotAIConfig.lootNeedRollLevel == 0 || RollUniqueCheck(proto, bot))
|
||||
vote = PASS;
|
||||
else if (sPlayerbotAIConfig.lootNeedRollLevel == 1)
|
||||
vote = GREED;
|
||||
}
|
||||
else if (vote == GREED && !sPlayerbotAIConfig.lootGreedRollLevel)
|
||||
vote = PASS;
|
||||
|
||||
}
|
||||
else if (sPlayerbotAIConfig.lootRollLevel == 1)
|
||||
{
|
||||
// Level 1 = "greed" mode: bots greed on useful items but never need
|
||||
// Only downgrade NEED to GREED, preserve GREED votes as-is
|
||||
if (vote == NEED)
|
||||
{
|
||||
if (RollUniqueCheck(proto, bot))
|
||||
{
|
||||
vote = PASS;
|
||||
}
|
||||
else
|
||||
{
|
||||
vote = GREED;
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (group->GetLootMethod())
|
||||
{
|
||||
case MASTER_LOOT:
|
||||
@ -109,14 +120,11 @@ bool LootRollAction::Execute(Event /*event*/)
|
||||
return false;
|
||||
}
|
||||
|
||||
RollVote LootRollAction::CalculateRollVote(ItemTemplate const* proto, ItemUsage usage)
|
||||
RollVote LootRollAction::CalculateRollVote(ItemTemplate const* proto)
|
||||
{
|
||||
if (usage == ITEM_USAGE_NONE)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << proto->ItemId;
|
||||
usage = AI_VALUE2(ItemUsage, "item usage", out.str());
|
||||
}
|
||||
std::ostringstream out;
|
||||
out << proto->ItemId;
|
||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", out.str());
|
||||
|
||||
RollVote needVote = PASS;
|
||||
switch (usage)
|
||||
@ -129,13 +137,11 @@ RollVote LootRollAction::CalculateRollVote(ItemTemplate const* proto, ItemUsage
|
||||
break;
|
||||
case ITEM_USAGE_SKILL:
|
||||
case ITEM_USAGE_USE:
|
||||
case ITEM_USAGE_DISENCHANT:
|
||||
case ITEM_USAGE_AH:
|
||||
case ITEM_USAGE_VENDOR:
|
||||
needVote = GREED;
|
||||
break;
|
||||
case ITEM_USAGE_DISENCHANT:
|
||||
needVote = sPlayerbotAIConfig.lootRollDisenchant ? DISENCHANT : GREED;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -189,7 +195,9 @@ bool CanBotUseToken(ItemTemplate const* proto, Player* bot)
|
||||
|
||||
// Check if the bot's class is allowed to use the token
|
||||
if (proto->AllowableClass & botClassMask)
|
||||
{
|
||||
return true; // Bot's class is eligible to use this token
|
||||
}
|
||||
|
||||
return false; // Bot's class cannot use this token
|
||||
}
|
||||
@ -205,9 +213,13 @@ bool RollUniqueCheck(ItemTemplate const* proto, Player* bot)
|
||||
// Determine if the unique item is already equipped
|
||||
bool isEquipped = (totalItemCount > bagItemCount);
|
||||
if (isEquipped && proto->HasFlag(ITEM_FLAG_UNIQUE_EQUIPPABLE))
|
||||
{
|
||||
return true; // Unique Item is already equipped
|
||||
}
|
||||
else if (proto->HasFlag(ITEM_FLAG_UNIQUE_EQUIPPABLE) && (bagItemCount > 1))
|
||||
{
|
||||
return true; // Unique item already in bag, don't roll for it
|
||||
}
|
||||
return false; // Item is not equipped or in bags, roll for it
|
||||
}
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ public:
|
||||
bool Execute(Event event) override;
|
||||
|
||||
protected:
|
||||
RollVote CalculateRollVote(ItemTemplate const* proto, ItemUsage usage = ITEM_USAGE_NONE);
|
||||
RollVote CalculateRollVote(ItemTemplate const* proto);
|
||||
};
|
||||
|
||||
bool CanBotUseToken(ItemTemplate const* proto, Player* bot);
|
||||
|
||||
@ -1387,8 +1387,8 @@ bool MovementAction::Flee(Unit* target)
|
||||
}
|
||||
}
|
||||
|
||||
Unit* currentVictim = target->GetThreatMgr().GetCurrentVictim();
|
||||
if (currentVictim && currentVictim == bot) // bot is target - try to flee to tank or master
|
||||
HostileReference* ref = target->GetThreatMgr().getCurrentVictim();
|
||||
if (ref && ref->getTarget() == bot) // bot is target - try to flee to tank or master
|
||||
{
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
|
||||
@ -6,8 +6,7 @@
|
||||
#include "TellTargetAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "CombatManager.h"
|
||||
#include "ThreatManager.h"
|
||||
#include "ThreatMgr.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "PlayerbotAI.h"
|
||||
|
||||
@ -43,21 +42,21 @@ bool TellAttackersAction::Execute(Event /*event*/)
|
||||
|
||||
botAI->TellMaster("--- Threat ---");
|
||||
|
||||
auto const& threatenedByMe = bot->GetThreatMgr().GetThreatenedByMeList();
|
||||
if (threatenedByMe.empty())
|
||||
HostileReference* ref = bot->getHostileRefMgr().getFirst();
|
||||
if (!ref)
|
||||
return true;
|
||||
|
||||
for (auto const& [guid, ref] : threatenedByMe)
|
||||
while (ref)
|
||||
{
|
||||
Unit* unit = ref->GetOwner();
|
||||
if (!unit)
|
||||
continue;
|
||||
|
||||
ThreatMgr* threatMgr = ref->GetSource();
|
||||
Unit* unit = threatMgr->GetOwner();
|
||||
float threat = ref->GetThreat();
|
||||
|
||||
std::ostringstream out;
|
||||
out << unit->GetName() << " (" << threat << ")";
|
||||
botAI->TellMaster(out);
|
||||
|
||||
ref = ref->next();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@ -104,13 +104,6 @@ public:
|
||||
creators["target"] = &ChatTriggerContext::target;
|
||||
creators["formation"] = &ChatTriggerContext::formation;
|
||||
creators["stance"] = &ChatTriggerContext::stance;
|
||||
creators["cancel tree form"] = &ChatTriggerContext::cancel_tree_form;
|
||||
creators["cancel travel form"] = &ChatTriggerContext::cancel_travel_form;
|
||||
creators["cancel bear form"] = &ChatTriggerContext::cancel_bear_form;
|
||||
creators["cancel dire bear form"] = &ChatTriggerContext::cancel_dire_bear_form;
|
||||
creators["cancel cat form"] = &ChatTriggerContext::cancel_cat_form;
|
||||
creators["cancel moonkin form"] = &ChatTriggerContext::cancel_moonkin_form;
|
||||
creators["cancel aquatic form"] = &ChatTriggerContext::cancel_aquatic_form;
|
||||
creators["sendmail"] = &ChatTriggerContext::sendmail;
|
||||
creators["mail"] = &ChatTriggerContext::mail;
|
||||
creators["outfit"] = &ChatTriggerContext::outfit;
|
||||
@ -166,13 +159,6 @@ private:
|
||||
static Trigger* sendmail(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "sendmail"); }
|
||||
static Trigger* formation(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "formation"); }
|
||||
static Trigger* stance(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "stance"); }
|
||||
static Trigger* cancel_tree_form(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "cancel tree form"); }
|
||||
static Trigger* cancel_travel_form(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "cancel travel form"); }
|
||||
static Trigger* cancel_bear_form(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "cancel bear form"); }
|
||||
static Trigger* cancel_dire_bear_form(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "cancel dire bear form"); }
|
||||
static Trigger* cancel_cat_form(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "cancel cat form"); }
|
||||
static Trigger* cancel_moonkin_form(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "cancel moonkin form"); }
|
||||
static Trigger* cancel_aquatic_form(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "cancel aquatic form"); }
|
||||
static Trigger* attackers(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "attackers"); }
|
||||
static Trigger* target(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "target"); }
|
||||
static Trigger* max_dps(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "max dps"); }
|
||||
|
||||
@ -160,13 +160,6 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
|
||||
supported.push_back("save mana");
|
||||
supported.push_back("formation");
|
||||
supported.push_back("stance");
|
||||
supported.push_back("cancel tree form");
|
||||
supported.push_back("cancel travel form");
|
||||
supported.push_back("cancel bear form");
|
||||
supported.push_back("cancel dire bear form");
|
||||
supported.push_back("cancel cat form");
|
||||
supported.push_back("cancel moonkin form");
|
||||
supported.push_back("cancel aquatic form");
|
||||
supported.push_back("sendmail");
|
||||
supported.push_back("mail");
|
||||
supported.push_back("outfit");
|
||||
|
||||
@ -34,9 +34,6 @@ void RacialsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
NextAction("berserking", ACTION_NORMAL + 5),
|
||||
NextAction("use trinket", ACTION_NORMAL + 4) }));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"loss of control", { NextAction("every man for himself", ACTION_EMERGENCY + 1) }));
|
||||
|
||||
}
|
||||
|
||||
RacialsStrategy::RacialsStrategy(PlayerbotAI* botAI) : Strategy(botAI)
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
#include "PositionValue.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "ThreatManager.h"
|
||||
#include "ThreatMgr.h"
|
||||
#include "Timer.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Player.h"
|
||||
@ -217,7 +217,7 @@ bool LowTankThreatTrigger::IsActive()
|
||||
if (!current_target)
|
||||
return false;
|
||||
|
||||
ThreatManager& mgr = current_target->GetThreatMgr();
|
||||
ThreatMgr& mgr = current_target->GetThreatMgr();
|
||||
float threat = mgr.GetThreat(bot);
|
||||
float tankThreat = mgr.GetThreat(mt);
|
||||
return tankThreat == 0.0f || threat > tankThreat * 0.5f;
|
||||
@ -464,15 +464,6 @@ bool AttackerCountTrigger::IsActive() { return AI_VALUE(uint8, "attacker count")
|
||||
|
||||
bool HasAuraTrigger::IsActive() { return botAI->HasAura(getName(), GetTarget(), false, false, -1, true); }
|
||||
|
||||
bool LossOfControlTrigger::IsActive()
|
||||
{
|
||||
return bot->HasAuraType(SPELL_AURA_MOD_STUN) ||
|
||||
bot->HasAuraType(SPELL_AURA_MOD_FEAR) ||
|
||||
bot->HasAuraType(SPELL_AURA_MOD_ROOT) ||
|
||||
bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) ||
|
||||
bot->HasAuraType(SPELL_AURA_MOD_CHARM);
|
||||
}
|
||||
|
||||
bool HasAuraStackTrigger::IsActive()
|
||||
{
|
||||
Aura* aura = botAI->GetAura(getName(), GetTarget(), false, true, stack);
|
||||
|
||||
@ -746,14 +746,6 @@ public:
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class LossOfControlTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
LossOfControlTrigger(PlayerbotAI* botAI) : Trigger(botAI, "loss of control", 1) {}
|
||||
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class IsSwimmingTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
|
||||
@ -59,7 +59,6 @@ public:
|
||||
creators["party member almost full health"] = &TriggerContext::PartyMemberAlmostFullHealth;
|
||||
|
||||
creators["generic boost"] = &TriggerContext::generic_boost;
|
||||
creators["loss of control"] = &TriggerContext::loss_of_control;
|
||||
|
||||
creators["protect party member"] = &TriggerContext::protect_party_member;
|
||||
|
||||
@ -104,8 +103,7 @@ public:
|
||||
creators["enemy within melee"] = &TriggerContext::enemy_within_melee;
|
||||
creators["party member to heal out of spell range"] = &TriggerContext::party_member_to_heal_out_of_spell_range;
|
||||
|
||||
creators["combo points 5 available"] = &TriggerContext::ComboPoints5Available;
|
||||
creators["combo points 4 available"] = &TriggerContext::ComboPoints4Available;
|
||||
creators["combo points available"] = &TriggerContext::ComboPointsAvailable;
|
||||
creators["combo points 3 available"] = &TriggerContext::ComboPoints3Available;
|
||||
creators["target with combo points almost dead"] = &TriggerContext::target_with_combo_points_almost_dead;
|
||||
creators["combo points not full"] = &TriggerContext::ComboPointsNotFull;
|
||||
@ -340,8 +338,7 @@ private:
|
||||
{
|
||||
return new PartyMemberToHealOutOfSpellRangeTrigger(botAI);
|
||||
}
|
||||
static Trigger* ComboPoints5Available(PlayerbotAI* botAI) { return new ComboPointsAvailableTrigger(botAI, 5); }
|
||||
static Trigger* ComboPoints4Available(PlayerbotAI* botAI) { return new ComboPointsAvailableTrigger(botAI, 4); }
|
||||
static Trigger* ComboPointsAvailable(PlayerbotAI* botAI) { return new ComboPointsAvailableTrigger(botAI); }
|
||||
static Trigger* ComboPoints3Available(PlayerbotAI* botAI) { return new ComboPointsAvailableTrigger(botAI, 3); }
|
||||
static Trigger* target_with_combo_points_almost_dead(PlayerbotAI* ai)
|
||||
{
|
||||
@ -364,7 +361,6 @@ private:
|
||||
return new PartyMemberAlmostFullHealthTrigger(botAI);
|
||||
}
|
||||
static Trigger* generic_boost(PlayerbotAI* botAI) { return new GenericBoostTrigger(botAI); }
|
||||
static Trigger* loss_of_control(PlayerbotAI* botAI) { return new LossOfControlTrigger(botAI); }
|
||||
static Trigger* PartyMemberCriticalHealth(PlayerbotAI* botAI)
|
||||
{
|
||||
return new PartyMemberCriticalHealthTrigger(botAI);
|
||||
|
||||
@ -92,15 +92,21 @@ void AttackersValue::AddAttackersOf(Player* player, std::unordered_set<Unit*>& t
|
||||
if (!player || !player->IsInWorld() || player->IsBeingTeleported())
|
||||
return;
|
||||
|
||||
for (auto const& [guid, ref] : player->GetThreatMgr().GetThreatenedByMeList())
|
||||
HostileRefMgr& refManager = player->getHostileRefMgr();
|
||||
HostileReference* ref = refManager.getFirst();
|
||||
if (!ref)
|
||||
return;
|
||||
|
||||
while (ref)
|
||||
{
|
||||
Unit* attacker = ref->GetOwner();
|
||||
if (!attacker)
|
||||
continue;
|
||||
ThreatMgr* threatMgr = ref->GetSource();
|
||||
Unit* attacker = threatMgr->GetOwner();
|
||||
|
||||
if (player->IsValidAttackTarget(attacker) &&
|
||||
player->GetDistance2d(attacker) < sPlayerbotAIConfig.sightDistance)
|
||||
targets.insert(attacker);
|
||||
|
||||
ref = ref->next();
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,6 +131,7 @@ bool AttackersValue::hasRealThreat(Unit* attacker)
|
||||
return attacker && attacker->IsInWorld() && attacker->IsAlive() && !attacker->IsPolymorphed() &&
|
||||
// !attacker->isInRoots() &&
|
||||
!attacker->IsFriendlyTo(bot);
|
||||
(attacker->GetThreatMgr().getCurrentVictim() || dynamic_cast<Player*>(attacker));
|
||||
}
|
||||
|
||||
bool AttackersValue::IsPossibleTarget(Unit* attacker, Player* bot, float /*range*/)
|
||||
@ -234,6 +241,9 @@ bool AttackersValue::IsPossibleTarget(Unit* attacker, Player* bot, float /*range
|
||||
bool AttackersValue::IsValidTarget(Unit* attacker, Player* bot)
|
||||
{
|
||||
return IsPossibleTarget(attacker, bot) && bot->IsWithinLOSInMap(attacker);
|
||||
// (attacker->GetThreatMgr().getCurrentVictim() || attacker->GetGuidValue(UNIT_FIELD_TARGET) ||
|
||||
// attacker->GetGUID().IsPlayer() || attacker->GetGUID() ==
|
||||
// GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue<ObjectGuid>("pull target")->Get());
|
||||
}
|
||||
|
||||
bool PossibleAddsValue::Calculate()
|
||||
@ -245,24 +255,27 @@ bool PossibleAddsValue::Calculate()
|
||||
{
|
||||
if (find(attackers.begin(), attackers.end(), guid) != attackers.end())
|
||||
continue;
|
||||
Unit* add = botAI->GetUnit(guid);
|
||||
if (!add || !add->IsInWorld() || add->IsDuringRemoveFromWorld())
|
||||
continue;
|
||||
|
||||
if (!add->GetTarget() && !add->GetThreatMgr().GetLastVictim() && add->IsHostileTo(bot))
|
||||
if (Unit* add = botAI->GetUnit(guid))
|
||||
{
|
||||
for (ObjectGuid const attackerGUID : attackers)
|
||||
if (!add->IsInWorld() || add->IsDuringRemoveFromWorld())
|
||||
continue;
|
||||
|
||||
if (!add->GetTarget() && !add->GetThreatMgr().getCurrentVictim() && add->IsHostileTo(bot))
|
||||
{
|
||||
Unit* attacker = botAI->GetUnit(attackerGUID);
|
||||
if (!attacker)
|
||||
continue;
|
||||
for (ObjectGuid const attackerGUID : attackers)
|
||||
{
|
||||
Unit* attacker = botAI->GetUnit(attackerGUID);
|
||||
if (!attacker)
|
||||
continue;
|
||||
|
||||
float dist = ServerFacade::instance().GetDistance2d(attacker, add);
|
||||
if (ServerFacade::instance().IsDistanceLessOrEqualThan(dist, sPlayerbotAIConfig.aoeRadius * 1.5f))
|
||||
continue;
|
||||
float dist = ServerFacade::instance().GetDistance2d(attacker, add);
|
||||
if (ServerFacade::instance().IsDistanceLessOrEqualThan(dist, sPlayerbotAIConfig.aoeRadius * 1.5f))
|
||||
continue;
|
||||
|
||||
if (ServerFacade::instance().IsDistanceLessOrEqualThan(dist, sPlayerbotAIConfig.aggroDistance))
|
||||
return true;
|
||||
if (ServerFacade::instance().IsDistanceLessOrEqualThan(dist, sPlayerbotAIConfig.aggroDistance))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
void CheckAttacker(Unit* creature, ThreatManager* threatMgr) override
|
||||
void CheckAttacker(Unit* creature, ThreatMgr* threatMgr) override
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
if (!botAI->CanCastSpell(spell, creature))
|
||||
|
||||
@ -13,7 +13,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
void CheckAttacker(Unit* attacker, ThreatManager* threatMgr) override
|
||||
void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override
|
||||
{
|
||||
if (botAI->HasAura(spell, attacker))
|
||||
result = attacker;
|
||||
|
||||
@ -13,14 +13,16 @@ class FindMaxThreatGapTargetStrategy : public FindTargetStrategy
|
||||
public:
|
||||
FindMaxThreatGapTargetStrategy(PlayerbotAI* botAI) : FindTargetStrategy(botAI), minThreat(0) {}
|
||||
|
||||
void CheckAttacker(Unit* attacker, ThreatManager* threatMgr) override
|
||||
void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override
|
||||
{
|
||||
if (!attacker->IsAlive())
|
||||
{
|
||||
return;
|
||||
|
||||
}
|
||||
if (foundHighPriority)
|
||||
{
|
||||
return;
|
||||
|
||||
}
|
||||
if (IsHighPriority(attacker))
|
||||
{
|
||||
result = attacker;
|
||||
@ -30,7 +32,7 @@ public:
|
||||
if (!result || CalcThreatGap(attacker, threatMgr) > CalcThreatGap(result, &result->GetThreatMgr()))
|
||||
result = attacker;
|
||||
}
|
||||
float CalcThreatGap(Unit* attacker, ThreatManager* threatMgr)
|
||||
float CalcThreatGap(Unit* attacker, ThreatMgr* threatMgr)
|
||||
{
|
||||
Unit* victim = attacker->GetVictim();
|
||||
return threatMgr->GetThreat(victim) - threatMgr->GetThreat(attacker);
|
||||
@ -50,7 +52,7 @@ public:
|
||||
result = nullptr;
|
||||
}
|
||||
|
||||
void CheckAttacker(Unit* attacker, ThreatManager* threatMgr) override
|
||||
void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override
|
||||
{
|
||||
if (Group* group = botAI->GetBot()->GetGroup())
|
||||
{
|
||||
@ -59,11 +61,13 @@ public:
|
||||
return;
|
||||
}
|
||||
if (!attacker->IsAlive())
|
||||
{
|
||||
return;
|
||||
|
||||
}
|
||||
if (foundHighPriority)
|
||||
{
|
||||
return;
|
||||
|
||||
}
|
||||
if (IsHighPriority(attacker))
|
||||
{
|
||||
result = attacker;
|
||||
@ -86,19 +90,24 @@ public:
|
||||
int new_level = GetIntervalLevel(new_unit);
|
||||
int old_level = GetIntervalLevel(old_unit);
|
||||
if (new_level != old_level)
|
||||
{
|
||||
return new_level > old_level;
|
||||
|
||||
}
|
||||
int32_t level = new_level;
|
||||
if (level % 10 == 2 || level % 10 == 0)
|
||||
{
|
||||
return new_time < old_time;
|
||||
}
|
||||
// dont switch targets when all of them with low health
|
||||
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("current target")->Get();
|
||||
if (currentTarget == new_unit)
|
||||
{
|
||||
return true;
|
||||
|
||||
}
|
||||
if (currentTarget == old_unit)
|
||||
{
|
||||
return false;
|
||||
|
||||
}
|
||||
return new_time > old_time;
|
||||
}
|
||||
int32_t GetIntervalLevel(Unit* unit)
|
||||
@ -110,11 +119,13 @@ public:
|
||||
attackRange += 5.0f;
|
||||
int level = dis < attackRange ? 10 : 0;
|
||||
if (time >= 5 && time <= 30)
|
||||
{
|
||||
return level + 2;
|
||||
|
||||
}
|
||||
if (time > 30)
|
||||
{
|
||||
return level;
|
||||
|
||||
}
|
||||
return level + 1;
|
||||
}
|
||||
|
||||
@ -132,7 +143,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
void CheckAttacker(Unit* attacker, ThreatManager*) override
|
||||
void CheckAttacker(Unit* attacker, ThreatMgr*) override
|
||||
{
|
||||
if (Group* group = botAI->GetBot()->GetGroup())
|
||||
{
|
||||
@ -141,11 +152,13 @@ public:
|
||||
return;
|
||||
}
|
||||
if (!attacker->IsAlive())
|
||||
{
|
||||
return;
|
||||
|
||||
}
|
||||
if (foundHighPriority)
|
||||
{
|
||||
return;
|
||||
|
||||
}
|
||||
if (IsHighPriority(attacker))
|
||||
{
|
||||
result = attacker;
|
||||
@ -173,8 +186,9 @@ public:
|
||||
// attack enemy in range and with lowest health
|
||||
int level = new_level;
|
||||
if (level == 10)
|
||||
{
|
||||
return new_time < old_time;
|
||||
|
||||
}
|
||||
// all targets are far away, choose the closest one
|
||||
return botAI->GetBot()->GetDistance(new_unit) < botAI->GetBot()->GetDistance(old_unit);
|
||||
}
|
||||
@ -202,7 +216,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
void CheckAttacker(Unit* attacker, ThreatManager*) override
|
||||
void CheckAttacker(Unit* attacker, ThreatMgr*) override
|
||||
{
|
||||
if (Group* group = botAI->GetBot()->GetGroup())
|
||||
{
|
||||
@ -211,11 +225,13 @@ public:
|
||||
return;
|
||||
}
|
||||
if (!attacker->IsAlive())
|
||||
{
|
||||
return;
|
||||
|
||||
}
|
||||
if (foundHighPriority)
|
||||
{
|
||||
return;
|
||||
|
||||
}
|
||||
if (IsHighPriority(attacker))
|
||||
{
|
||||
result = attacker;
|
||||
@ -238,8 +254,9 @@ public:
|
||||
int new_level = GetIntervalLevel(new_unit);
|
||||
int old_level = GetIntervalLevel(old_unit);
|
||||
if (new_level != old_level)
|
||||
{
|
||||
return new_level > old_level;
|
||||
|
||||
}
|
||||
// attack enemy in range and with lowest health
|
||||
int level = new_level;
|
||||
Player* bot = botAI->GetBot();
|
||||
@ -247,8 +264,9 @@ public:
|
||||
{
|
||||
Unit* combo_unit = bot->GetComboTarget();
|
||||
if (new_unit == combo_unit)
|
||||
{
|
||||
return true;
|
||||
|
||||
}
|
||||
return new_time < old_time;
|
||||
}
|
||||
// all targets are far away, choose the closest one
|
||||
@ -301,7 +319,7 @@ class FindMaxHpTargetStrategy : public FindTargetStrategy
|
||||
public:
|
||||
FindMaxHpTargetStrategy(PlayerbotAI* botAI) : FindTargetStrategy(botAI), maxHealth(0) {}
|
||||
|
||||
void CheckAttacker(Unit* attacker, ThreatManager*) override
|
||||
void CheckAttacker(Unit* attacker, ThreatMgr*) override
|
||||
{
|
||||
if (Group* group = botAI->GetBot()->GetGroup())
|
||||
{
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
|
||||
#include "EnemyPlayerValue.h"
|
||||
|
||||
#include "CombatManager.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "Vehicle.h"
|
||||
@ -52,21 +51,34 @@ Unit* EnemyPlayerValue::Calculate()
|
||||
controllingVehicle = true;
|
||||
}
|
||||
|
||||
// 1. Check units we are currently in PvP combat with.
|
||||
// 1. Check units we are currently in combat with.
|
||||
std::vector<Unit*> targets;
|
||||
Unit* pVictim = bot->GetVictim();
|
||||
for (auto const& [guid, combatRef] : bot->GetCombatManager().GetPvPCombatRefs())
|
||||
HostileReference* pReference = bot->getHostileRefMgr().getFirst();
|
||||
while (pReference)
|
||||
{
|
||||
Unit* pTarget = combatRef->GetOther(bot);
|
||||
if (!pTarget || pTarget == pVictim || !pTarget->IsPlayer() || !pTarget->CanSeeOrDetect(bot) ||
|
||||
!bot->IsWithinDist(pTarget, VISIBILITY_DISTANCE_NORMAL))
|
||||
continue;
|
||||
ThreatMgr* threatMgr = pReference->GetSource();
|
||||
if (Unit* pTarget = threatMgr->GetOwner())
|
||||
{
|
||||
if (pTarget != pVictim && pTarget->IsPlayer() && pTarget->CanSeeOrDetect(bot) &&
|
||||
bot->IsWithinDist(pTarget, VISIBILITY_DISTANCE_NORMAL))
|
||||
{
|
||||
if (bot->GetTeamId() == TEAM_HORDE)
|
||||
{
|
||||
if (pTarget->HasAura(23333))
|
||||
return pTarget;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pTarget->HasAura(23335))
|
||||
return pTarget;
|
||||
}
|
||||
|
||||
if ((bot->GetTeamId() == TEAM_HORDE && pTarget->HasAura(23333)) ||
|
||||
(bot->GetTeamId() == TEAM_ALLIANCE && pTarget->HasAura(23335)))
|
||||
return pTarget;
|
||||
targets.push_back(pTarget);
|
||||
}
|
||||
}
|
||||
|
||||
targets.push_back(pTarget);
|
||||
pReference = pReference->next();
|
||||
}
|
||||
|
||||
if (!targets.empty())
|
||||
|
||||
@ -153,8 +153,9 @@ ItemUsage ItemUsageValue::Calculate()
|
||||
// Need to add something like free bagspace or item value.
|
||||
if (proto->SellPrice > 0)
|
||||
{
|
||||
if (proto->Quality >= ITEM_QUALITY_NORMAL && !isSoulbound && proto->Bonding != BIND_WHEN_PICKED_UP)
|
||||
if (proto->Quality >= ITEM_QUALITY_NORMAL && !isSoulbound)
|
||||
return ITEM_USAGE_AH;
|
||||
|
||||
else
|
||||
return ITEM_USAGE_VENDOR;
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ class FindLeastHpTargetStrategy : public FindNonCcTargetStrategy
|
||||
public:
|
||||
FindLeastHpTargetStrategy(PlayerbotAI* botAI) : FindNonCcTargetStrategy(botAI), minHealth(0) {}
|
||||
|
||||
void CheckAttacker(Unit* attacker, ThreatManager* threatMgr) override
|
||||
void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override
|
||||
{
|
||||
if (IsCcTarget(attacker))
|
||||
return;
|
||||
|
||||
@ -15,11 +15,12 @@ class FindTargetForTankStrategy : public FindNonCcTargetStrategy
|
||||
public:
|
||||
FindTargetForTankStrategy(PlayerbotAI* botAI) : FindNonCcTargetStrategy(botAI), minThreat(0) {}
|
||||
|
||||
void CheckAttacker(Unit* creature, ThreatManager* threatMgr) override
|
||||
void CheckAttacker(Unit* creature, ThreatMgr* threatMgr) override
|
||||
{
|
||||
if (!creature || !creature->IsAlive())
|
||||
{
|
||||
return;
|
||||
|
||||
}
|
||||
Player* bot = botAI->GetBot();
|
||||
float threat = threatMgr->GetThreat(bot);
|
||||
if (!result)
|
||||
@ -28,10 +29,14 @@ public:
|
||||
result = creature;
|
||||
}
|
||||
// neglect if victim is main tank, or no victim (for untauntable target)
|
||||
if (Unit* victim = threatMgr->GetCurrentVictim())
|
||||
if (threatMgr->getCurrentVictim())
|
||||
{
|
||||
if (victim->ToPlayer() && botAI->IsMainTank(victim->ToPlayer()))
|
||||
// float max_threat = threatMgr->GetThreat(threatMgr->getCurrentVictim()->getTarget());
|
||||
Unit* victim = threatMgr->getCurrentVictim()->getTarget();
|
||||
if (victim && victim->ToPlayer() && botAI->IsMainTank(victim->ToPlayer()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (minThreat >= threat)
|
||||
{
|
||||
@ -49,7 +54,7 @@ class FindTankTargetSmartStrategy : public FindTargetStrategy
|
||||
public:
|
||||
FindTankTargetSmartStrategy(PlayerbotAI* botAI) : FindTargetStrategy(botAI) {}
|
||||
|
||||
void CheckAttacker(Unit* attacker, ThreatManager* threatMgr) override
|
||||
void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override
|
||||
{
|
||||
if (Group* group = botAI->GetBot()->GetGroup())
|
||||
{
|
||||
@ -58,10 +63,13 @@ public:
|
||||
return;
|
||||
}
|
||||
if (!attacker->IsAlive())
|
||||
{
|
||||
return;
|
||||
|
||||
}
|
||||
if (!result || IsBetter(attacker, result))
|
||||
{
|
||||
result = attacker;
|
||||
}
|
||||
}
|
||||
bool IsBetter(Unit* new_unit, Unit* old_unit)
|
||||
{
|
||||
@ -72,7 +80,6 @@ public:
|
||||
{
|
||||
if (old_unit == currentTarget)
|
||||
return false;
|
||||
|
||||
if (new_unit == currentTarget)
|
||||
return true;
|
||||
}
|
||||
@ -82,22 +89,26 @@ public:
|
||||
float old_dis = bot->GetDistance(old_unit);
|
||||
// hasAggro? -> withinMelee? -> threat
|
||||
if (GetIntervalLevel(new_unit) != GetIntervalLevel(old_unit))
|
||||
{
|
||||
return GetIntervalLevel(new_unit) > GetIntervalLevel(old_unit);
|
||||
|
||||
}
|
||||
int32_t interval = GetIntervalLevel(new_unit);
|
||||
if (interval == 2)
|
||||
{
|
||||
return new_dis < old_dis;
|
||||
|
||||
}
|
||||
return new_threat < old_threat;
|
||||
}
|
||||
int32_t GetIntervalLevel(Unit* unit)
|
||||
{
|
||||
if (!botAI->HasAggro(unit))
|
||||
{
|
||||
return 2;
|
||||
|
||||
}
|
||||
if (botAI->GetBot()->IsWithinMeleeRange(unit))
|
||||
{
|
||||
return 1;
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
@ -5,13 +5,12 @@
|
||||
|
||||
#include "TargetValue.h"
|
||||
|
||||
#include "CombatManager.h"
|
||||
#include "LastMovementValue.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "Playerbots.h"
|
||||
#include "RtiTargetValue.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "ThreatManager.h"
|
||||
#include "ThreatMgr.h"
|
||||
|
||||
Unit* FindTargetStrategy::GetResult() { return result; }
|
||||
|
||||
@ -24,8 +23,8 @@ Unit* TargetValue::FindTarget(FindTargetStrategy* strategy)
|
||||
if (!unit)
|
||||
continue;
|
||||
|
||||
ThreatManager& threatMgr = unit->GetThreatMgr();
|
||||
strategy->CheckAttacker(unit, &threatMgr);
|
||||
ThreatMgr& ThreatMgr = unit->GetThreatMgr();
|
||||
strategy->CheckAttacker(unit, &ThreatMgr);
|
||||
}
|
||||
|
||||
return strategy->GetResult();
|
||||
@ -145,23 +144,24 @@ Unit* FindTargetValue::Calculate()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
for (auto const& [guid, ref] : bot->GetThreatMgr().GetThreatenedByMeList())
|
||||
HostileReference* ref = bot->getHostileRefMgr().getFirst();
|
||||
while (ref)
|
||||
{
|
||||
Unit* unit = ref->GetOwner();
|
||||
if (!unit)
|
||||
continue;
|
||||
|
||||
ThreatMgr* threatManager = ref->GetSource();
|
||||
Unit* unit = threatManager->GetOwner();
|
||||
std::wstring wnamepart;
|
||||
Utf8toWStr(unit->GetName(), wnamepart);
|
||||
wstrToLower(wnamepart);
|
||||
if (!qualifier.empty() && qualifier.length() == wnamepart.length() && Utf8FitTo(qualifier, wnamepart))
|
||||
{
|
||||
return unit;
|
||||
}
|
||||
ref = ref->next();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void FindBossTargetStrategy::CheckAttacker(Unit* attacker, ThreatManager* threatManager)
|
||||
void FindBossTargetStrategy::CheckAttacker(Unit* attacker, ThreatMgr* threatManager)
|
||||
{
|
||||
UnitAI* unitAI = attacker->GetAI();
|
||||
BossAI* bossAI = dynamic_cast<BossAI*>(unitAI);
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
#include "Value.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
class ThreatManager;
|
||||
class ThreatMgr;
|
||||
class Unit;
|
||||
|
||||
class FindTargetStrategy
|
||||
@ -20,7 +20,7 @@ public:
|
||||
FindTargetStrategy(PlayerbotAI* botAI) : result(nullptr), botAI(botAI) {}
|
||||
|
||||
Unit* GetResult();
|
||||
virtual void CheckAttacker(Unit* attacker, ThreatManager* threatMgr) = 0;
|
||||
virtual void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) = 0;
|
||||
void GetPlayerCount(Unit* creature, uint32* tankCount, uint32* dpsCount);
|
||||
bool IsHighPriority(Unit* attacker);
|
||||
|
||||
@ -129,7 +129,7 @@ class FindBossTargetStrategy : public FindTargetStrategy
|
||||
{
|
||||
public:
|
||||
FindBossTargetStrategy(PlayerbotAI* ai) : FindTargetStrategy(ai) {}
|
||||
virtual void CheckAttacker(Unit* attacker, ThreatManager* threatManager);
|
||||
virtual void CheckAttacker(Unit* attacker, ThreatMgr* threatManager);
|
||||
};
|
||||
|
||||
class BossTargetValue : public TargetValue, public Qualified
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
#include "ThreatValues.h"
|
||||
|
||||
#include "Playerbots.h"
|
||||
#include "ThreatManager.h"
|
||||
#include "ThreatMgr.h"
|
||||
|
||||
uint8 ThreatValue::Calculate()
|
||||
{
|
||||
|
||||
@ -44,13 +44,13 @@ bool CastCasterFormAction::isUseful()
|
||||
AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig.mediumHealth;
|
||||
}
|
||||
|
||||
bool CastCancelDruidAction::Execute(Event /*event*/)
|
||||
bool CastCancelTreeFormAction::Execute(Event /*event*/)
|
||||
{
|
||||
botAI->RemoveAura(auraName);
|
||||
botAI->RemoveAura("tree of life");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CastCancelDruidAction::isUseful() { return botAI->HasAura(auraId, bot); }
|
||||
bool CastCancelTreeFormAction::isUseful() { return botAI->HasAura(33891, bot); }
|
||||
|
||||
bool CastTreeFormAction::isUseful()
|
||||
{
|
||||
|
||||
@ -71,78 +71,14 @@ public:
|
||||
bool isPossible() override { return true; }
|
||||
};
|
||||
|
||||
class CastCancelDruidAction : public CastBuffSpellAction
|
||||
class CastCancelTreeFormAction : public CastBuffSpellAction
|
||||
{
|
||||
public:
|
||||
CastCancelDruidAction(PlayerbotAI* botAI, std::string const& actionName, std::string const& auraName, uint32 auraId)
|
||||
: CastBuffSpellAction(botAI, actionName), auraName(auraName), auraId(auraId)
|
||||
{
|
||||
}
|
||||
CastCancelTreeFormAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "cancel tree form") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
bool isPossible() override { return true; }
|
||||
|
||||
private:
|
||||
std::string auraName;
|
||||
uint32 auraId;
|
||||
};
|
||||
|
||||
class CastCancelTreeFormAction : public CastCancelDruidAction
|
||||
{
|
||||
public:
|
||||
CastCancelTreeFormAction(PlayerbotAI* botAI)
|
||||
: CastCancelDruidAction(botAI, "cancel tree form", "tree of life", 33891)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class CastCancelTravelFormAction : public CastCancelDruidAction
|
||||
{
|
||||
public:
|
||||
CastCancelTravelFormAction(PlayerbotAI* botAI)
|
||||
: CastCancelDruidAction(botAI, "cancel travel form", "travel form", 783)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class CastCancelBearFormAction : public CastCancelDruidAction
|
||||
{
|
||||
public:
|
||||
CastCancelBearFormAction(PlayerbotAI* botAI) : CastCancelDruidAction(botAI, "cancel bear form", "bear form", 5487) {}
|
||||
};
|
||||
|
||||
class CastCancelDireBearFormAction : public CastCancelDruidAction
|
||||
{
|
||||
public:
|
||||
CastCancelDireBearFormAction(PlayerbotAI* botAI)
|
||||
: CastCancelDruidAction(botAI, "cancel dire bear form", "dire bear form", 9634)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class CastCancelCatFormAction : public CastCancelDruidAction
|
||||
{
|
||||
public:
|
||||
CastCancelCatFormAction(PlayerbotAI* botAI) : CastCancelDruidAction(botAI, "cancel cat form", "cat form", 768) {}
|
||||
};
|
||||
|
||||
class CastCancelMoonkinFormAction : public CastCancelDruidAction
|
||||
{
|
||||
public:
|
||||
CastCancelMoonkinFormAction(PlayerbotAI* botAI)
|
||||
: CastCancelDruidAction(botAI, "cancel moonkin form", "moonkin form", 24858)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class CastCancelAquaticFormAction : public CastCancelDruidAction
|
||||
{
|
||||
public:
|
||||
CastCancelAquaticFormAction(PlayerbotAI* botAI)
|
||||
: CastCancelDruidAction(botAI, "cancel aquatic form", "aquatic form", 1066)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -170,12 +170,6 @@ public:
|
||||
creators["aquatic form"] = &DruidAiObjectContextInternal::aquatic_form;
|
||||
creators["caster form"] = &DruidAiObjectContextInternal::caster_form;
|
||||
creators["cancel tree form"] = &DruidAiObjectContextInternal::cancel_tree_form;
|
||||
creators["cancel travel form"] = &DruidAiObjectContextInternal::cancel_travel_form;
|
||||
creators["cancel bear form"] = &DruidAiObjectContextInternal::cancel_bear_form;
|
||||
creators["cancel dire bear form"] = &DruidAiObjectContextInternal::cancel_dire_bear_form;
|
||||
creators["cancel cat form"] = &DruidAiObjectContextInternal::cancel_cat_form;
|
||||
creators["cancel moonkin form"] = &DruidAiObjectContextInternal::cancel_moonkin_form;
|
||||
creators["cancel aquatic form"] = &DruidAiObjectContextInternal::cancel_aquatic_form;
|
||||
creators["mangle (bear)"] = &DruidAiObjectContextInternal::mangle_bear;
|
||||
creators["maul"] = &DruidAiObjectContextInternal::maul;
|
||||
creators["bash"] = &DruidAiObjectContextInternal::bash;
|
||||
@ -264,12 +258,6 @@ private:
|
||||
static Action* aquatic_form(PlayerbotAI* botAI) { return new CastAquaticFormAction(botAI); }
|
||||
static Action* caster_form(PlayerbotAI* botAI) { return new CastCasterFormAction(botAI); }
|
||||
static Action* cancel_tree_form(PlayerbotAI* botAI) { return new CastCancelTreeFormAction(botAI); }
|
||||
static Action* cancel_travel_form(PlayerbotAI* botAI) { return new CastCancelTravelFormAction(botAI); }
|
||||
static Action* cancel_bear_form(PlayerbotAI* botAI) { return new CastCancelBearFormAction(botAI); }
|
||||
static Action* cancel_dire_bear_form(PlayerbotAI* botAI) { return new CastCancelDireBearFormAction(botAI); }
|
||||
static Action* cancel_cat_form(PlayerbotAI* botAI) { return new CastCancelCatFormAction(botAI); }
|
||||
static Action* cancel_moonkin_form(PlayerbotAI* botAI) { return new CastCancelMoonkinFormAction(botAI); }
|
||||
static Action* cancel_aquatic_form(PlayerbotAI* botAI) { return new CastCancelAquaticFormAction(botAI); }
|
||||
static Action* mangle_bear(PlayerbotAI* botAI) { return new CastMangleBearAction(botAI); }
|
||||
static Action* maul(PlayerbotAI* botAI) { return new CastMaulAction(botAI); }
|
||||
static Action* bash(PlayerbotAI* botAI) { return new CastBashAction(botAI); }
|
||||
|
||||
@ -228,7 +228,7 @@ void CatDpsDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"combo points 5 available",
|
||||
"combo points available",
|
||||
{
|
||||
NextAction("rip", ACTION_HIGH + 6)
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ void OffhealDruidCatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"combo points 5 available",
|
||||
"combo points available",
|
||||
{
|
||||
NextAction("rip", ACTION_HIGH + 6)
|
||||
}
|
||||
@ -257,7 +257,7 @@ void OffhealDruidCatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
);
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"tiger's fury",
|
||||
"low energy",
|
||||
{
|
||||
NextAction("tiger's fury", ACTION_NORMAL + 1)
|
||||
}
|
||||
|
||||
@ -472,8 +472,9 @@ Unit* CastRighteousDefenseAction::GetTarget()
|
||||
{
|
||||
Unit* current_target = AI_VALUE(Unit*, "current target");
|
||||
if (!current_target)
|
||||
return nullptr;
|
||||
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return current_target->GetVictim();
|
||||
}
|
||||
|
||||
|
||||
@ -91,8 +91,9 @@ public:
|
||||
class CastBlessingOnPartyAction : public BuffOnPartyAction
|
||||
{
|
||||
public:
|
||||
CastBlessingOnPartyAction(PlayerbotAI* botAI, std::string const name)
|
||||
: BuffOnPartyAction(botAI, name), name(name) {}
|
||||
CastBlessingOnPartyAction(PlayerbotAI* botAI, std::string const name) : BuffOnPartyAction(botAI, name), name(name)
|
||||
{
|
||||
}
|
||||
|
||||
Value<Unit*>* GetTargetValue() override;
|
||||
|
||||
@ -153,7 +154,9 @@ public:
|
||||
class CastBlessingOfSanctuaryOnPartyAction : public BuffOnPartyAction
|
||||
{
|
||||
public:
|
||||
CastBlessingOfSanctuaryOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "blessing of sanctuary") {}
|
||||
CastBlessingOfSanctuaryOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "blessing of sanctuary")
|
||||
{
|
||||
}
|
||||
|
||||
std::string const getName() override { return "blessing of sanctuary on party"; }
|
||||
Value<Unit*>* GetTargetValue() override;
|
||||
@ -170,14 +173,18 @@ class CastHolyShockOnPartyAction : public HealPartyMemberAction
|
||||
{
|
||||
public:
|
||||
CastHolyShockOnPartyAction(PlayerbotAI* botAI)
|
||||
: HealPartyMemberAction(botAI, "holy shock", 25.0f, HealingManaEfficiency::LOW) {}
|
||||
: HealPartyMemberAction(botAI, "holy shock", 25.0f, HealingManaEfficiency::LOW)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class CastHolyLightOnPartyAction : public HealPartyMemberAction
|
||||
{
|
||||
public:
|
||||
CastHolyLightOnPartyAction(PlayerbotAI* botAI)
|
||||
: HealPartyMemberAction(botAI, "holy light", 50.0f, HealingManaEfficiency::MEDIUM) {}
|
||||
: HealPartyMemberAction(botAI, "holy light", 50.0f, HealingManaEfficiency::MEDIUM)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class CastFlashOfLightAction : public CastHealingSpellAction
|
||||
@ -190,7 +197,9 @@ class CastFlashOfLightOnPartyAction : public HealPartyMemberAction
|
||||
{
|
||||
public:
|
||||
CastFlashOfLightOnPartyAction(PlayerbotAI* botAI)
|
||||
: HealPartyMemberAction(botAI, "flash of light", 15.0f, HealingManaEfficiency::HIGH) {}
|
||||
: HealPartyMemberAction(botAI, "flash of light", 15.0f, HealingManaEfficiency::HIGH)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class CastLayOnHandsAction : public CastHealingSpellAction
|
||||
@ -348,7 +357,9 @@ class CastHammerOfJusticeOnEnemyHealerAction : public CastSpellOnEnemyHealerActi
|
||||
{
|
||||
public:
|
||||
CastHammerOfJusticeOnEnemyHealerAction(PlayerbotAI* botAI)
|
||||
: CastSpellOnEnemyHealerAction(botAI, "hammer of justice") {}
|
||||
: CastSpellOnEnemyHealerAction(botAI, "hammer of justice")
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class CastHammerOfJusticeSnareAction : public CastSnareSpellAction
|
||||
@ -357,12 +368,6 @@ public:
|
||||
CastHammerOfJusticeSnareAction(PlayerbotAI* botAI) : CastSnareSpellAction(botAI, "hammer of justice") {}
|
||||
};
|
||||
|
||||
class CastSenseUndeadAction : public CastBuffSpellAction
|
||||
{
|
||||
public:
|
||||
CastSenseUndeadAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "sense undead") {}
|
||||
};
|
||||
|
||||
class CastTurnUndeadAction : public CastBuffSpellAction
|
||||
{
|
||||
public:
|
||||
@ -376,25 +381,25 @@ PROTECT_ACTION(CastBlessingOfProtectionProtectAction, "blessing of protection");
|
||||
class CastDivinePleaAction : public CastBuffSpellAction
|
||||
{
|
||||
public:
|
||||
CastDivinePleaAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "divine plea") {}
|
||||
CastDivinePleaAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "divine plea") {}
|
||||
};
|
||||
|
||||
class ShieldOfRighteousnessAction : public CastMeleeSpellAction
|
||||
{
|
||||
public:
|
||||
ShieldOfRighteousnessAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "shield of righteousness") {}
|
||||
ShieldOfRighteousnessAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "shield of righteousness") {}
|
||||
};
|
||||
|
||||
class CastBeaconOfLightOnMainTankAction : public BuffOnMainTankAction
|
||||
{
|
||||
public:
|
||||
CastBeaconOfLightOnMainTankAction(PlayerbotAI* botAI) : BuffOnMainTankAction(botAI, "beacon of light", true) {}
|
||||
CastBeaconOfLightOnMainTankAction(PlayerbotAI* ai) : BuffOnMainTankAction(ai, "beacon of light", true) {}
|
||||
};
|
||||
|
||||
class CastSacredShieldOnMainTankAction : public BuffOnMainTankAction
|
||||
{
|
||||
public:
|
||||
CastSacredShieldOnMainTankAction(PlayerbotAI* botAI) : BuffOnMainTankAction(botAI, "sacred shield", false) {}
|
||||
CastSacredShieldOnMainTankAction(PlayerbotAI* ai) : BuffOnMainTankAction(ai, "sacred shield", false) {}
|
||||
};
|
||||
|
||||
class CastAvengingWrathAction : public CastBuffSpellAction
|
||||
@ -423,5 +428,4 @@ public:
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -132,7 +132,6 @@ public:
|
||||
&PaladinTriggerFactoryInternal::hammer_of_justice_on_enemy_target;
|
||||
creators["hammer of justice on snare target"] =
|
||||
&PaladinTriggerFactoryInternal::hammer_of_justice_on_snare_target;
|
||||
creators["not sensing undead"] = &PaladinTriggerFactoryInternal::not_sensing_undead;
|
||||
creators["divine favor"] = &PaladinTriggerFactoryInternal::divine_favor;
|
||||
creators["turn undead"] = &PaladinTriggerFactoryInternal::turn_undead;
|
||||
creators["avenger's shield"] = &PaladinTriggerFactoryInternal::avenger_shield;
|
||||
@ -152,7 +151,6 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
static Trigger* not_sensing_undead(PlayerbotAI* botAI) { return new NotSensingUndeadTrigger(botAI); }
|
||||
static Trigger* turn_undead(PlayerbotAI* botAI) { return new TurnUndeadTrigger(botAI); }
|
||||
static Trigger* divine_favor(PlayerbotAI* botAI) { return new DivineFavorTrigger(botAI); }
|
||||
static Trigger* holy_shield(PlayerbotAI* botAI) { return new HolyShieldTrigger(botAI); }
|
||||
@ -290,7 +288,6 @@ public:
|
||||
creators["hammer of justice on snare target"] =
|
||||
&PaladinAiObjectContextInternal::hammer_of_justice_on_snare_target;
|
||||
creators["divine favor"] = &PaladinAiObjectContextInternal::divine_favor;
|
||||
creators["sense undead"] = &PaladinAiObjectContextInternal::sense_undead;
|
||||
creators["turn undead"] = &PaladinAiObjectContextInternal::turn_undead;
|
||||
creators["blessing of protection on party"] = &PaladinAiObjectContextInternal::blessing_of_protection_on_party;
|
||||
creators["righteous defense"] = &PaladinAiObjectContextInternal::righteous_defense;
|
||||
@ -315,7 +312,6 @@ private:
|
||||
{
|
||||
return new CastBlessingOfProtectionProtectAction(botAI);
|
||||
}
|
||||
static Action* sense_undead(PlayerbotAI* botAI) { return new CastSenseUndeadAction(botAI); }
|
||||
static Action* turn_undead(PlayerbotAI* botAI) { return new CastTurnUndeadAction(botAI); }
|
||||
static Action* divine_favor(PlayerbotAI* botAI) { return new CastDivineFavorAction(botAI); }
|
||||
static Action* righteous_fury(PlayerbotAI* botAI) { return new CastRighteousFuryAction(botAI); }
|
||||
|
||||
@ -15,6 +15,9 @@ public:
|
||||
{
|
||||
creators["sanctity aura"] = &sanctity_aura;
|
||||
creators["retribution aura"] = &retribution_aura;
|
||||
creators["seal of corruption"] = &seal_of_corruption;
|
||||
creators["seal of vengeance"] = &seal_of_vengeance;
|
||||
creators["seal of command"] = &seal_of_command;
|
||||
creators["blessing of might"] = &blessing_of_might;
|
||||
creators["crusader strike"] = &crusader_strike;
|
||||
creators["repentance"] = &repentance;
|
||||
@ -24,6 +27,36 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
static ActionNode* seal_of_corruption([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode(
|
||||
"seal of corruption",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("seal of vengeance") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* seal_of_vengeance([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode(
|
||||
"seal of vengeance",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("seal of command") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* seal_of_command([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode(
|
||||
"seal of command",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("seal of righteousness") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
|
||||
static ActionNode* blessing_of_might([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode(
|
||||
|
||||
@ -19,15 +19,14 @@ void GenericPaladinNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& tr
|
||||
NonCombatStrategy::InitTriggers(triggers);
|
||||
|
||||
triggers.push_back(new TriggerNode("party member dead", { NextAction("redemption", ACTION_CRITICAL_HEAL + 10) }));
|
||||
triggers.push_back(new TriggerNode("party member almost full health", { NextAction("flash of light on party", ACTION_MEDIUM_HEAL + 5.0f) }));
|
||||
triggers.push_back(new TriggerNode("party member medium health", { NextAction("flash of light on party", ACTION_MEDIUM_HEAL + 6.0f) }));
|
||||
triggers.push_back(new TriggerNode("party member low health", { NextAction("holy light on party", ACTION_MEDIUM_HEAL + 7.0f) }));
|
||||
triggers.push_back(new TriggerNode("party member critical health", { NextAction("holy light on party", ACTION_MEDIUM_HEAL + 8.0f) }));
|
||||
triggers.push_back(new TriggerNode("not sensing undead", { NextAction("sense undead", ACTION_IDLE + 1.0f) }));
|
||||
triggers.push_back(new TriggerNode("party member almost full health", { NextAction("flash of light on party", 25.0f) }));
|
||||
triggers.push_back(new TriggerNode("party member medium health", { NextAction("flash of light on party", 26.0f) }));
|
||||
triggers.push_back(new TriggerNode("party member low health", { NextAction("holy light on party", 27.0f) }));
|
||||
triggers.push_back(new TriggerNode("party member critical health", { NextAction("holy light on party", 28.0f) }));
|
||||
|
||||
int specTab = AiFactory::GetPlayerSpecTab(botAI->GetBot());
|
||||
if (specTab == PALADIN_TAB_HOLY || specTab == PALADIN_TAB_PROTECTION)
|
||||
triggers.push_back(new TriggerNode("often", { NextAction("apply oil", ACTION_IDLE + 1.0f) }));
|
||||
if (specTab == PALADIN_TAB_RETRIBUTION)
|
||||
triggers.push_back(new TriggerNode("often", { NextAction("apply stone", ACTION_IDLE + 1.0f) }));
|
||||
if (specTab == 0 || specTab == 1) // Holy or Protection
|
||||
triggers.push_back(new TriggerNode("often", { NextAction("apply oil", 1.0f) }));
|
||||
if (specTab == 2) // Retribution
|
||||
triggers.push_back(new TriggerNode("often", { NextAction("apply stone", 1.0f) }));
|
||||
}
|
||||
|
||||
@ -22,9 +22,6 @@ public:
|
||||
creators["cleanse magic"] = &cleanse_magic;
|
||||
creators["cleanse poison on party"] = &cleanse_poison_on_party;
|
||||
creators["cleanse disease on party"] = &cleanse_disease_on_party;
|
||||
creators["seal of corruption"] = &seal_of_corruption;
|
||||
creators["seal of vengeance"] = &seal_of_vengeance;
|
||||
creators["seal of command"] = &seal_of_command;
|
||||
creators["seal of wisdom"] = &seal_of_wisdom;
|
||||
creators["seal of justice"] = &seal_of_justice;
|
||||
creators["hand of reckoning"] = &hand_of_reckoning;
|
||||
@ -44,6 +41,7 @@ public:
|
||||
creators["blessing of wisdom on party"] = &blessing_of_wisdom_on_party;
|
||||
creators["blessing of sanctuary on party"] = &blessing_of_sanctuary_on_party;
|
||||
creators["blessing of sanctuary"] = &blessing_of_sanctuary;
|
||||
creators["seal of command"] = &seal_of_command;
|
||||
creators["taunt spell"] = &hand_of_reckoning;
|
||||
creators["righteous defense"] = &righteous_defense;
|
||||
creators["avenger's shield"] = &avengers_shield;
|
||||
@ -157,39 +155,18 @@ private:
|
||||
/*A*/ { NextAction("purify disease on party") },
|
||||
/*C*/ {});
|
||||
}
|
||||
static ActionNode* seal_of_corruption(PlayerbotAI* /* ai */)
|
||||
{
|
||||
return new ActionNode("seal of corruption",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("seal of vengeance") },
|
||||
/*C*/ {});
|
||||
}
|
||||
static ActionNode* seal_of_vengeance(PlayerbotAI* /* ai */)
|
||||
{
|
||||
return new ActionNode("seal of vengeance",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("seal of command") },
|
||||
/*C*/ {});
|
||||
}
|
||||
static ActionNode* seal_of_command(PlayerbotAI* /* ai */)
|
||||
{
|
||||
return new ActionNode("seal of command",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("seal of righteousness") },
|
||||
/*C*/ {});
|
||||
}
|
||||
static ActionNode* seal_of_wisdom(PlayerbotAI* /* ai */)
|
||||
{
|
||||
return new ActionNode ("seal of wisdom",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("seal of corruption") },
|
||||
/*A*/ { NextAction("seal of righteousness") },
|
||||
/*C*/ {});
|
||||
}
|
||||
static ActionNode* seal_of_justice(PlayerbotAI* /* ai */)
|
||||
{
|
||||
return new ActionNode("seal of justice",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("seal of corruption") },
|
||||
/*A*/ { NextAction("seal of righteousness") },
|
||||
/*C*/ {});
|
||||
}
|
||||
static ActionNode* hand_of_reckoning(PlayerbotAI* /* ai */)
|
||||
@ -269,6 +246,13 @@ private:
|
||||
/*A*/ {},
|
||||
/*C*/ {});
|
||||
}
|
||||
static ActionNode* seal_of_command(PlayerbotAI* /* ai */)
|
||||
{
|
||||
return new ActionNode("seal of command",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("seal of righteousness") },
|
||||
/*C*/ {});
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -30,7 +30,7 @@ void HealPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
new TriggerNode(
|
||||
"seal",
|
||||
{
|
||||
NextAction("seal of wisdom", ACTION_HIGH),
|
||||
NextAction("seal of wisdom", ACTION_HIGH)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
@ -30,8 +30,3 @@ bool BlessingTrigger::IsActive()
|
||||
return SpellTrigger::IsActive() && !botAI->HasAnyAuraOf(target, "blessing of might", "blessing of wisdom",
|
||||
"blessing of kings", "blessing of sanctuary", nullptr);
|
||||
}
|
||||
|
||||
bool NotSensingUndeadTrigger::IsActive()
|
||||
{
|
||||
return !botAI->HasAura("sense undead", bot);
|
||||
}
|
||||
|
||||
@ -77,7 +77,9 @@ class BlessingOnPartyTrigger : public BuffOnPartyTrigger
|
||||
{
|
||||
public:
|
||||
BlessingOnPartyTrigger(PlayerbotAI* botAI)
|
||||
: BuffOnPartyTrigger(botAI, "blessing of kings,blessing of might,blessing of wisdom", 2 * 2000) {}
|
||||
: BuffOnPartyTrigger(botAI, "blessing of kings,blessing of might,blessing of wisdom", 2 * 2000)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class BlessingTrigger : public BuffTrigger
|
||||
@ -91,8 +93,7 @@ public:
|
||||
class HammerOfJusticeInterruptSpellTrigger : public InterruptSpellTrigger
|
||||
{
|
||||
public:
|
||||
HammerOfJusticeInterruptSpellTrigger(PlayerbotAI* botAI)
|
||||
: InterruptSpellTrigger(botAI, "hammer of justice") {}
|
||||
HammerOfJusticeInterruptSpellTrigger(PlayerbotAI* botAI) : InterruptSpellTrigger(botAI, "hammer of justice") {}
|
||||
};
|
||||
|
||||
class HammerOfJusticeSnareTrigger : public SnareTargetTrigger
|
||||
@ -143,7 +144,9 @@ class CleanseCurePartyMemberDiseaseTrigger : public PartyMemberNeedCureTrigger
|
||||
{
|
||||
public:
|
||||
CleanseCurePartyMemberDiseaseTrigger(PlayerbotAI* botAI)
|
||||
: PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_DISEASE) {}
|
||||
: PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_DISEASE)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class CleanseCurePoisonTrigger : public NeedCureTrigger
|
||||
@ -156,7 +159,9 @@ class CleanseCurePartyMemberPoisonTrigger : public PartyMemberNeedCureTrigger
|
||||
{
|
||||
public:
|
||||
CleanseCurePartyMemberPoisonTrigger(PlayerbotAI* botAI)
|
||||
: PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_POISON) {}
|
||||
: PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_POISON)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class CleanseCureMagicTrigger : public NeedCureTrigger
|
||||
@ -168,15 +173,15 @@ public:
|
||||
class CleanseCurePartyMemberMagicTrigger : public PartyMemberNeedCureTrigger
|
||||
{
|
||||
public:
|
||||
CleanseCurePartyMemberMagicTrigger(PlayerbotAI* botAI)
|
||||
: PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_MAGIC) {}
|
||||
CleanseCurePartyMemberMagicTrigger(PlayerbotAI* botAI) : PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_MAGIC)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class HammerOfJusticeEnemyHealerTrigger : public InterruptEnemyHealerTrigger
|
||||
{
|
||||
public:
|
||||
HammerOfJusticeEnemyHealerTrigger(PlayerbotAI* botAI)
|
||||
: InterruptEnemyHealerTrigger(botAI, "hammer of justice") {}
|
||||
HammerOfJusticeEnemyHealerTrigger(PlayerbotAI* botAI) : InterruptEnemyHealerTrigger(botAI, "hammer of justice") {}
|
||||
};
|
||||
|
||||
class DivineFavorTrigger : public BuffTrigger
|
||||
@ -185,14 +190,6 @@ public:
|
||||
DivineFavorTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "divine favor") {}
|
||||
};
|
||||
|
||||
class NotSensingUndeadTrigger : public BuffTrigger
|
||||
{
|
||||
public:
|
||||
NotSensingUndeadTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "not sensing undead") {}
|
||||
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class TurnUndeadTrigger : public HasCcTargetTrigger
|
||||
{
|
||||
public:
|
||||
@ -204,8 +201,7 @@ DEBUFF_TRIGGER(AvengerShieldTrigger, "avenger's shield");
|
||||
class BeaconOfLightOnMainTankTrigger : public BuffOnMainTankTrigger
|
||||
{
|
||||
public:
|
||||
BeaconOfLightOnMainTankTrigger(PlayerbotAI* ai)
|
||||
: BuffOnMainTankTrigger(ai, "beacon of light", true) {}
|
||||
BeaconOfLightOnMainTankTrigger(PlayerbotAI* ai) : BuffOnMainTankTrigger(ai, "beacon of light", true) {}
|
||||
};
|
||||
|
||||
class SacredShieldOnMainTankTrigger : public BuffOnMainTankTrigger
|
||||
@ -217,29 +213,34 @@ public:
|
||||
class BlessingOfKingsOnPartyTrigger : public BuffOnPartyTrigger
|
||||
{
|
||||
public:
|
||||
BlessingOfKingsOnPartyTrigger(PlayerbotAI* botAI)
|
||||
: BuffOnPartyTrigger(botAI, "blessing of kings", 2 * 2000) {}
|
||||
BlessingOfKingsOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "blessing of kings", 2 * 2000) {}
|
||||
};
|
||||
|
||||
class BlessingOfWisdomOnPartyTrigger : public BuffOnPartyTrigger
|
||||
{
|
||||
public:
|
||||
BlessingOfWisdomOnPartyTrigger(PlayerbotAI* botAI)
|
||||
: BuffOnPartyTrigger(botAI, "blessing of might,blessing of wisdom", 2 * 2000) {}
|
||||
: BuffOnPartyTrigger(botAI, "blessing of might,blessing of wisdom", 2 * 2000)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class BlessingOfMightOnPartyTrigger : public BuffOnPartyTrigger
|
||||
{
|
||||
public:
|
||||
BlessingOfMightOnPartyTrigger(PlayerbotAI* botAI)
|
||||
: BuffOnPartyTrigger(botAI, "blessing of might,blessing of wisdom", 2 * 2000) {}
|
||||
: BuffOnPartyTrigger(botAI, "blessing of might,blessing of wisdom", 2 * 2000)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class BlessingOfSanctuaryOnPartyTrigger : public BuffOnPartyTrigger
|
||||
{
|
||||
public:
|
||||
BlessingOfSanctuaryOnPartyTrigger(PlayerbotAI* botAI)
|
||||
: BuffOnPartyTrigger(botAI, "blessing of sanctuary", 2 * 2000) {}
|
||||
: BuffOnPartyTrigger(botAI, "blessing of sanctuary", 2 * 2000)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class AvengingWrathTrigger : public BoostTrigger
|
||||
@ -247,5 +248,4 @@ class AvengingWrathTrigger : public BoostTrigger
|
||||
public:
|
||||
AvengingWrathTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "avenging wrath") {}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -61,7 +61,7 @@ bool CastEnvenomAction::isUseful()
|
||||
bool CastEnvenomAction::isPossible()
|
||||
{
|
||||
// alternate to eviscerate if talents unlearned
|
||||
return botAI->HasAura(58410, bot) /* Master Poisoner Rank 3 */;
|
||||
return botAI->HasAura(58410, bot) /* Master Poisoner */;
|
||||
}
|
||||
|
||||
bool CastTricksOfTheTradeOnMainTankAction::isUseful()
|
||||
|
||||
@ -78,12 +78,6 @@ public:
|
||||
CastFeintAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "feint") {}
|
||||
};
|
||||
|
||||
class CastColdBloodAction : public CastBuffSpellAction
|
||||
{
|
||||
public:
|
||||
CastColdBloodAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "cold blood") {}
|
||||
};
|
||||
|
||||
class CastDismantleAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
|
||||
@ -143,7 +143,6 @@ public:
|
||||
creators["use instant poison on off hand"] = &RogueAiObjectContextInternal::use_instant_poison_off_hand;
|
||||
creators["fan of knives"] = &RogueAiObjectContextInternal::fan_of_knives;
|
||||
creators["killing spree"] = &RogueAiObjectContextInternal::killing_spree;
|
||||
creators["cold blood"] = &RogueAiObjectContextInternal::cold_blood;
|
||||
}
|
||||
|
||||
private:
|
||||
@ -185,7 +184,6 @@ private:
|
||||
static Action* use_instant_poison_off_hand(PlayerbotAI* ai) { return new UseInstantPoisonOffHandAction(ai); }
|
||||
static Action* fan_of_knives(PlayerbotAI* ai) { return new FanOfKnivesAction(ai); }
|
||||
static Action* killing_spree(PlayerbotAI* ai) { return new CastKillingSpreeAction(ai); }
|
||||
static Action* cold_blood(PlayerbotAI* ai) { return new CastColdBloodAction(ai); }
|
||||
};
|
||||
|
||||
SharedNamedObjectContextList<Strategy> RogueAiObjectContext::sharedStrategyContexts;
|
||||
|
||||
@ -29,7 +29,7 @@ private:
|
||||
return new ActionNode(
|
||||
"envenom",
|
||||
/*P*/ {},
|
||||
/*A*/ { NextAction("eviscerate") },
|
||||
/*A*/ { NextAction("rupture") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
@ -108,10 +108,10 @@ void AssassinationRogueStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"combo points 4 available",
|
||||
"combo points 3 available",
|
||||
{
|
||||
NextAction("cold blood", ACTION_HIGH + 6),
|
||||
NextAction("envenom", ACTION_HIGH + 5)
|
||||
NextAction("envenom", ACTION_HIGH + 5),
|
||||
NextAction("eviscerate", ACTION_HIGH + 3)
|
||||
}
|
||||
)
|
||||
);
|
||||
@ -120,7 +120,8 @@ void AssassinationRogueStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
|
||||
new TriggerNode(
|
||||
"target with combo points almost dead",
|
||||
{
|
||||
NextAction("envenom", ACTION_HIGH + 4)
|
||||
NextAction("envenom", ACTION_HIGH + 4),
|
||||
NextAction("eviscerate", ACTION_HIGH + 2)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
@ -12,14 +12,36 @@ class DpsRogueStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
||||
public:
|
||||
DpsRogueStrategyActionNodeFactory()
|
||||
{
|
||||
creators["mutilate"] = &mutilate;
|
||||
creators["sinister strike"] = &sinister_strike;
|
||||
creators["kick"] = &kick;
|
||||
creators["kidney shot"] = &kidney_shot;
|
||||
creators["backstab"] = &backstab;
|
||||
creators["melee"] = &melee;
|
||||
creators["rupture"] = &rupture;
|
||||
}
|
||||
|
||||
private:
|
||||
static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode(
|
||||
"melee",
|
||||
/*P*/ {},
|
||||
/*A*/ {
|
||||
NextAction("mutilate") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
static ActionNode* mutilate([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode(
|
||||
"mutilate",
|
||||
/*P*/ {},
|
||||
/*A*/ {
|
||||
NextAction("sinister strike") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
static ActionNode* sinister_strike([[maybe_unused]] PlayerbotAI* botAI)
|
||||
{
|
||||
return new ActionNode(
|
||||
@ -55,7 +77,7 @@ private:
|
||||
"backstab",
|
||||
/*P*/ {},
|
||||
/*A*/ {
|
||||
NextAction("sinister strike") },
|
||||
NextAction("mutilate") },
|
||||
/*C*/ {}
|
||||
);
|
||||
}
|
||||
@ -118,7 +140,7 @@ void DpsRogueStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"combo points 5 available",
|
||||
"combo points available",
|
||||
{
|
||||
NextAction("rupture", ACTION_HIGH + 1),
|
||||
NextAction("eviscerate", ACTION_HIGH)
|
||||
@ -313,7 +335,7 @@ void StealthedRogueStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
triggers.push_back(
|
||||
new TriggerNode(
|
||||
"combo points 5 available",
|
||||
"combo points available",
|
||||
{
|
||||
NextAction("eviscerate", ACTION_HIGH)
|
||||
}
|
||||
|
||||
@ -231,6 +231,7 @@ bool NewRpgDoQuestAction::Execute(Event /*event*/)
|
||||
return false;
|
||||
auto& data = *dataPtr;
|
||||
uint32 questId = data.questId;
|
||||
const Quest* quest = data.quest;
|
||||
uint8 questStatus = bot->GetQuestStatus(questId);
|
||||
switch (questStatus)
|
||||
{
|
||||
@ -437,7 +438,7 @@ bool NewRpgTravelFlightAction::Execute(Event /*event*/)
|
||||
if (bot->GetDistance(flightMaster) > INTERACTION_DISTANCE)
|
||||
return MoveFarTo(flightMaster);
|
||||
|
||||
std::vector<uint32> nodes = data.path;
|
||||
std::vector<uint32> nodes = {data.fromNode, data.toNode};
|
||||
|
||||
botAI->RemoveShapeshift();
|
||||
if (bot->IsMounted())
|
||||
@ -446,7 +447,7 @@ bool NewRpgTravelFlightAction::Execute(Event /*event*/)
|
||||
if (!bot->ActivateTaxiPathTo(nodes, flightMaster, 0))
|
||||
{
|
||||
LOG_DEBUG("playerbots", "[New RPG] {} active taxi path {} (from {} to {}) failed", bot->GetName(),
|
||||
flightMaster->GetEntry(), nodes[0], nodes[nodes.size() - 1]);
|
||||
flightMaster->GetEntry(), nodes[0], nodes[1]);
|
||||
botAI->rpgInfo.ChangeToIdle();
|
||||
}
|
||||
return true;
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include "BroadcastHelper.h"
|
||||
#include "ChatHelper.h"
|
||||
#include "Creature.h"
|
||||
#include "FlightMasterCache.h"
|
||||
#include "G3D/Vector2.h"
|
||||
#include "GameObject.h"
|
||||
#include "GossipDef.h"
|
||||
@ -855,7 +856,7 @@ bool NewRpgBaseAction::GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector
|
||||
|
||||
WorldPosition NewRpgBaseAction::SelectRandomGrindPos(Player* bot)
|
||||
{
|
||||
const std::vector<WorldLocation>& locs = sTravelMgr.GetLocsPerLevelCache(bot->GetLevel());
|
||||
const std::vector<WorldLocation>& locs = sRandomPlayerbotMgr.locsPerLevelCache[bot->GetLevel()];
|
||||
float hiRange = 500.0f;
|
||||
float loRange = 2500.0f;
|
||||
if (bot->GetLevel() < 5)
|
||||
@ -913,7 +914,9 @@ WorldPosition NewRpgBaseAction::SelectRandomGrindPos(Player* bot)
|
||||
|
||||
WorldPosition NewRpgBaseAction::SelectRandomCampPos(Player* bot)
|
||||
{
|
||||
const std::vector<WorldLocation> locs = sTravelMgr.GetTravelHubs(bot);
|
||||
const std::vector<WorldLocation>& locs = IsAlliance(bot->getRace())
|
||||
? sRandomPlayerbotMgr.allianceStarterPerLevelCache[bot->GetLevel()]
|
||||
: sRandomPlayerbotMgr.hordeStarterPerLevelCache[bot->GetLevel()];
|
||||
|
||||
bool inCity = false;
|
||||
|
||||
@ -954,19 +957,70 @@ WorldPosition NewRpgBaseAction::SelectRandomCampPos(Player* bot)
|
||||
return dest;
|
||||
}
|
||||
|
||||
bool NewRpgBaseAction::SelectRandomFlightTaxiNode(ObjectGuid& flightMaster, std::vector<uint32>& path)
|
||||
bool NewRpgBaseAction::SelectRandomFlightTaxiNode(ObjectGuid& flightMaster, uint32& fromNode, uint32& toNode)
|
||||
{
|
||||
flightMaster = sTravelMgr.GetNearestFlightMasterGuid(bot);
|
||||
if (!flightMaster)
|
||||
Creature* nearestFlightMaster = FlightMasterCache::Instance().GetNearestFlightMaster(bot);
|
||||
if (!nearestFlightMaster || bot->GetDistance(nearestFlightMaster) > 500.0f)
|
||||
return false;
|
||||
|
||||
std::vector<std::vector<uint32>> availablePaths = sTravelMgr.GetOptimalFlightDestinations(bot);
|
||||
if (availablePaths.empty())
|
||||
fromNode = sObjectMgr->GetNearestTaxiNode(nearestFlightMaster->GetPositionX(), nearestFlightMaster->GetPositionY(),
|
||||
nearestFlightMaster->GetPositionZ(), nearestFlightMaster->GetMapId(),
|
||||
bot->GetTeamId());
|
||||
|
||||
if (!fromNode)
|
||||
return false;
|
||||
|
||||
path = availablePaths[urand(0, availablePaths.size() - 1)];
|
||||
std::vector<uint32> availableToNodes;
|
||||
for (uint32 i = 1; i < sTaxiNodesStore.GetNumRows(); ++i)
|
||||
{
|
||||
if (fromNode == i)
|
||||
continue;
|
||||
|
||||
TaxiNodesEntry const* node = sTaxiNodesStore.LookupEntry(i);
|
||||
|
||||
// check map
|
||||
if (!node || node->map_id != bot->GetMapId() ||
|
||||
(!node->MountCreatureID[bot->GetTeamId() == TEAM_ALLIANCE ? 1 : 0])) // dk flight
|
||||
continue;
|
||||
|
||||
// check taxi node known
|
||||
if (!bot->isTaxiCheater() && !bot->m_taxi.IsTaximaskNodeKnown(i))
|
||||
continue;
|
||||
|
||||
// check distance by level
|
||||
if (!botAI->CheckLocationDistanceByLevel(bot, WorldLocation(node->map_id, node->x, node->y, node->z), false))
|
||||
continue;
|
||||
|
||||
// check path
|
||||
uint32 path, cost;
|
||||
sObjectMgr->GetTaxiPath(fromNode, i, path, cost);
|
||||
if (!path)
|
||||
continue;
|
||||
|
||||
// check area level
|
||||
uint32 nodeZoneId = bot->GetMap()->GetZoneId(bot->GetPhaseMask(), node->x, node->y, node->z);
|
||||
bool capital = false;
|
||||
if (AreaTableEntry const* zone = sAreaTableStore.LookupEntry(nodeZoneId))
|
||||
{
|
||||
capital = zone->flags & AREA_FLAG_CAPITAL;
|
||||
}
|
||||
|
||||
auto itr = sRandomPlayerbotMgr.zone2LevelBracket.find(nodeZoneId);
|
||||
if (!capital && itr == sRandomPlayerbotMgr.zone2LevelBracket.end())
|
||||
continue;
|
||||
|
||||
if (!capital && (bot->GetLevel() < itr->second.low || bot->GetLevel() > itr->second.high))
|
||||
continue;
|
||||
|
||||
availableToNodes.push_back(i);
|
||||
}
|
||||
if (availableToNodes.empty())
|
||||
return false;
|
||||
|
||||
flightMaster = nearestFlightMaster->GetGUID();
|
||||
toNode = availableToNodes[urand(0, availableToNodes.size() - 1)];
|
||||
LOG_DEBUG("playerbots", "[New RPG] Bot {} select random flight taxi node from:{} (node {}) to:{} ({} available)",
|
||||
bot->GetName(), flightMaster.GetEntry(), path[0], path[path.size() - 1], availablePaths.size());
|
||||
bot->GetName(), flightMaster.GetEntry(), fromNode, toNode, availableToNodes.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1067,10 +1121,10 @@ bool NewRpgBaseAction::RandomChangeStatus(std::vector<NewRpgStatus> candidateSta
|
||||
case RPG_TRAVEL_FLIGHT:
|
||||
{
|
||||
ObjectGuid flightMaster;
|
||||
std::vector<uint32> path;
|
||||
if (SelectRandomFlightTaxiNode(flightMaster, path))
|
||||
uint32 fromNode, toNode;
|
||||
if (SelectRandomFlightTaxiNode(flightMaster, fromNode, toNode))
|
||||
{
|
||||
botAI->rpgInfo.ChangeToTravelFlight(flightMaster, path);
|
||||
botAI->rpgInfo.ChangeToTravelFlight(flightMaster, fromNode, toNode);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -1143,8 +1197,8 @@ bool NewRpgBaseAction::CheckRpgStatusAvailable(NewRpgStatus status)
|
||||
case RPG_TRAVEL_FLIGHT:
|
||||
{
|
||||
ObjectGuid flightMaster;
|
||||
std::vector<uint32> path;
|
||||
return SelectRandomFlightTaxiNode(flightMaster, path);
|
||||
uint32 fromNode, toNode;
|
||||
return SelectRandomFlightTaxiNode(flightMaster, fromNode, toNode);
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
|
||||
@ -54,7 +54,7 @@ protected:
|
||||
bool GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector<POIInfo>& poiInfo, bool toComplete = false);
|
||||
static WorldPosition SelectRandomGrindPos(Player* bot);
|
||||
static WorldPosition SelectRandomCampPos(Player* bot);
|
||||
bool SelectRandomFlightTaxiNode(ObjectGuid& flightMaster, std::vector<uint32>& path);
|
||||
bool SelectRandomFlightTaxiNode(ObjectGuid& flightMaster, uint32& fromNode, uint32& toNode);
|
||||
bool RandomChangeStatus(std::vector<NewRpgStatus> candidateStatus);
|
||||
bool CheckRpgStatusAvailable(NewRpgStatus status);
|
||||
|
||||
|
||||
@ -37,12 +37,13 @@ void NewRpgInfo::ChangeToDoQuest(uint32 questId, const Quest* quest)
|
||||
data = do_quest;
|
||||
}
|
||||
|
||||
void NewRpgInfo::ChangeToTravelFlight(ObjectGuid fromFlightMaster, std::vector<uint32> path)
|
||||
void NewRpgInfo::ChangeToTravelFlight(ObjectGuid fromFlightMaster, uint32 fromNode, uint32 toNode)
|
||||
{
|
||||
startT = getMSTime();
|
||||
TravelFlight flight;
|
||||
flight.fromFlightMaster = fromFlightMaster;
|
||||
flight.path = std::move(path);
|
||||
flight.fromNode = fromNode;
|
||||
flight.toNode = toNode;
|
||||
flight.inFlight = false;
|
||||
data = flight;
|
||||
}
|
||||
@ -149,8 +150,8 @@ std::string NewRpgInfo::ToString()
|
||||
{
|
||||
out << "TRAVEL_FLIGHT";
|
||||
out << "\nfromFlightMaster: " << arg.fromFlightMaster.GetEntry();
|
||||
out << "\nfromNode: " << arg.path[0];
|
||||
out << "\ntoNode: " << arg.path[arg.path.size() - 1];
|
||||
out << "\nfromNode: " << arg.fromNode;
|
||||
out << "\ntoNode: " << arg.toNode;
|
||||
out << "\ninFlight: " << arg.inFlight;
|
||||
}
|
||||
else
|
||||
|
||||
@ -50,7 +50,8 @@ struct NewRpgInfo
|
||||
struct TravelFlight
|
||||
{
|
||||
ObjectGuid fromFlightMaster{};
|
||||
std::vector<uint32> path;
|
||||
uint32 fromNode{0};
|
||||
uint32 toNode{0};
|
||||
bool inFlight{false};
|
||||
};
|
||||
// RPG_REST
|
||||
@ -90,7 +91,7 @@ struct NewRpgInfo
|
||||
void ChangeToWanderNpc();
|
||||
void ChangeToWanderRandom();
|
||||
void ChangeToDoQuest(uint32 questId, const Quest* quest);
|
||||
void ChangeToTravelFlight(ObjectGuid fromFlightMaster, std::vector<uint32> path);
|
||||
void ChangeToTravelFlight(ObjectGuid fromFlightMaster, uint32 fromNode, uint32 toNode);
|
||||
void ChangeToRest();
|
||||
void ChangeToIdle();
|
||||
bool CanChangeTo(NewRpgStatus status);
|
||||
|
||||
@ -762,7 +762,7 @@ void PlayerbotFactory::InitPetTalents()
|
||||
// pet_family->petTalentType);
|
||||
return;
|
||||
}
|
||||
std::map<uint32, std::vector<TalentEntry const*>> spells;
|
||||
std::unordered_map<uint32, std::vector<TalentEntry const*>> spells;
|
||||
bool diveTypePet = (1LL << ci->family) & diveMask;
|
||||
|
||||
for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i)
|
||||
@ -2553,15 +2553,17 @@ void PlayerbotFactory::InitClassSpells()
|
||||
bot->learnSpell(7386, false); // Sunder Armor
|
||||
}
|
||||
if (level >= 30)
|
||||
{
|
||||
bot->learnSpell(2458, false); // Berserker Stance
|
||||
}
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
bot->learnSpell(21084, true);
|
||||
bot->learnSpell(635, true);
|
||||
if (level >= 12)
|
||||
{
|
||||
bot->learnSpell(7328, false); // Redemption
|
||||
if (level >= 20)
|
||||
bot->learnSpell(5502, false); // Sense Undead
|
||||
}
|
||||
break;
|
||||
case CLASS_ROGUE:
|
||||
bot->learnSpell(1752, true);
|
||||
@ -2603,11 +2605,17 @@ void PlayerbotFactory::InitClassSpells()
|
||||
bot->learnSpell(686, true);
|
||||
bot->learnSpell(688, false); // summon imp
|
||||
if (level >= 10)
|
||||
{
|
||||
bot->learnSpell(697, false); // summon voidwalker
|
||||
}
|
||||
if (level >= 20)
|
||||
{
|
||||
bot->learnSpell(712, false); // summon succubus
|
||||
}
|
||||
if (level >= 30)
|
||||
{
|
||||
bot->learnSpell(691, false); // summon felhunter
|
||||
}
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
bot->learnSpell(5176, true);
|
||||
@ -2624,11 +2632,17 @@ void PlayerbotFactory::InitClassSpells()
|
||||
bot->learnSpell(331, true);
|
||||
// bot->learnSpell(66747, true); // Totem of the Earthen Ring
|
||||
if (level >= 4)
|
||||
{
|
||||
bot->learnSpell(8071, false); // stoneskin totem
|
||||
}
|
||||
if (level >= 10)
|
||||
{
|
||||
bot->learnSpell(3599, false); // searing totem
|
||||
}
|
||||
if (level >= 20)
|
||||
{
|
||||
bot->learnSpell(5394, false); // healing stream totem
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -2653,7 +2667,7 @@ void PlayerbotFactory::InitSpecialSpells()
|
||||
void PlayerbotFactory::InitTalents(uint32 specNo)
|
||||
{
|
||||
uint32 classMask = bot->getClassMask();
|
||||
std::map<uint32, std::vector<TalentEntry const*>> spells;
|
||||
std::unordered_map<uint32, std::vector<TalentEntry const*>> spells;
|
||||
for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i)
|
||||
{
|
||||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(i);
|
||||
@ -3328,36 +3342,18 @@ void PlayerbotFactory::InitReagents()
|
||||
items.push_back({44615, 40}); // Devout Candle
|
||||
break;
|
||||
case CLASS_SHAMAN:
|
||||
{
|
||||
HasRelicBySubclassVisitor relicVisitor(ITEM_SUBCLASS_ARMOR_TOTEM);
|
||||
IterateItems(&relicVisitor, (IterateItemsMask)(ITERATE_ITEMS_IN_BAGS | ITERATE_ITEMS_IN_EQUIP));
|
||||
bool hasRelic = relicVisitor.found;
|
||||
|
||||
if (!hasRelic)
|
||||
{
|
||||
if (level >= 4)
|
||||
items.push_back({5175, 1}); // Earth Totem
|
||||
if (level >= 10)
|
||||
items.push_back({5176, 1}); // Flame Totem
|
||||
if (level >= 20)
|
||||
items.push_back({5177, 1}); // Water Totem
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemIds totemIds = {5175, 5176, 5177, 5178};
|
||||
FindItemByIdsVisitor totemVisitor(totemIds);
|
||||
IterateItems(&totemVisitor, (IterateItemsMask)(ITERATE_ITEMS_IN_BAGS | ITERATE_ITEMS_IN_EQUIP | ITERATE_ITEMS_IN_BANK));
|
||||
for (Item* item : totemVisitor.GetResult())
|
||||
bot->DestroyItem(item->GetBagSlot(), item->GetSlot(), true);
|
||||
}
|
||||
if (level >= 4)
|
||||
items.push_back({5175, 1}); // Earth Totem
|
||||
if (level >= 10)
|
||||
items.push_back({5176, 1}); // Flame Totem
|
||||
if (level >= 20)
|
||||
items.push_back({5177, 1}); // Water Totem
|
||||
if (level >= 30)
|
||||
{
|
||||
if (!hasRelic)
|
||||
items.push_back({5178, 1}); // Air Totem
|
||||
items.push_back({5178, 1}); // Air Totem
|
||||
items.push_back({17030, 20}); // Ankh
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CLASS_WARLOCK:
|
||||
items.push_back({6265, 5}); // Soul Shard
|
||||
break;
|
||||
|
||||
@ -1119,9 +1119,6 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
|
||||
if (guid1.IsEmpty() || p.size() > p.DEFAULT_SIZE)
|
||||
return;
|
||||
|
||||
if (lang == LANG_ADDON)
|
||||
return;
|
||||
|
||||
if (p.GetOpcode() == SMSG_GM_MESSAGECHAT)
|
||||
{
|
||||
p >> textLen;
|
||||
@ -1171,6 +1168,8 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
|
||||
|
||||
if (HasRealPlayerMaster() && guid1 != GetMaster()->GetGUID())
|
||||
return;
|
||||
if (lang == LANG_ADDON)
|
||||
return;
|
||||
|
||||
if (message.starts_with(sPlayerbotAIConfig.toxicLinksPrefix) &&
|
||||
(GetChatHelper()->ExtractAllItemIds(message).size() > 0 ||
|
||||
@ -1250,10 +1249,17 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
|
||||
|
||||
p >> guid.ReadAsPacked() >> counter >> vcos >> vsin >> horizontalSpeed >> verticalSpeed;
|
||||
if (horizontalSpeed <= 0.1f)
|
||||
{
|
||||
horizontalSpeed = 0.11f;
|
||||
}
|
||||
verticalSpeed = -verticalSpeed;
|
||||
|
||||
// high vertical may result in stuck as bot can not handle gravity
|
||||
if (verticalSpeed > 35.0f)
|
||||
break;
|
||||
// stop casting
|
||||
InterruptSpell();
|
||||
|
||||
// stop movement
|
||||
bot->StopMoving();
|
||||
bot->GetMotionMaster()->Clear();
|
||||
|
||||
@ -6480,7 +6486,7 @@ ChatChannelSource PlayerbotAI::GetChatChannelSource(Player* bot, uint32 type, st
|
||||
return ChatChannelSource::SRC_UNDEFINED;
|
||||
}
|
||||
|
||||
bool PlayerbotAI::StarterLevelDistanceCheck(Player* player, const WorldLocation& loc, bool fromStartUp)
|
||||
bool PlayerbotAI::CheckLocationDistanceByLevel(Player* player, const WorldLocation& loc, bool fromStartUp)
|
||||
{
|
||||
if (player->GetLevel() > 16)
|
||||
return true;
|
||||
|
||||
@ -556,7 +556,7 @@ public:
|
||||
bool IsSafe(WorldObject* obj);
|
||||
ChatChannelSource GetChatChannelSource(Player* bot, uint32 type, std::string channelName);
|
||||
|
||||
bool StarterLevelDistanceCheck(Player* player, const WorldLocation &loc, bool fromStartUp = false);
|
||||
bool CheckLocationDistanceByLevel(Player* player, const WorldLocation &loc, bool fromStartUp = false);
|
||||
|
||||
bool HasCheat(BotCheatMask mask)
|
||||
{
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Define.h"
|
||||
#include "FleeManager.h"
|
||||
#include "FlightMasterCache.h"
|
||||
#include "GridNotifiers.h"
|
||||
#include "LFGMgr.h"
|
||||
#include "MapMgr.h"
|
||||
@ -46,7 +47,9 @@
|
||||
#include "World.h"
|
||||
#include "Cell.h"
|
||||
#include "GridNotifiers.h"
|
||||
// Required for Cell because of poor AC implementation
|
||||
#include "CellImpl.h"
|
||||
// Required for GridNotifiers because of poor AC implementation
|
||||
#include "GridNotifiersImpl.h"
|
||||
|
||||
struct GuidClassRaceInfo
|
||||
@ -56,6 +59,48 @@ struct GuidClassRaceInfo
|
||||
uint32 rRace;
|
||||
};
|
||||
|
||||
enum class CityId : uint8 {
|
||||
STORMWIND, IRONFORGE, DARNASSUS, EXODAR,
|
||||
ORGRIMMAR, UNDERCITY, THUNDER_BLUFF, SILVERMOON_CITY,
|
||||
SHATTRATH_CITY, DALARAN
|
||||
};
|
||||
|
||||
enum class FactionId : uint8 { ALLIANCE, HORDE, NEUTRAL };
|
||||
|
||||
// Map of banker entry → city + faction
|
||||
static const std::unordered_map<uint16, std::pair<CityId, FactionId>> bankerToCity = {
|
||||
{2455, {CityId::STORMWIND, FactionId::ALLIANCE}}, {2456, {CityId::STORMWIND, FactionId::ALLIANCE}}, {2457, {CityId::STORMWIND, FactionId::ALLIANCE}},
|
||||
{2460, {CityId::IRONFORGE, FactionId::ALLIANCE}}, {2461, {CityId::IRONFORGE, FactionId::ALLIANCE}}, {5099, {CityId::IRONFORGE, FactionId::ALLIANCE}},
|
||||
{4155, {CityId::DARNASSUS, FactionId::ALLIANCE}}, {4208, {CityId::DARNASSUS, FactionId::ALLIANCE}}, {4209, {CityId::DARNASSUS, FactionId::ALLIANCE}},
|
||||
{17773, {CityId::EXODAR, FactionId::ALLIANCE}}, {18350, {CityId::EXODAR, FactionId::ALLIANCE}}, {16710, {CityId::EXODAR, FactionId::ALLIANCE}},
|
||||
{3320, {CityId::ORGRIMMAR, FactionId::HORDE}}, {3309, {CityId::ORGRIMMAR, FactionId::HORDE}}, {3318, {CityId::ORGRIMMAR, FactionId::HORDE}},
|
||||
{4549, {CityId::UNDERCITY, FactionId::HORDE}}, {2459, {CityId::UNDERCITY, FactionId::HORDE}}, {2458, {CityId::UNDERCITY, FactionId::HORDE}}, {4550, {CityId::UNDERCITY, FactionId::HORDE}},
|
||||
{2996, {CityId::THUNDER_BLUFF, FactionId::HORDE}}, {8356, {CityId::THUNDER_BLUFF, FactionId::HORDE}}, {8357, {CityId::THUNDER_BLUFF, FactionId::HORDE}},
|
||||
{17631, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {17632, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {17633, {CityId::SILVERMOON_CITY, FactionId::HORDE}},
|
||||
{16615, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {16616, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {16617, {CityId::SILVERMOON_CITY, FactionId::HORDE}},
|
||||
{19246, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}}, {19338, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}},
|
||||
{19034, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}}, {19318, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}},
|
||||
{30604, {CityId::DALARAN, FactionId::NEUTRAL}}, {30605, {CityId::DALARAN, FactionId::NEUTRAL}}, {30607, {CityId::DALARAN, FactionId::NEUTRAL}},
|
||||
{28675, {CityId::DALARAN, FactionId::NEUTRAL}}, {28676, {CityId::DALARAN, FactionId::NEUTRAL}}, {28677, {CityId::DALARAN, FactionId::NEUTRAL}}
|
||||
};
|
||||
|
||||
// Map of city → available banker entries
|
||||
static const std::unordered_map<CityId, std::vector<uint16>> cityToBankers = {
|
||||
{CityId::STORMWIND, {2455, 2456, 2457}},
|
||||
{CityId::IRONFORGE, {2460, 2461, 5099}},
|
||||
{CityId::DARNASSUS, {4155, 4208, 4209}},
|
||||
{CityId::EXODAR, {17773, 18350, 16710}},
|
||||
{CityId::ORGRIMMAR, {3320, 3309, 3318}},
|
||||
{CityId::UNDERCITY, {4549, 2459, 2458, 4550}},
|
||||
{CityId::THUNDER_BLUFF, {2996, 8356, 8357}},
|
||||
{CityId::SILVERMOON_CITY, {17631, 17632, 17633, 16615, 16616, 16617}},
|
||||
{CityId::SHATTRATH_CITY, {19246, 19338, 19034, 19318}},
|
||||
{CityId::DALARAN, {30604, 30605, 30607, 28675, 28676, 28677, 29530}}
|
||||
};
|
||||
|
||||
// Quick lookup map: banker entry → location
|
||||
static std::unordered_map<uint32, WorldLocation> bankerEntryToLocation;
|
||||
|
||||
void PrintStatsThread() { sRandomPlayerbotMgr.PrintStats(); }
|
||||
|
||||
void activatePrintStatsThread()
|
||||
@ -1673,7 +1718,7 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
|
||||
|
||||
z = 0.05f + ground;
|
||||
|
||||
if (!botAI->StarterLevelDistanceCheck(bot, loc, true))
|
||||
if (!botAI->CheckLocationDistanceByLevel(bot, loc, true))
|
||||
continue;
|
||||
|
||||
const LocaleConstant& locale = sWorld->GetDefaultDbcLocale();
|
||||
@ -1717,6 +1762,329 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
|
||||
// tlocs.size());
|
||||
}
|
||||
|
||||
void RandomPlayerbotMgr::PrepareZone2LevelBracket()
|
||||
{
|
||||
// Classic WoW - Low - level zones
|
||||
zone2LevelBracket[1] = {5, 12}; // Dun Morogh
|
||||
zone2LevelBracket[12] = {5, 12}; // Elwynn Forest
|
||||
zone2LevelBracket[14] = {5, 12}; // Durotar
|
||||
zone2LevelBracket[85] = {5, 12}; // Tirisfal Glades
|
||||
zone2LevelBracket[141] = {5, 12}; // Teldrassil
|
||||
zone2LevelBracket[215] = {5, 12}; // Mulgore
|
||||
zone2LevelBracket[3430] = {5, 12}; // Eversong Woods
|
||||
zone2LevelBracket[3524] = {5, 12}; // Azuremyst Isle
|
||||
|
||||
// Classic WoW - Mid - level zones
|
||||
zone2LevelBracket[17] = {10, 25}; // Barrens
|
||||
zone2LevelBracket[38] = {10, 20}; // Loch Modan
|
||||
zone2LevelBracket[40] = {10, 21}; // Westfall
|
||||
zone2LevelBracket[130] = {10, 23}; // Silverpine Forest
|
||||
zone2LevelBracket[148] = {10, 21}; // Darkshore
|
||||
zone2LevelBracket[3433] = {10, 22}; // Ghostlands
|
||||
zone2LevelBracket[3525] = {10, 21}; // Bloodmyst Isle
|
||||
|
||||
// Classic WoW - High - level zones
|
||||
zone2LevelBracket[10] = {19, 33}; // Duskwood
|
||||
zone2LevelBracket[11] = {21, 30}; // Wetlands
|
||||
zone2LevelBracket[44] = {16, 28}; // Redridge Mountains
|
||||
zone2LevelBracket[267] = {20, 34}; // Hillsbrad Foothills
|
||||
zone2LevelBracket[331] = {18, 33}; // Ashenvale
|
||||
zone2LevelBracket[400] = {24, 36}; // Thousand Needles
|
||||
zone2LevelBracket[406] = {16, 29}; // Stonetalon Mountains
|
||||
|
||||
// Classic WoW - Higher - level zones
|
||||
zone2LevelBracket[3] = {36, 46}; // Badlands
|
||||
zone2LevelBracket[8] = {36, 46}; // Swamp of Sorrows
|
||||
zone2LevelBracket[15] = {35, 46}; // Dustwallow Marsh
|
||||
zone2LevelBracket[16] = {45, 52}; // Azshara
|
||||
zone2LevelBracket[33] = {32, 47}; // Stranglethorn Vale
|
||||
zone2LevelBracket[45] = {30, 42}; // Arathi Highlands
|
||||
zone2LevelBracket[47] = {42, 51}; // Hinterlands
|
||||
zone2LevelBracket[51] = {45, 51}; // Searing Gorge
|
||||
zone2LevelBracket[357] = {40, 52}; // Feralas
|
||||
zone2LevelBracket[405] = {30, 41}; // Desolace
|
||||
zone2LevelBracket[440] = {41, 52}; // Tanaris
|
||||
|
||||
// Classic WoW - Top - level zones
|
||||
zone2LevelBracket[4] = {52, 57}; // Blasted Lands
|
||||
zone2LevelBracket[28] = {50, 60}; // Western Plaguelands
|
||||
zone2LevelBracket[46] = {51, 60}; // Burning Steppes
|
||||
zone2LevelBracket[139] = {54, 62}; // Eastern Plaguelands
|
||||
zone2LevelBracket[361] = {47, 57}; // Felwood
|
||||
zone2LevelBracket[490] = {49, 56}; // Un'Goro Crater
|
||||
zone2LevelBracket[618] = {54, 61}; // Winterspring
|
||||
zone2LevelBracket[1377] = {54, 63}; // Silithus
|
||||
|
||||
// The Burning Crusade - Zones
|
||||
zone2LevelBracket[3483] = {58, 66}; // Hellfire Peninsula
|
||||
zone2LevelBracket[3518] = {64, 70}; // Nagrand
|
||||
zone2LevelBracket[3519] = {62, 73}; // Terokkar Forest
|
||||
zone2LevelBracket[3520] = {66, 73}; // Shadowmoon Valley
|
||||
zone2LevelBracket[3521] = {60, 67}; // Zangarmarsh
|
||||
zone2LevelBracket[3522] = {64, 73}; // Blade's Edge Mountains
|
||||
zone2LevelBracket[3523] = {67, 73}; // Netherstorm
|
||||
zone2LevelBracket[4080] = {68, 73}; // Isle of Quel'Danas
|
||||
|
||||
// Wrath of the Lich King - Zones
|
||||
zone2LevelBracket[65] = {71, 77}; // Dragonblight
|
||||
zone2LevelBracket[66] = {74, 80}; // Zul'Drak
|
||||
zone2LevelBracket[67] = {77, 80}; // Storm Peaks
|
||||
zone2LevelBracket[210] = {77, 80}; // Icecrown Glacier
|
||||
zone2LevelBracket[394] = {72, 78}; // Grizzly Hills
|
||||
zone2LevelBracket[495] = {68, 74}; // Howling Fjord
|
||||
zone2LevelBracket[2817] = {77, 80}; // Crystalsong Forest
|
||||
zone2LevelBracket[3537] = {68, 75}; // Borean Tundra
|
||||
zone2LevelBracket[3711] = {75, 80}; // Sholazar Basin
|
||||
zone2LevelBracket[4197] = {79, 80}; // Wintergrasp
|
||||
|
||||
// Override with values from config
|
||||
for (auto const& [zoneId, bracketPair] : sPlayerbotAIConfig.zoneBrackets)
|
||||
{
|
||||
zone2LevelBracket[zoneId] = {bracketPair.first, bracketPair.second};
|
||||
}
|
||||
}
|
||||
|
||||
void RandomPlayerbotMgr::PrepareTeleportCache()
|
||||
{
|
||||
uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
|
||||
|
||||
LOG_INFO("playerbots", "Preparing random teleport caches for {} levels...", maxLevel);
|
||||
|
||||
QueryResult results = WorldDatabase.Query(
|
||||
"SELECT "
|
||||
"g.map, "
|
||||
"position_x, "
|
||||
"position_y, "
|
||||
"position_z, "
|
||||
"t.minlevel, "
|
||||
"t.maxlevel "
|
||||
"FROM "
|
||||
"(SELECT "
|
||||
"map, "
|
||||
"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.maxlevel - t.minlevel < 3 "
|
||||
"AND map IN ({}) "
|
||||
"AND t.entry not in (32820, 24196, 30627, 30617) "
|
||||
"AND c.spawntimesecs < 1000 "
|
||||
"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 / 50), "
|
||||
"ROUND(position_y / 50), "
|
||||
"ROUND(position_z / 50) "
|
||||
"HAVING "
|
||||
"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 "
|
||||
"t.minlevel;",
|
||||
sPlayerbotAIConfig.randomBotMapsAsString.c_str());
|
||||
uint32 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>();
|
||||
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.randomBotTeleLowerLevel;
|
||||
l <= (int32)level + (int32)sPlayerbotAIConfig.randomBotTeleHigherLevel; l++)
|
||||
{
|
||||
if (l < 1 || l > maxLevel)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
locsPerLevelCache[(uint8)l].push_back(loc);
|
||||
}
|
||||
} while (results->NextRow());
|
||||
}
|
||||
LOG_INFO("playerbots", ">> {} locations for level collected.", collected_locs);
|
||||
|
||||
if (sPlayerbotAIConfig.enableNewRpgStrategy)
|
||||
{
|
||||
PrepareZone2LevelBracket();
|
||||
LOG_INFO("playerbots", "Preparing innkeepers / flightmasters locations for level...");
|
||||
results = WorldDatabase.Query(
|
||||
"SELECT "
|
||||
"map, "
|
||||
"position_x, "
|
||||
"position_y, "
|
||||
"position_z, "
|
||||
"orientation, "
|
||||
"t.faction, "
|
||||
"t.entry, "
|
||||
"t.npcflag, "
|
||||
"c.guid "
|
||||
"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 tEntry = fields[6].Get<uint32>();
|
||||
uint32 tNpcflag = fields[7].Get<uint32>();
|
||||
uint32 guid = fields[8].Get<uint32>();
|
||||
|
||||
if (tEntry == 3838 || tEntry == 29480)
|
||||
continue;
|
||||
|
||||
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;
|
||||
bool forHorde = !(entry->hostileMask & 4);
|
||||
bool forAlliance = !(entry->hostileMask & 2);
|
||||
if (tNpcflag & UNIT_NPC_FLAG_FLIGHTMASTER)
|
||||
{
|
||||
WorldPosition pos(mapId, x, y, z, orient);
|
||||
if (forHorde)
|
||||
FlightMasterCache::Instance().AddHordeFlightMaster(guid, pos);
|
||||
|
||||
if (forAlliance)
|
||||
FlightMasterCache::Instance().AddAllianceFlightMaster(guid, pos);
|
||||
}
|
||||
const AreaTableEntry* area = sAreaTableStore.LookupEntry(map->GetAreaId(PHASEMASK_NORMAL, x, y, z));
|
||||
uint32 zoneId = area->zone ? area->zone : area->ID;
|
||||
if (zone2LevelBracket.find(zoneId) == zone2LevelBracket.end())
|
||||
continue;
|
||||
LevelBracket bracket = zone2LevelBracket[zoneId];
|
||||
for (int i = bracket.low; i <= bracket.high; i++)
|
||||
{
|
||||
if (forHorde)
|
||||
hordeStarterPerLevelCache[i].push_back(loc);
|
||||
if (forAlliance)
|
||||
allianceStarterPerLevelCache[i].push_back(loc);
|
||||
}
|
||||
|
||||
} while (results->NextRow());
|
||||
}
|
||||
|
||||
// add all initial position
|
||||
for (uint32 i = 1; i < sRaceMgr->GetMaxRaces(); 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)) & sRaceMgr->GetAllianceRaceMask())
|
||||
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, "
|
||||
"position_x, "
|
||||
"position_y, "
|
||||
"position_z, "
|
||||
"orientation, "
|
||||
"t.minlevel, "
|
||||
"t.entry "
|
||||
"FROM "
|
||||
"creature c "
|
||||
"INNER JOIN creature_template t on c.id1 = t.entry "
|
||||
"WHERE "
|
||||
"t.npcflag & 131072 "
|
||||
"AND t.npcflag != 135298 "
|
||||
"AND t.minlevel != 55 "
|
||||
"AND t.minlevel != 65 "
|
||||
"AND t.faction not in (35, 474, 69, 57) "
|
||||
"AND t.entry not in (30606, 30608, 29282) "
|
||||
"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 level = fields[5].Get<uint32>();
|
||||
uint32 entry = fields[6].Get<uint32>();
|
||||
BankerLocation bLoc;
|
||||
bLoc.loc = WorldLocation(mapId, x + cos(orient) * 6.0f, y + sin(orient) * 6.0f, z + 2.0f, orient + M_PI);
|
||||
bLoc.entry = entry;
|
||||
collected_locs++;
|
||||
for (int32 l = 1; l <= maxLevel; l++)
|
||||
{
|
||||
// Bots 1-60 go to base game bankers (all have minlevel 30 or 45)
|
||||
if (l <=60 && level > 45)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Bots 61-70 go to Shattrath bankers (all have minlevel 60 or 70)
|
||||
if ((l >=61 && l <=70) && (level < 60 || level > 70))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Bots 71+ go to Dalaran bankers (all have minlevel 75)
|
||||
if ((l >=71) && level != 75)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bankerLocsPerLevelCache[(uint8)l].push_back(bLoc);
|
||||
bankerEntryToLocation[bLoc.entry] = bLoc.loc;
|
||||
}
|
||||
} while (results->NextRow());
|
||||
}
|
||||
LOG_INFO("playerbots", ">> {} banker locations for level collected.", collected_locs);
|
||||
}
|
||||
|
||||
void RandomPlayerbotMgr::PrepareAddclassCache()
|
||||
{
|
||||
// Using accounts marked as type 2 (AddClass)
|
||||
@ -1757,6 +2125,11 @@ void RandomPlayerbotMgr::Init()
|
||||
if (sPlayerbotAIConfig.addClassCommand)
|
||||
sRandomPlayerbotMgr.PrepareAddclassCache();
|
||||
|
||||
if (sPlayerbotAIConfig.enabled)
|
||||
{
|
||||
sRandomPlayerbotMgr.PrepareTeleportCache();
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig.randomBotJoinBG)
|
||||
sRandomPlayerbotMgr.LoadBattleMastersCache();
|
||||
|
||||
@ -1768,17 +2141,103 @@ void RandomPlayerbotMgr::RandomTeleportForLevel(Player* bot)
|
||||
if (bot->InBattleground())
|
||||
return;
|
||||
|
||||
std::vector<WorldLocation> locs = sTravelMgr.GetCityLocations(bot);
|
||||
if (!locs.empty())
|
||||
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];
|
||||
if (level >= 10 && urand(0, 100) < sPlayerbotAIConfig.probTeleToBankers * 100)
|
||||
{
|
||||
RandomTeleport(bot, locs, true);
|
||||
return;
|
||||
std::vector<WorldLocation> fallbackLocs;
|
||||
for (auto& bLoc : bankerLocsPerLevelCache[level])
|
||||
fallbackLocs.push_back(bLoc.loc);
|
||||
|
||||
if (!sPlayerbotAIConfig.enableWeightTeleToCityBankers)
|
||||
{
|
||||
RandomTeleport(bot, fallbackLocs, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect valid cities based on bot faction.
|
||||
std::unordered_set<CityId> validBankerCities;
|
||||
for (auto& loc : bankerLocsPerLevelCache[level])
|
||||
{
|
||||
auto cityIt = bankerToCity.find(loc.entry);
|
||||
if (cityIt == bankerToCity.end()) continue;
|
||||
|
||||
CityId cityId = cityIt->second.first;
|
||||
FactionId cityFactionId = cityIt->second.second;
|
||||
|
||||
if ((IsAlliance(bot->getRace()) && cityFactionId == FactionId::ALLIANCE) ||
|
||||
(!IsAlliance(bot->getRace()) && cityFactionId == FactionId::HORDE) ||
|
||||
(cityFactionId == FactionId::NEUTRAL))
|
||||
{
|
||||
validBankerCities.insert(cityId);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback if no valid cities
|
||||
if (validBankerCities.empty())
|
||||
{
|
||||
RandomTeleport(bot, fallbackLocs, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply weights to valid cities
|
||||
std::vector<CityId> weightedCities;
|
||||
for (CityId city : validBankerCities)
|
||||
{
|
||||
int weight = 0;
|
||||
switch (city)
|
||||
{
|
||||
case CityId::STORMWIND: weight = sPlayerbotAIConfig.weightTeleToStormwind; break;
|
||||
case CityId::IRONFORGE: weight = sPlayerbotAIConfig.weightTeleToIronforge; break;
|
||||
case CityId::DARNASSUS: weight = sPlayerbotAIConfig.weightTeleToDarnassus; break;
|
||||
case CityId::EXODAR: weight = sPlayerbotAIConfig.weightTeleToExodar; break;
|
||||
case CityId::ORGRIMMAR: weight = sPlayerbotAIConfig.weightTeleToOrgrimmar; break;
|
||||
case CityId::UNDERCITY: weight = sPlayerbotAIConfig.weightTeleToUndercity; break;
|
||||
case CityId::THUNDER_BLUFF: weight = sPlayerbotAIConfig.weightTeleToThunderBluff; break;
|
||||
case CityId::SILVERMOON_CITY: weight = sPlayerbotAIConfig.weightTeleToSilvermoonCity; break;
|
||||
case CityId::SHATTRATH_CITY: weight = sPlayerbotAIConfig.weightTeleToShattrathCity; break;
|
||||
case CityId::DALARAN: weight = sPlayerbotAIConfig.weightTeleToDalaran; break;
|
||||
default: weight = 0; break;
|
||||
}
|
||||
if (weight <= 0) continue;
|
||||
|
||||
for (int i = 0; i < weight; ++i)
|
||||
{
|
||||
weightedCities.push_back(city);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback if no valid cities
|
||||
if (weightedCities.empty())
|
||||
{
|
||||
RandomTeleport(bot, fallbackLocs, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pick a weighted city randomly, then a random banker in that city
|
||||
// then teleport to that banker
|
||||
CityId selectedCity = weightedCities[urand(0, weightedCities.size() - 1)];
|
||||
auto const& bankers = cityToBankers.at(selectedCity);
|
||||
uint32 selectedBankerEntry = bankers[urand(0, bankers.size() - 1)];
|
||||
auto locIt = bankerEntryToLocation.find(selectedBankerEntry);
|
||||
if (locIt != bankerEntryToLocation.end())
|
||||
{
|
||||
std::vector<WorldLocation> teleportTarget = { locIt->second };
|
||||
RandomTeleport(bot, teleportTarget, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback if something went wrong
|
||||
RandomTeleport(bot, *locs);
|
||||
}
|
||||
locs = sTravelMgr.GetTeleportLocations(bot);
|
||||
if (!locs.empty())
|
||||
else
|
||||
{
|
||||
RandomTeleport(bot, locs, false);
|
||||
return;
|
||||
RandomTeleport(bot, *locs);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1787,11 +2246,17 @@ void RandomPlayerbotMgr::RandomTeleportGrindForLevel(Player* bot)
|
||||
if (bot->InBattleground())
|
||||
return;
|
||||
|
||||
std::vector<WorldLocation> locs = sTravelMgr.GetTeleportLocations(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(), locs.size());
|
||||
bot->GetLevel(), locs->size());
|
||||
|
||||
RandomTeleport(bot, locs);
|
||||
RandomTeleport(bot, *locs);
|
||||
}
|
||||
|
||||
void RandomPlayerbotMgr::RandomTeleport(Player* bot)
|
||||
|
||||
@ -164,8 +164,25 @@ public:
|
||||
static uint8 GetTeamClassIdx(bool isAlliance, uint8 claz) { return isAlliance * 20 + claz; }
|
||||
|
||||
void PrepareAddclassCache();
|
||||
void PrepareZone2LevelBracket();
|
||||
void PrepareTeleportCache();
|
||||
void Init();
|
||||
std::map<uint8, std::unordered_set<ObjectGuid>> addclassCache;
|
||||
std::map<uint8, std::vector<WorldLocation>> locsPerLevelCache;
|
||||
std::map<uint8, std::vector<WorldLocation>> allianceStarterPerLevelCache;
|
||||
std::map<uint8, std::vector<WorldLocation>> hordeStarterPerLevelCache;
|
||||
|
||||
struct LevelBracket {
|
||||
uint32 low;
|
||||
uint32 high;
|
||||
bool InsideBracket(uint32 val) { return val >= low && val <= high; }
|
||||
};
|
||||
std::map<uint32, LevelBracket> zone2LevelBracket;
|
||||
struct BankerLocation {
|
||||
WorldLocation loc;
|
||||
uint32 entry;
|
||||
};
|
||||
std::map<uint8, std::vector<BankerLocation>> bankerLocsPerLevelCache;
|
||||
|
||||
// Account type management
|
||||
void AssignAccountTypes();
|
||||
|
||||
39
src/Db/FlightMasterCache.cpp
Normal file
39
src/Db/FlightMasterCache.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
#include "FlightMasterCache.h"
|
||||
|
||||
void FlightMasterCache::AddHordeFlightMaster(uint32 entry, WorldPosition pos)
|
||||
{
|
||||
hordeFlightMasterCache[entry] = pos;
|
||||
}
|
||||
|
||||
void FlightMasterCache::AddAllianceFlightMaster(uint32 entry, WorldPosition pos)
|
||||
{
|
||||
allianceFlightMasterCache[entry] = pos;
|
||||
}
|
||||
|
||||
Creature* FlightMasterCache::GetNearestFlightMaster(Player* bot)
|
||||
{
|
||||
std::map<uint32, WorldPosition>& flightMasterCache =
|
||||
(bot->GetTeamId() == TEAM_ALLIANCE) ? allianceFlightMasterCache : hordeFlightMasterCache;
|
||||
|
||||
Creature* nearestFlightMaster = nullptr;
|
||||
float nearestDistance = std::numeric_limits<float>::max();
|
||||
|
||||
for (auto const& [entry, pos] : flightMasterCache)
|
||||
{
|
||||
if (pos.GetMapId() == bot->GetMapId())
|
||||
{
|
||||
float distance = bot->GetExactDist2dSq(pos);
|
||||
if (distance < nearestDistance)
|
||||
{
|
||||
Creature* flightMaster = ObjectAccessor::GetSpawnedCreatureByDBGUID(bot->GetMapId(), entry);
|
||||
if (flightMaster)
|
||||
{
|
||||
nearestDistance = distance;
|
||||
nearestFlightMaster = flightMaster;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nearestFlightMaster;
|
||||
}
|
||||
36
src/Db/FlightMasterCache.h
Normal file
36
src/Db/FlightMasterCache.h
Normal file
@ -0,0 +1,36 @@
|
||||
#ifndef _PLAYERBOT_FLIGHTMASTER_H
|
||||
#define _PLAYERBOT_FLIGHTMASTER_H
|
||||
|
||||
#include "Creature.h"
|
||||
#include "Player.h"
|
||||
#include "TravelMgr.h"
|
||||
|
||||
class FlightMasterCache
|
||||
{
|
||||
public:
|
||||
static FlightMasterCache& Instance()
|
||||
{
|
||||
static FlightMasterCache instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
Creature* GetNearestFlightMaster(Player* bot);
|
||||
void AddHordeFlightMaster(uint32 entry, WorldPosition pos);
|
||||
void AddAllianceFlightMaster(uint32 entry, WorldPosition pos);
|
||||
|
||||
private:
|
||||
FlightMasterCache() = default;
|
||||
~FlightMasterCache() = default;
|
||||
|
||||
FlightMasterCache(const FlightMasterCache&) = delete;
|
||||
FlightMasterCache& operator=(const FlightMasterCache&) = delete;
|
||||
|
||||
FlightMasterCache(FlightMasterCache&&) = delete;
|
||||
FlightMasterCache& operator=(FlightMasterCache&&) = delete;
|
||||
|
||||
std::map<uint32, WorldPosition> allianceFlightMasterCache;
|
||||
std::map<uint32, WorldPosition> hordeFlightMasterCache;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -66,29 +66,6 @@ private:
|
||||
Player* bot;
|
||||
};
|
||||
|
||||
class HasRelicBySubclassVisitor : public IterateItemsVisitor
|
||||
{
|
||||
public:
|
||||
HasRelicBySubclassVisitor(uint32 subClass) : subClass(subClass) {}
|
||||
|
||||
bool Visit(Item* item) override
|
||||
{
|
||||
ItemTemplate const* proto = item->GetTemplate();
|
||||
if (proto && proto->InventoryType == INVTYPE_RELIC && proto->SubClass == subClass)
|
||||
{
|
||||
found = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
|
||||
private:
|
||||
uint32 subClass;
|
||||
};
|
||||
|
||||
class FindItemsByQualityVisitor : public IterateItemsVisitor
|
||||
{
|
||||
public:
|
||||
|
||||
@ -8,10 +8,6 @@
|
||||
#include <iomanip>
|
||||
#include <numeric>
|
||||
|
||||
#include "Creature.h"
|
||||
#include "Log.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "TravelNode.h"
|
||||
#include "Talentspec.h"
|
||||
#include "ChatHelper.h"
|
||||
#include "MMapFactory.h"
|
||||
@ -26,71 +22,6 @@
|
||||
#include "Corpse.h"
|
||||
#include "CellImpl.h"
|
||||
|
||||
// Navigation data
|
||||
|
||||
enum class CityId : uint8
|
||||
{
|
||||
STORMWIND,
|
||||
IRONFORGE,
|
||||
DARNASSUS,
|
||||
EXODAR,
|
||||
ORGRIMMAR,
|
||||
UNDERCITY,
|
||||
THUNDER_BLUFF,
|
||||
SILVERMOON_CITY,
|
||||
SHATTRATH_CITY,
|
||||
DALARAN
|
||||
};
|
||||
|
||||
static const std::unordered_map<uint16, std::pair<CityId, TeamId>> bankerToCity = {
|
||||
{2455, {CityId::STORMWIND, TEAM_ALLIANCE}}, {2456, {CityId::STORMWIND, TEAM_ALLIANCE}}, {2457, {CityId::STORMWIND, TEAM_ALLIANCE}},
|
||||
{2460, {CityId::IRONFORGE, TEAM_ALLIANCE}}, {2461, {CityId::IRONFORGE, TEAM_ALLIANCE}}, {5099, {CityId::IRONFORGE, TEAM_ALLIANCE}},
|
||||
{4155, {CityId::DARNASSUS, TEAM_ALLIANCE}}, {4208, {CityId::DARNASSUS, TEAM_ALLIANCE}}, {4209, {CityId::DARNASSUS, TEAM_ALLIANCE}},
|
||||
{17773, {CityId::EXODAR, TEAM_ALLIANCE}}, {18350, {CityId::EXODAR, TEAM_ALLIANCE}}, {16710, {CityId::EXODAR, TEAM_ALLIANCE}},
|
||||
{3320, {CityId::ORGRIMMAR, TEAM_HORDE}}, {3309, {CityId::ORGRIMMAR, TEAM_HORDE}}, {3318, {CityId::ORGRIMMAR, TEAM_HORDE}},
|
||||
{4549, {CityId::UNDERCITY, TEAM_HORDE}}, {2459, {CityId::UNDERCITY, TEAM_HORDE}}, {2458, {CityId::UNDERCITY, TEAM_HORDE}}, {4550, {CityId::UNDERCITY, TEAM_HORDE}},
|
||||
{2996, {CityId::THUNDER_BLUFF, TEAM_HORDE}}, {8356, {CityId::THUNDER_BLUFF, TEAM_HORDE}}, {8357, {CityId::THUNDER_BLUFF, TEAM_HORDE}},
|
||||
{17631, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, {17632, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, {17633, {CityId::SILVERMOON_CITY, TEAM_HORDE}},
|
||||
{16615, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, {16616, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, {16617, {CityId::SILVERMOON_CITY, TEAM_HORDE}},
|
||||
{19246, {CityId::SHATTRATH_CITY, TEAM_NEUTRAL}}, {19338, {CityId::SHATTRATH_CITY, TEAM_NEUTRAL}},
|
||||
{19034, {CityId::SHATTRATH_CITY, TEAM_NEUTRAL}}, {19318, {CityId::SHATTRATH_CITY, TEAM_NEUTRAL}},
|
||||
{30604, {CityId::DALARAN, TEAM_NEUTRAL}}, {30605, {CityId::DALARAN, TEAM_NEUTRAL}}, {30607, {CityId::DALARAN, TEAM_NEUTRAL}},
|
||||
{28675, {CityId::DALARAN, TEAM_NEUTRAL}}, {28676, {CityId::DALARAN, TEAM_NEUTRAL}}, {28677, {CityId::DALARAN, TEAM_NEUTRAL}}
|
||||
};
|
||||
|
||||
static const std::unordered_map<CityId, std::vector<uint16>> cityToBankers = {
|
||||
{CityId::STORMWIND, {2455, 2456, 2457}},
|
||||
{CityId::IRONFORGE, {2460, 2461, 5099}},
|
||||
{CityId::DARNASSUS, {4155, 4208, 4209}},
|
||||
{CityId::EXODAR, {17773, 18350, 16710}},
|
||||
{CityId::ORGRIMMAR, {3320, 3309, 3318}},
|
||||
{CityId::UNDERCITY, {4549, 2459, 2458, 4550}},
|
||||
{CityId::THUNDER_BLUFF, {2996, 8356, 8357}},
|
||||
{CityId::SILVERMOON_CITY, {17631, 17632, 17633, 16615, 16616, 16617}},
|
||||
{CityId::SHATTRATH_CITY, {19246, 19338, 19034, 19318}},
|
||||
{CityId::DALARAN, {30604, 30605, 30607, 28675, 28676, 28677, 29530}}
|
||||
};
|
||||
|
||||
static int GetCityWeight(CityId city)
|
||||
{
|
||||
int weight = 0;
|
||||
switch (city)
|
||||
{
|
||||
case CityId::STORMWIND: weight = sPlayerbotAIConfig.weightTeleToStormwind; break;
|
||||
case CityId::IRONFORGE: weight = sPlayerbotAIConfig.weightTeleToIronforge; break;
|
||||
case CityId::DARNASSUS: weight = sPlayerbotAIConfig.weightTeleToDarnassus; break;
|
||||
case CityId::EXODAR: weight = sPlayerbotAIConfig.weightTeleToExodar; break;
|
||||
case CityId::ORGRIMMAR: weight = sPlayerbotAIConfig.weightTeleToOrgrimmar; break;
|
||||
case CityId::UNDERCITY: weight = sPlayerbotAIConfig.weightTeleToUndercity; break;
|
||||
case CityId::THUNDER_BLUFF: weight = sPlayerbotAIConfig.weightTeleToThunderBluff; break;
|
||||
case CityId::SILVERMOON_CITY: weight = sPlayerbotAIConfig.weightTeleToSilvermoonCity; break;
|
||||
case CityId::SHATTRATH_CITY: weight = sPlayerbotAIConfig.weightTeleToShattrathCity; break;
|
||||
case CityId::DALARAN: weight = sPlayerbotAIConfig.weightTeleToDalaran; break;
|
||||
default: weight = 0; break;
|
||||
}
|
||||
return weight;
|
||||
}
|
||||
|
||||
WorldPosition::WorldPosition(std::string const str)
|
||||
{
|
||||
std::vector<std::string> tokens = split(str, '|');
|
||||
@ -4356,434 +4287,3 @@ void TravelMgr::printObj(WorldObject* obj, std::string const type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TravelMgr::Init()
|
||||
{
|
||||
if (sPlayerbotAIConfig.enabled)
|
||||
{
|
||||
PrepareZone2LevelBracket();
|
||||
PrepareDestinationCache();
|
||||
}
|
||||
sTravelNodeMap.InitTaxiGraph();
|
||||
LOG_INFO("playerbots", "Playerbots Taxi graph and destination cache built.");
|
||||
}
|
||||
|
||||
Creature* TravelMgr::GetNearestFlightMaster(Player* bot)
|
||||
{
|
||||
std::map<uint32, WorldPosition>& flightMasterCache =
|
||||
(bot->GetTeamId() == TEAM_ALLIANCE) ? allianceFlightMasterCache : hordeFlightMasterCache;
|
||||
|
||||
Creature* nearestFlightMaster = nullptr;
|
||||
float nearestDistance = std::numeric_limits<float>::max();
|
||||
|
||||
for (auto const& [entry, pos] : flightMasterCache)
|
||||
{
|
||||
if (pos.GetMapId() != bot->GetMapId())
|
||||
continue;
|
||||
|
||||
float distance = bot->GetExactDist2dSq(pos);
|
||||
if (distance > nearestDistance)
|
||||
continue;
|
||||
|
||||
Creature* flightMaster = ObjectAccessor::GetSpawnedCreatureByDBGUID(bot->GetMapId(), entry);
|
||||
if (flightMaster)
|
||||
{
|
||||
nearestDistance = distance;
|
||||
nearestFlightMaster = flightMaster;
|
||||
}
|
||||
}
|
||||
|
||||
return nearestFlightMaster;
|
||||
}
|
||||
|
||||
ObjectGuid TravelMgr::GetNearestFlightMasterGuid(Player* bot)
|
||||
{
|
||||
Creature* nearestFlightMaster = GetNearestFlightMaster(bot);
|
||||
if (!nearestFlightMaster)
|
||||
return ObjectGuid::Empty;
|
||||
|
||||
return nearestFlightMaster->GetGUID();
|
||||
}
|
||||
|
||||
std::vector<std::vector<uint32>> TravelMgr::GetOptimalFlightDestinations(Player* bot)
|
||||
{
|
||||
std::vector<std::vector<uint32>> validDestinations;
|
||||
|
||||
Creature* nearestFlightMaster = GetNearestFlightMaster(bot);
|
||||
if (!nearestFlightMaster || bot->GetDistance(nearestFlightMaster) > 500.0f)
|
||||
return validDestinations;
|
||||
|
||||
uint32 fromNode = sObjectMgr->GetNearestTaxiNode(nearestFlightMaster->GetPositionX(), nearestFlightMaster->GetPositionY(),
|
||||
nearestFlightMaster->GetPositionZ(), nearestFlightMaster->GetMapId(),
|
||||
bot->GetTeamId());
|
||||
if (!fromNode)
|
||||
return validDestinations;
|
||||
std::vector<WorldLocation> candidateLocations;
|
||||
if (bot->GetLevel() >= 10 && urand(0, 100) < sPlayerbotAIConfig.probTeleToBankers * 100)
|
||||
candidateLocations = GetCityLocations(bot);
|
||||
|
||||
std::vector<WorldLocation> hubLocations = GetTravelHubs(bot);
|
||||
candidateLocations.insert(candidateLocations.end(), hubLocations.begin(), hubLocations.end());
|
||||
|
||||
for (auto const& loc : candidateLocations)
|
||||
{
|
||||
uint32 candidateNode = sObjectMgr->GetNearestTaxiNode(loc.GetPositionX(), loc.GetPositionY(),
|
||||
loc.GetPositionZ(), loc.GetMapId(),
|
||||
bot->GetTeamId());
|
||||
if (!candidateNode)
|
||||
continue;
|
||||
|
||||
std::vector<uint32> path = sTravelNodeMap.FindTaxiPath(fromNode, candidateNode);
|
||||
if (!path.empty())
|
||||
validDestinations.push_back(path);
|
||||
}
|
||||
return validDestinations;
|
||||
}
|
||||
|
||||
const std::vector<WorldLocation> TravelMgr::GetTeleportLocations(Player* bot)
|
||||
{
|
||||
uint32 level = bot->GetLevel();
|
||||
uint8 isAlliance = bot->GetTeamId() == TEAM_ALLIANCE;
|
||||
if (sPlayerbotAIConfig.enableNewRpgStrategy)
|
||||
return isAlliance ? allianceHubsPerLevelCache[level] : hordeHubsPerLevelCache[level];
|
||||
|
||||
return locsPerLevelCache[level];
|
||||
}
|
||||
|
||||
const std::vector<WorldLocation> TravelMgr::GetTravelHubs(Player* bot)
|
||||
{
|
||||
std::vector<WorldLocation> locs = bot->GetTeamId() == TEAM_ALLIANCE
|
||||
? allianceHubsPerLevelCache[bot->GetLevel()]
|
||||
: hordeHubsPerLevelCache[bot->GetLevel()];
|
||||
return locs;
|
||||
}
|
||||
|
||||
std::vector<WorldLocation> TravelMgr::GetCityLocations(Player* bot)
|
||||
{
|
||||
uint32 level = bot->GetLevel();
|
||||
|
||||
std::vector<WorldLocation> fallbackLocations;
|
||||
for (auto& bLoc : bankerLocsPerLevelCache[level])
|
||||
fallbackLocations.push_back(bLoc.loc);
|
||||
|
||||
if (!sPlayerbotAIConfig.enableWeightTeleToCityBankers)
|
||||
return fallbackLocations;
|
||||
|
||||
TeamId botTeamId = bot->GetTeamId();
|
||||
std::unordered_set<CityId> validBankerCities;
|
||||
for (auto& loc : bankerLocsPerLevelCache[level])
|
||||
{
|
||||
auto cityIt = bankerToCity.find(loc.entry);
|
||||
if (cityIt == bankerToCity.end())
|
||||
continue;
|
||||
|
||||
TeamId cityTeamId = cityIt->second.second;
|
||||
|
||||
if (cityTeamId == botTeamId ||
|
||||
(cityTeamId == TEAM_NEUTRAL)
|
||||
)
|
||||
validBankerCities.insert(cityIt->second.first);
|
||||
}
|
||||
// Fallback if no valid cities
|
||||
if (validBankerCities.empty())
|
||||
return fallbackLocations;
|
||||
|
||||
// Apply weights to valid cities
|
||||
std::vector<CityId> weightedCities;
|
||||
for (CityId city : validBankerCities)
|
||||
{
|
||||
int weight = GetCityWeight(city);
|
||||
if (weight <= 0)
|
||||
continue;
|
||||
|
||||
for (int i = 0; i < weight; ++i)
|
||||
weightedCities.push_back(city);
|
||||
}
|
||||
|
||||
// Fallback if no valid cities
|
||||
if (weightedCities.empty())
|
||||
return fallbackLocations;
|
||||
|
||||
// Pick a weighted city randomly, then a random banker in that city
|
||||
CityId selectedCity = weightedCities[urand(0, weightedCities.size() - 1)];
|
||||
|
||||
auto const& bankers = cityToBankers.at(selectedCity);
|
||||
uint32 selectedBankerEntry = bankers[urand(0, bankers.size() - 1)];
|
||||
auto locIt = bankerEntryToLocation.find(selectedBankerEntry);
|
||||
if (locIt != bankerEntryToLocation.end())
|
||||
return { locIt->second };
|
||||
// Fallback if something went wrong
|
||||
return fallbackLocations;
|
||||
}
|
||||
|
||||
void TravelMgr::PrepareZone2LevelBracket()
|
||||
{
|
||||
// Classic WoW - Low - level zones
|
||||
zone2LevelBracket[1] = {5, 12}; // Dun Morogh
|
||||
zone2LevelBracket[12] = {5, 12}; // Elwynn Forest
|
||||
zone2LevelBracket[14] = {5, 12}; // Durotar
|
||||
zone2LevelBracket[85] = {5, 12}; // Tirisfal Glades
|
||||
zone2LevelBracket[141] = {5, 12}; // Teldrassil
|
||||
zone2LevelBracket[215] = {5, 12}; // Mulgore
|
||||
zone2LevelBracket[3430] = {5, 12}; // Eversong Woods
|
||||
zone2LevelBracket[3524] = {5, 12}; // Azuremyst Isle
|
||||
|
||||
// Classic WoW - Mid - level zones
|
||||
zone2LevelBracket[17] = {10, 25}; // Barrens
|
||||
zone2LevelBracket[38] = {10, 20}; // Loch Modan
|
||||
zone2LevelBracket[40] = {10, 21}; // Westfall
|
||||
zone2LevelBracket[130] = {10, 23}; // Silverpine Forest
|
||||
zone2LevelBracket[148] = {10, 21}; // Darkshore
|
||||
zone2LevelBracket[3433] = {10, 22}; // Ghostlands
|
||||
zone2LevelBracket[3525] = {10, 21}; // Bloodmyst Isle
|
||||
|
||||
// Classic WoW - High - level zones
|
||||
zone2LevelBracket[10] = {19, 33}; // Deadwind Pass
|
||||
zone2LevelBracket[11] = {21, 30}; // Wetlands
|
||||
zone2LevelBracket[44] = {16, 28}; // Redridge Mountains
|
||||
zone2LevelBracket[267] = {20, 34}; // Hillsbrad Foothills
|
||||
zone2LevelBracket[331] = {18, 33}; // Ashenvale
|
||||
zone2LevelBracket[400] = {24, 36}; // Thousand Needles
|
||||
zone2LevelBracket[406] = {16, 29}; // Stonetalon Mountains
|
||||
|
||||
// Classic WoW - Higher - level zones
|
||||
zone2LevelBracket[3] = {36, 46}; // Badlands
|
||||
zone2LevelBracket[8] = {36, 46}; // Swamp of Sorrows
|
||||
zone2LevelBracket[15] = {35, 46}; // Dustwallow Marsh
|
||||
zone2LevelBracket[16] = {45, 52}; // Azshara
|
||||
zone2LevelBracket[33] = {32, 47}; // Stranglethorn Vale
|
||||
zone2LevelBracket[45] = {30, 42}; // Arathi Highlands
|
||||
zone2LevelBracket[47] = {42, 51}; // Hinterlands
|
||||
zone2LevelBracket[51] = {45, 51}; // Searing Gorge
|
||||
zone2LevelBracket[357] = {40, 52}; // Feralas
|
||||
zone2LevelBracket[405] = {30, 41}; // Desolace
|
||||
zone2LevelBracket[440] = {41, 52}; // Tanaris
|
||||
|
||||
// Classic WoW - Top - level zones
|
||||
zone2LevelBracket[4] = {52, 57}; // Blasted Lands
|
||||
zone2LevelBracket[28] = {50, 60}; // Western Plaguelands
|
||||
zone2LevelBracket[46] = {51, 60}; // Burning Steppes
|
||||
zone2LevelBracket[139] = {54, 62}; // Eastern Plaguelands
|
||||
zone2LevelBracket[361] = {47, 57}; // Felwood
|
||||
zone2LevelBracket[490] = {49, 56}; // Un'Goro Crater
|
||||
zone2LevelBracket[618] = {54, 61}; // Winterspring
|
||||
zone2LevelBracket[1377] = {54, 63}; // Silithus
|
||||
|
||||
// The Burning Crusade - Zones
|
||||
zone2LevelBracket[3483] = {58, 66}; // Hellfire Peninsula
|
||||
zone2LevelBracket[3518] = {64, 70}; // Nagrand
|
||||
zone2LevelBracket[3519] = {62, 73}; // Terokkar Forest
|
||||
zone2LevelBracket[3520] = {66, 73}; // Shadowmoon Valley
|
||||
zone2LevelBracket[3521] = {60, 67}; // Zangarmarsh
|
||||
zone2LevelBracket[3522] = {64, 73}; // Blade's Edge Mountains
|
||||
zone2LevelBracket[3523] = {67, 73}; // Netherstorm
|
||||
zone2LevelBracket[4080] = {68, 73}; // Isle of Quel'Danas
|
||||
|
||||
// Wrath of the Lich King - Zones
|
||||
zone2LevelBracket[65] = {71, 77}; // Dragonblight
|
||||
zone2LevelBracket[66] = {74, 80}; // Zul'Drak
|
||||
zone2LevelBracket[67] = {77, 80}; // Storm Peaks
|
||||
zone2LevelBracket[210] = {77, 80}; // Icecrown Glacier
|
||||
zone2LevelBracket[394] = {72, 78}; // Grizzly Hills
|
||||
zone2LevelBracket[495] = {68, 74}; // Howling Fjord
|
||||
zone2LevelBracket[2817] = {77, 80}; // Crystalsong Forest
|
||||
zone2LevelBracket[3537] = {68, 75}; // Borean Tundra
|
||||
zone2LevelBracket[3711] = {75, 80}; // Sholazar Basin
|
||||
zone2LevelBracket[4197] = {79, 80}; // Wintergrasp
|
||||
|
||||
// Override with values from config
|
||||
for (auto const& [zoneId, bracketPair] : sPlayerbotAIConfig.zoneBrackets)
|
||||
zone2LevelBracket[zoneId] = {bracketPair.first, bracketPair.second};
|
||||
}
|
||||
|
||||
void TravelMgr::PrepareDestinationCache()
|
||||
{
|
||||
uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
|
||||
uint32 flightMastersCount = 0;
|
||||
uint32 innkeepersCount = 0;
|
||||
uint32 bankerCount = 0;
|
||||
|
||||
LOG_INFO("playerbots", "Preparing destination caches for {} levels...", maxLevel);
|
||||
// Temporary map to group creatures by entry and area
|
||||
std::map<std::tuple<uint16, int32, int32, int32>, std::vector<CreatureData>> tempLocsCache;
|
||||
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> tempCreatureCache;
|
||||
for (auto const& [guid, creatureData] : sObjectMgr->GetAllCreatureData())
|
||||
{
|
||||
CreatureTemplate const* creatureTemplate = sObjectMgr->GetCreatureTemplate(creatureData.id1);
|
||||
if (!creatureTemplate)
|
||||
continue;
|
||||
|
||||
uint16 mapId = creatureData.mapid;
|
||||
if (std::find(sPlayerbotAIConfig.randomBotMaps.begin(), sPlayerbotAIConfig.randomBotMaps.end(), mapId)
|
||||
== sPlayerbotAIConfig.randomBotMaps.end())
|
||||
continue;
|
||||
|
||||
float x = creatureData.posX;
|
||||
float y = creatureData.posY;
|
||||
float z = creatureData.posZ;
|
||||
float orient = creatureData.orientation;
|
||||
uint32 templateEntry = creatureData.id1;
|
||||
|
||||
Map* map = sMapMgr->FindMap(mapId, 0);
|
||||
if (!map)
|
||||
continue;
|
||||
|
||||
AreaTableEntry const* area = sAreaTableStore.LookupEntry(map->GetAreaId(PHASEMASK_NORMAL, x, y, z));
|
||||
if (!area)
|
||||
continue;
|
||||
|
||||
uint32 areaId = area->zone ? area->zone : area->ID;
|
||||
|
||||
// CREATURES
|
||||
if (creatureTemplate->npcflag == 0 &&
|
||||
creatureTemplate->lootid != 0 &&
|
||||
creatureTemplate->maxlevel - creatureTemplate->minlevel < 3 &&
|
||||
creatureTemplate->Entry != 32820 && creatureTemplate->Entry != 24196 &&
|
||||
creatureTemplate->Entry != 30627 && creatureTemplate->Entry != 30617 &&
|
||||
creatureData.spawntimesecs < 1000 &&
|
||||
creatureTemplate->faction != 11 && creatureTemplate->faction != 71 &&
|
||||
creatureTemplate->faction != 79 && creatureTemplate->faction != 85 &&
|
||||
creatureTemplate->faction != 188 && creatureTemplate->faction != 1575 &&
|
||||
(creatureTemplate->unit_flags & 256) == 0 &&
|
||||
(creatureTemplate->unit_flags & 4096) == 0 &&
|
||||
creatureTemplate->rank == 0)
|
||||
{
|
||||
uint32 roundX = (x / 50.0f) * 10.0f;
|
||||
uint32 roundY = (y / 50.0f) * 10.0f;
|
||||
uint32 roundZ = (z / 50.0f) * 10.0f;
|
||||
tempLocsCache[std::make_tuple(mapId, roundX, roundY, roundZ)].push_back(creatureData);
|
||||
tempCreatureCache[templateEntry][areaId].push_back(WorldLocation(mapId, x, y, z));
|
||||
}
|
||||
// FLIGHT MASTERS
|
||||
else if ((creatureTemplate->npcflag & UNIT_NPC_FLAG_FLIGHTMASTER ||
|
||||
creatureTemplate->npcflag & UNIT_NPC_FLAG_INNKEEPER) &&
|
||||
creatureTemplate->Entry != 3838 && creatureTemplate->Entry != 29480)
|
||||
{
|
||||
FactionTemplateEntry const* factionEntry = sFactionTemplateStore.LookupEntry(creatureTemplate->faction);
|
||||
bool forHorde = !(factionEntry->hostileMask & 4);
|
||||
bool forAlliance = !(factionEntry->hostileMask & 2);
|
||||
|
||||
if (creatureTemplate->npcflag & UNIT_NPC_FLAG_FLIGHTMASTER)
|
||||
{
|
||||
WorldPosition pos(mapId, x, y, z, orient);
|
||||
if (forHorde)
|
||||
hordeFlightMasterCache[guid] = pos;
|
||||
|
||||
if (forAlliance)
|
||||
allianceFlightMasterCache[guid] = pos;
|
||||
flightMastersCount++;
|
||||
}
|
||||
else if (creatureTemplate->npcflag & UNIT_NPC_FLAG_INNKEEPER)
|
||||
{
|
||||
if (zone2LevelBracket.find(areaId) == zone2LevelBracket.end())
|
||||
continue;
|
||||
|
||||
LevelBracket bracket = zone2LevelBracket[areaId];
|
||||
WorldPosition loc(mapId, x + cos(orient) * 5.0f, y + sin(orient) * 5.0f, z + 0.5f, orient + M_PI);
|
||||
for (int i = bracket.low; i <= bracket.high; i++)
|
||||
{
|
||||
if (forHorde)
|
||||
hordeHubsPerLevelCache[i].push_back(loc);
|
||||
|
||||
if (forAlliance)
|
||||
allianceHubsPerLevelCache[i].push_back(loc);
|
||||
innkeepersCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// === BANKERS ===
|
||||
else if (creatureTemplate->npcflag & UNIT_NPC_FLAG_BANKER &&
|
||||
creatureTemplate->npcflag != 135298 &&
|
||||
creatureTemplate->minlevel != 55 &&
|
||||
creatureTemplate->minlevel != 65 &&
|
||||
creatureTemplate->faction != 35 && creatureTemplate->faction != 474 &&
|
||||
creatureTemplate->faction != 69 && creatureTemplate->faction != 57 &&
|
||||
creatureTemplate->Entry != 30606 && creatureTemplate->Entry != 30608 &&
|
||||
creatureTemplate->Entry != 29282)
|
||||
{
|
||||
BankerLocation bLoc;
|
||||
bLoc.loc = WorldLocation(mapId, x + cos(orient) * 6.0f, y + sin(orient) * 6.0f, z + 2.0f, orient + M_PI);
|
||||
bLoc.entry = templateEntry;
|
||||
uint32 level = (creatureTemplate->minlevel + creatureTemplate->maxlevel + 1) / 2;
|
||||
for (int32 l = 1; l <= maxLevel; l++)
|
||||
{
|
||||
// Bots 1-60 go to base game bankers (all have minlevel 30 or 45)
|
||||
if (l <=60 && level > 45)
|
||||
continue;
|
||||
|
||||
// Bots 61-70 go to Shattrath bankers (all have minlevel 60 or 70)
|
||||
if ((l >=61 && l <=70) && (level < 60 || level > 70))
|
||||
continue;
|
||||
|
||||
// Bots 71+ go to Dalaran bankers (all have minlevel 75)
|
||||
if ((l >=71) && level != 75)
|
||||
continue;
|
||||
|
||||
bankerLocsPerLevelCache[(uint8)l].push_back(bLoc);
|
||||
bankerEntryToLocation[bLoc.entry] = bLoc.loc;
|
||||
}
|
||||
bankerCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Process temporary caches
|
||||
for (auto const& [gridTuple, creatureDataList] : tempLocsCache)
|
||||
{
|
||||
if (creatureDataList.size() > 2)
|
||||
{
|
||||
CreatureTemplate const* creatureTemplate = sObjectMgr->GetCreatureTemplate(creatureDataList[0].id1);
|
||||
uint32 level = (creatureTemplate->minlevel + creatureTemplate->maxlevel + 1) / 2;
|
||||
for (int32 l = (int32)level - (int32)sPlayerbotAIConfig.randomBotTeleLowerLevel;
|
||||
l <= (int32)level + (int32)sPlayerbotAIConfig.randomBotTeleHigherLevel; l++)
|
||||
{
|
||||
if (l < 1 || l > maxLevel)
|
||||
continue;
|
||||
|
||||
locsPerLevelCache[(uint8)l].push_back(WorldLocation(std::get<0>(gridTuple)));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto const& [entry, areaMap] : tempCreatureCache)
|
||||
{
|
||||
for (auto const& [area, locList] : areaMap)
|
||||
{
|
||||
if (locList.size() > 3)
|
||||
continue;
|
||||
|
||||
float totalX = 0, totalY = 0, totalZ = 0;
|
||||
for (auto const& loc : locList)
|
||||
{
|
||||
totalX += loc.GetPositionX();
|
||||
totalY += loc.GetPositionY();
|
||||
totalZ += loc.GetPositionZ();
|
||||
}
|
||||
float avgX = totalX / locList.size();
|
||||
float avgY = totalY / locList.size();
|
||||
float avgZ = totalZ / locList.size();
|
||||
creatureSpawnsByTemplate[entry].push_back(WorldLocation(locList[0].GetMapId(), avgX, avgY, avgZ, 0));
|
||||
}
|
||||
}
|
||||
// Add travel hubs based on player start locations
|
||||
for (uint32 i = 1; i < sRaceMgr->GetMaxRaces(); 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)) & sRaceMgr->GetAllianceRaceMask())
|
||||
allianceHubsPerLevelCache[(uint8)l].push_back(pos);
|
||||
else
|
||||
hordeHubsPerLevelCache[(uint8)l].push_back(pos);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
LOG_INFO("playerbots", ">> {} flight masters and {} innkeepers and {} banker locations for level collected.", flightMastersCount, innkeepersCount, bankerCount);
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
#define _PLAYERBOT_TRAVELMGR_H
|
||||
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <map>
|
||||
#include <random>
|
||||
|
||||
#include "AiObject.h"
|
||||
@ -16,7 +15,6 @@
|
||||
#include "GridDefines.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
|
||||
class Creature;
|
||||
class GuidPosition;
|
||||
class ObjectGuid;
|
||||
class Quest;
|
||||
@ -856,16 +854,6 @@ public:
|
||||
void Clear();
|
||||
void LoadQuestTravelTable();
|
||||
|
||||
// Navigation
|
||||
void Init();
|
||||
Creature* GetNearestFlightMaster(Player* bot);
|
||||
ObjectGuid GetNearestFlightMasterGuid(Player* bot);
|
||||
std::vector<std::vector<uint32>> GetOptimalFlightDestinations(Player* bot);
|
||||
const std::vector<WorldLocation> GetTeleportLocations(Player* bot);
|
||||
const std::vector<WorldLocation> GetTravelHubs(Player* bot);
|
||||
std::vector<WorldLocation> GetCityLocations(Player* bot);
|
||||
const std::vector<WorldLocation>& GetLocsPerLevelCache(uint8 level) { return locsPerLevelCache[level]; }
|
||||
|
||||
template <class D, class W, class URBG>
|
||||
void weighted_shuffle(D first, D last, W first_weight, W last_weight, URBG&& g)
|
||||
{
|
||||
@ -955,37 +943,6 @@ private:
|
||||
|
||||
TravelMgr(TravelMgr&&) = delete;
|
||||
TravelMgr& operator=(TravelMgr&&) = delete;
|
||||
|
||||
// Navigation initialization
|
||||
void PrepareZone2LevelBracket();
|
||||
void PrepareDestinationCache();
|
||||
|
||||
// Internal types
|
||||
struct LevelBracket
|
||||
{
|
||||
uint32 low;
|
||||
uint32 high;
|
||||
bool InsideBracket(uint32 val) const { return val >= low && val <= high; }
|
||||
};
|
||||
|
||||
struct BankerLocation
|
||||
{
|
||||
WorldLocation loc;
|
||||
uint32 entry;
|
||||
};
|
||||
|
||||
// Navigation caches
|
||||
std::map<uint32, WorldPosition> allianceFlightMasterCache;
|
||||
std::map<uint32, WorldPosition> hordeFlightMasterCache;
|
||||
std::map<uint8, std::vector<WorldLocation>> allianceHubsPerLevelCache;
|
||||
std::map<uint8, std::vector<WorldLocation>> hordeHubsPerLevelCache;
|
||||
std::map<uint8, std::vector<BankerLocation>> bankerLocsPerLevelCache;
|
||||
std::unordered_map<uint32, WorldLocation> bankerEntryToLocation;
|
||||
std::map<uint8, std::vector<WorldLocation>> locsPerLevelCache;
|
||||
std::unordered_map<uint32, std::vector<WorldLocation>> creatureSpawnsByTemplate;
|
||||
std::map<uint32, LevelBracket> zone2LevelBracket;
|
||||
};
|
||||
|
||||
#define sTravelMgr TravelMgr::instance()
|
||||
|
||||
#endif
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "BudgetValues.h"
|
||||
#include "PathGenerator.h"
|
||||
@ -2448,127 +2447,3 @@ WorldPosition TravelNodeMap::getMapOffset(uint32 mapId)
|
||||
|
||||
return WorldPosition(mapId, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// TravelNodeMap taxi graph (BFS-based flight path lookup)
|
||||
// ============================================================
|
||||
|
||||
void TravelNodeMap::InitTaxiGraph()
|
||||
{
|
||||
BuildTaxiGraph();
|
||||
ComputeAllPaths();
|
||||
}
|
||||
|
||||
std::vector<uint32> TravelNodeMap::FindTaxiPath(uint32 fromNode, uint32 toNode)
|
||||
{
|
||||
if (fromNode == toNode)
|
||||
return {};
|
||||
|
||||
TaxiNodesEntry const* startNode = sTaxiNodesStore.LookupEntry(fromNode);
|
||||
TaxiNodesEntry const* endNode = sTaxiNodesStore.LookupEntry(toNode);
|
||||
|
||||
if (!startNode || !endNode || startNode->map_id != endNode->map_id)
|
||||
return {};
|
||||
|
||||
auto cacheItr = taxiPathCache.find(fromNode);
|
||||
if (cacheItr == taxiPathCache.end())
|
||||
return {};
|
||||
|
||||
auto toNodeItr = cacheItr->second.find(toNode);
|
||||
if (toNodeItr == cacheItr->second.end())
|
||||
return {};
|
||||
|
||||
return toNodeItr->second;
|
||||
}
|
||||
|
||||
void TravelNodeMap::BuildTaxiGraph()
|
||||
{
|
||||
taxiGraph.clear();
|
||||
std::unordered_map<uint32, std::unordered_set<uint32>> tempGraph;
|
||||
for (uint32 i = 0; i < sTaxiPathStore.GetNumRows(); ++i)
|
||||
{
|
||||
TaxiPathEntry const* path = sTaxiPathStore.LookupEntry(i);
|
||||
if (!path)
|
||||
continue;
|
||||
|
||||
if (path->to == 0 || path->to == uint32(-1))
|
||||
continue;
|
||||
|
||||
tempGraph[path->from].insert(path->to);
|
||||
tempGraph[path->to].insert(path->from);
|
||||
}
|
||||
for (auto const& [node, neighbors] : tempGraph)
|
||||
taxiGraph[node] = std::vector<uint32>(neighbors.begin(), neighbors.end());
|
||||
}
|
||||
|
||||
void TravelNodeMap::ComputeAllPaths()
|
||||
{
|
||||
std::set<uint32> allNodes;
|
||||
for (auto const& [source, neighbors] : taxiGraph)
|
||||
allNodes.insert(source);
|
||||
|
||||
for (uint32 source : allNodes)
|
||||
{
|
||||
auto parentMap = BFS(source);
|
||||
|
||||
for (uint32 target : allNodes)
|
||||
{
|
||||
if (source == target)
|
||||
continue;
|
||||
|
||||
auto path = BuildPath(source, target, parentMap);
|
||||
if (!path.empty())
|
||||
taxiPathCache[source][target] = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<uint32, uint32> TravelNodeMap::BFS(uint32 fromNode)
|
||||
{
|
||||
std::queue<uint32> workQueue;
|
||||
std::unordered_set<uint32> visited;
|
||||
std::unordered_map<uint32, uint32> parentMap;
|
||||
|
||||
workQueue.push(fromNode);
|
||||
visited.insert(fromNode);
|
||||
parentMap[fromNode] = 0;
|
||||
|
||||
while (!workQueue.empty())
|
||||
{
|
||||
uint32 current = workQueue.front();
|
||||
workQueue.pop();
|
||||
|
||||
for (uint32 next : taxiGraph.at(current))
|
||||
{
|
||||
if (visited.count(next))
|
||||
continue;
|
||||
|
||||
visited.insert(next);
|
||||
parentMap[next] = current;
|
||||
workQueue.push(next);
|
||||
}
|
||||
}
|
||||
return parentMap;
|
||||
}
|
||||
|
||||
std::vector<uint32> TravelNodeMap::BuildPath(uint32 fromNode, uint32 toNode,
|
||||
const std::unordered_map<uint32, uint32>& parentMap)
|
||||
{
|
||||
if (!parentMap.count(toNode))
|
||||
return {}; // unreachable
|
||||
|
||||
std::vector<uint32> path;
|
||||
uint32 current = toNode;
|
||||
while (current != fromNode)
|
||||
{
|
||||
path.push_back(current);
|
||||
auto it = parentMap.find(current);
|
||||
if (it == parentMap.end() || it->second == 0)
|
||||
break;
|
||||
current = it->second;
|
||||
}
|
||||
|
||||
path.push_back(fromNode);
|
||||
std::reverse(path.begin(), path.end());
|
||||
return path;
|
||||
}
|
||||
|
||||
@ -580,10 +580,6 @@ public:
|
||||
void calcMapOffset();
|
||||
WorldPosition getMapOffset(uint32 mapId);
|
||||
|
||||
// Taxi graph (BFS-based path lookup between taxi nodes)
|
||||
void InitTaxiGraph();
|
||||
std::vector<uint32> FindTaxiPath(uint32 fromNode, uint32 toNode);
|
||||
|
||||
std::shared_timed_mutex m_nMapMtx;
|
||||
std::unordered_map<ObjectGuid, std::unordered_map<uint32, TravelNode*>> teleportNodes;
|
||||
|
||||
@ -597,16 +593,6 @@ private:
|
||||
TravelNodeMap(TravelNodeMap&&) = delete;
|
||||
TravelNodeMap& operator=(TravelNodeMap&&) = delete;
|
||||
|
||||
// Taxi graph internals
|
||||
void BuildTaxiGraph();
|
||||
void ComputeAllPaths();
|
||||
std::unordered_map<uint32, uint32> BFS(uint32 startNode);
|
||||
std::vector<uint32> BuildPath(uint32 fromNode, uint32 toNode,
|
||||
const std::unordered_map<uint32, uint32>& parentMap);
|
||||
|
||||
std::unordered_map<uint32, std::vector<uint32>> taxiGraph;
|
||||
std::map<uint32, std::map<uint32, std::vector<uint32>>> taxiPathCache;
|
||||
|
||||
std::vector<TravelNode*> m_nodes;
|
||||
|
||||
std::vector<std::pair<uint32, WorldPosition>> mapOffsets;
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
#include "RandomPlayerbotFactory.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "Talentspec.h"
|
||||
#include "TravelMgr.h"
|
||||
|
||||
template <class T>
|
||||
void LoadList(std::string const value, T& list)
|
||||
@ -621,10 +620,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
|
||||
// SPP automation
|
||||
freeMethodLoot = sConfigMgr->GetOption<bool>("AiPlayerbot.FreeMethodLoot", false);
|
||||
lootNeedRollLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.LootNeedRollLevel", 1);
|
||||
lootRollRecipe = sConfigMgr->GetOption<bool>("AiPlayerbot.LootRollRecipe", false);
|
||||
lootRollDisenchant = sConfigMgr->GetOption<bool>("AiPlayerbot.LootRollDisenchant", false);
|
||||
lootGreedRollLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.LootGreedRollLevel", false);
|
||||
lootRollLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.LootRollLevel", 1);
|
||||
autoPickReward = sConfigMgr->GetOption<std::string>("AiPlayerbot.AutoPickReward", "yes");
|
||||
autoEquipUpgradeLoot = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoEquipUpgradeLoot", true);
|
||||
equipUpgradeThreshold = sConfigMgr->GetOption<float>("AiPlayerbot.EquipUpgradeThreshold", 1.1f);
|
||||
@ -692,7 +688,6 @@ bool PlayerbotAIConfig::Initialize()
|
||||
{
|
||||
PlayerbotDungeonRepository::instance().LoadDungeonSuggestions();
|
||||
}
|
||||
sTravelMgr.Init();
|
||||
|
||||
excludedHunterPetFamilies.clear();
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.ExcludedHunterPetFamilies", ""), excludedHunterPetFamilies);
|
||||
|
||||
@ -346,10 +346,7 @@ public:
|
||||
uint32 botActiveAloneSmartScaleWhenMaxLevel;
|
||||
|
||||
bool freeMethodLoot;
|
||||
int32 lootNeedRollLevel;
|
||||
bool lootGreedRollLevel;
|
||||
bool lootRollRecipe;
|
||||
bool lootRollDisenchant;
|
||||
int32 lootRollLevel;
|
||||
std::string autoPickReward;
|
||||
bool autoEquipUpgradeLoot;
|
||||
float equipUpgradeThreshold;
|
||||
|
||||
@ -112,10 +112,13 @@ public:
|
||||
|
||||
if (sPlayerbotAIConfig.enabled || sPlayerbotAIConfig.randomBotAutologin)
|
||||
{
|
||||
std::string maxAllowedBotCount = std::to_string(sRandomPlayerbotMgr.GetMaxAllowedBotCount());
|
||||
std::string roundedTime =
|
||||
std::to_string(std::ceil((sPlayerbotAIConfig.maxRandomBots * 0.11 / 60) * 10) / 10.0);
|
||||
roundedTime = roundedTime.substr(0, roundedTime.find('.') + 2);
|
||||
|
||||
ChatHandler(player->GetSession()).SendSysMessage(
|
||||
"|cff00ff00Playerbots:|r The server is configured with " + maxAllowedBotCount + " bots.");
|
||||
"|cff00ff00Playerbots:|r bot initialization at server startup takes about '"
|
||||
+ roundedTime + "' minutes.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user