mxwow modules

This commit is contained in:
mikx
2025-03-24 23:53:45 -04:00
parent 93073b0be2
commit e87efbaf6e
99 changed files with 4909 additions and 814 deletions

View File

@@ -1,12 +1,22 @@
INSERT INTO `playerbots_travelnode` (`id`, `name`, `map_id`, `x`, `y`, `z`, `linked`) VALUES
(3780, 'Highlord Mograine', 533, 2524.32, -2951.28, 245.633, 1);
-- Delete existing entries from playerbots_travelnode, playerbots_travelnode_path, and playerbots_travelnode_link tables
DELETE FROM `playerbots_travelnode_link` WHERE `node_id` = 3780;
DELETE FROM `playerbots_travelnode_path` WHERE `node_id` = 3780;
DELETE FROM `playerbots_travelnode` WHERE `id` = 3780;
INSERT INTO `playerbots_travelnode_path` (`node_id`, `to_node_id`, `nr`, `map_id`, `x`, `y`, `z`) VALUES
-- Insert new entries into playerbots_travelnode
INSERT INTO `playerbots_travelnode` (`id`, `name`, `map_id`, `x`, `y`, `z`, `linked`)
VALUES (3780, 'Highlord Mograine', 533, 2524.32, -2951.28, 245.633, 1);
-- Insert new entries into playerbots_travelnode_path
INSERT INTO `playerbots_travelnode_path` (`node_id`, `to_node_id`, `nr`, `map_id`, `x`, `y`, `z`)
VALUES
(3780, 472, 0, 533, 2524.32, -2951.28, 245.633),
(3780, 472, 1, 533, 2528.79, -2948.58, 245.633),
(3780, 757, 0, 533, 2524.32, -2951.28, 245.633),
(3780, 757, 1, 533, 2517.62, -2959.38, 245.636);
INSERT INTO `playerbots_travelnode_link` (`node_id`, `to_node_id`, `type`, `object`, `distance`, `swim_distance`, `extra_cost`, `calculated`, `max_creature_0`, `max_creature_1`, `max_creature_2`) VALUES
-- Insert new entries into playerbots_travelnode_link
INSERT INTO `playerbots_travelnode_link` (`node_id`, `to_node_id`, `type`, `object`, `distance`, `swim_distance`, `extra_cost`, `calculated`, `max_creature_0`, `max_creature_1`, `max_creature_2`)
VALUES
(3780, 472, 1, 0, 5.3221, 0, 0, 1, 83, 0, 0),
(3780, 757, 1, 0, 10.6118, 0, 0, 1, 83, 0, 0);

View File

@@ -9,7 +9,7 @@
#include "Playerbots.h"
#include "Unit.h"
#define MAX_LOOT_OBJECT_COUNT 10
#define MAX_LOOT_OBJECT_COUNT 200
LootTarget::LootTarget(ObjectGuid guid) : guid(guid), asOfTime(time(nullptr)) {}

View File

@@ -14,10 +14,13 @@
#include "BudgetValues.h"
#include "ChannelMgr.h"
#include "CharacterPackets.h"
#include "Common.h"
#include "CreatureAIImpl.h"
#include "CreatureData.h"
#include "EmoteAction.h"
#include "Engine.h"
#include "ExternalEventHelper.h"
#include "GameObjectData.h"
#include "GameTime.h"
#include "GuildMgr.h"
#include "GuildTaskMgr.h"
@@ -32,6 +35,7 @@
#include "MoveSplineInit.h"
#include "NewRpgStrategy.h"
#include "ObjectGuid.h"
#include "ObjectMgr.h"
#include "PerformanceMonitor.h"
#include "Player.h"
#include "PlayerbotAIConfig.h"
@@ -174,6 +178,7 @@ PlayerbotAI::PlayerbotAI(Player* bot)
botOutgoingPacketHandlers.AddHandler(SMSG_RESURRECT_REQUEST, "resurrect request");
botOutgoingPacketHandlers.AddHandler(SMSG_INVENTORY_CHANGE_FAILURE, "cannot equip");
botOutgoingPacketHandlers.AddHandler(SMSG_TRADE_STATUS, "trade status");
botOutgoingPacketHandlers.AddHandler(SMSG_TRADE_STATUS_EXTENDED, "trade status extended");
botOutgoingPacketHandlers.AddHandler(SMSG_LOOT_RESPONSE, "loot response");
botOutgoingPacketHandlers.AddHandler(SMSG_ITEM_PUSH_RESULT, "item push result");
botOutgoingPacketHandlers.AddHandler(SMSG_PARTY_COMMAND_RESULT, "party command");
@@ -204,7 +209,7 @@ PlayerbotAI::PlayerbotAI(Player* bot)
masterIncomingPacketHandlers.AddHandler(CMSG_PUSHQUESTTOPARTY, "quest share");
botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_COMPLETE, "quest update complete");
botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_KILL, "quest update add kill");
botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_ITEM, "quest update add item");
// botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_ITEM, "quest update add item"); // SMSG_QUESTUPDATE_ADD_ITEM no longer used
botOutgoingPacketHandlers.AddHandler(SMSG_QUEST_CONFIRM_ACCEPT, "confirm quest");
}
@@ -1100,9 +1105,9 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
}
}
QueueChatResponse(
ChatQueuedReply{msgtype, guid1.GetCounter(), guid2.GetCounter(), message, chanName,
name, time(nullptr) + urand(inCombat ? 10 : 5, inCombat ? 25 : 15)});
QueueChatResponse(ChatQueuedReply{msgtype, guid1.GetCounter(), guid2.GetCounter(), message,
chanName, name,
time(nullptr) + urand(inCombat ? 10 : 5, inCombat ? 25 : 15)});
GetAiObjectContext()->GetValue<time_t>("last said", "chat")->Set(time(0) + urand(5, 25));
return;
}
@@ -1239,10 +1244,10 @@ void PlayerbotAI::ChangeEngine(BotState type)
switch (type)
{
case BOT_STATE_COMBAT:
// LOG_DEBUG("playerbots", "=== {} COMBAT ===", bot->GetName().c_str());
ChangeEngineOnCombat();
break;
case BOT_STATE_NON_COMBAT:
// LOG_DEBUG("playerbots", "=== {} NON-COMBAT ===", bot->GetName().c_str());
ChangeEngineOnNonCombat();
break;
case BOT_STATE_DEAD:
// LOG_DEBUG("playerbots", "=== {} DEAD ===", bot->GetName().c_str());
@@ -1253,6 +1258,23 @@ void PlayerbotAI::ChangeEngine(BotState type)
}
}
void PlayerbotAI::ChangeEngineOnCombat()
{
if (HasStrategy("stay", BOT_STATE_COMBAT))
{
aiObjectContext->GetValue<PositionInfo>("pos", "stay")
->Set(PositionInfo(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId()));
}
}
void PlayerbotAI::ChangeEngineOnNonCombat()
{
if (HasStrategy("stay", BOT_STATE_NON_COMBAT))
{
aiObjectContext->GetValue<PositionInfo>("pos", "stay")->Reset();
}
}
void PlayerbotAI::DoNextAction(bool min)
{
if (!bot->IsInWorld() || bot->IsBeingTeleported() || (GetMaster() && GetMaster()->IsBeingTeleported()))
@@ -2287,10 +2309,46 @@ const AreaTableEntry* PlayerbotAI::GetCurrentZone()
std::string PlayerbotAI::GetLocalizedAreaName(const AreaTableEntry* entry)
{
std::string name;
if (entry)
return entry->area_name[sWorld->GetDefaultDbcLocale()];
{
name = entry->area_name[sWorld->GetDefaultDbcLocale()];
if (name.empty())
name = entry->area_name[LOCALE_enUS];
}
return "";
return name;
}
std::string PlayerbotAI::GetLocalizedCreatureName(uint32 entry)
{
std::string name;
const CreatureLocale* cl = sObjectMgr->GetCreatureLocale(entry);
if (cl)
ObjectMgr::GetLocaleString(cl->Name, sWorld->GetDefaultDbcLocale(), name);
if (name.empty())
{
CreatureTemplate const* ct = sObjectMgr->GetCreatureTemplate(entry);
if (ct)
name = ct->Name;
}
return name;
}
std::string PlayerbotAI::GetLocalizedGameObjectName(uint32 entry)
{
std::string name;
const GameObjectLocale* gl = sObjectMgr->GetGameObjectLocale(entry);
if (gl)
ObjectMgr::GetLocaleString(gl->Name, sWorld->GetDefaultDbcLocale(), name);
if (name.empty())
{
GameObjectTemplate const* gt = sObjectMgr->GetGameObjectTemplate(entry);
if (gt)
name = gt->name;
}
return name;
}
std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
@@ -2807,7 +2865,6 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell,
if (!target)
target = bot;
if (Pet* pet = bot->GetPet())
if (pet->HasSpell(spellid))
return true;
@@ -3000,8 +3057,7 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, GameObject* goTarget, bool checkH
return false;
}
bool PlayerbotAI::CanCastSpell(uint32 spellid, float x, float y, float z, bool checkHasSpell,
Item* itemTarget)
bool PlayerbotAI::CanCastSpell(uint32 spellid, float x, float y, float z, bool checkHasSpell, Item* itemTarget)
{
if (!spellid)
return false;
@@ -3029,7 +3085,7 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, float x, float y, float z, bool c
Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE);
spell->m_targets.SetDst(x, y, z, 0.f);
Item* item = itemTarget ? itemTarget : aiObjectContext->GetValue<Item*>("item for spell", spellid)->Get();
spell->m_targets.SetItemTarget(item);
@@ -3133,7 +3189,8 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE);
SpellCastTargets targets;
if (spellInfo->Targets & TARGET_FLAG_ITEM)
if (spellInfo->Effects[0].Effect != SPELL_EFFECT_OPEN_LOCK &&
(spellInfo->Targets & TARGET_FLAG_ITEM || spellInfo->Targets & TARGET_FLAG_GAMEOBJECT_ITEM))
{
Item* item = itemTarget ? itemTarget : aiObjectContext->GetValue<Item*>("item for spell", spellId)->Get();
targets.SetItemTarget(item);
@@ -3176,6 +3233,20 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
targets.SetGOTarget(go);
faceTo = go;
}
else if (itemTarget)
{
Player* trader = bot->GetTrader();
if (trader)
{
targets.SetTradeItemTarget(bot);
targets.SetUnitTarget(bot);
faceTo = trader;
}
else
{
targets.SetItemTarget(itemTarget);
}
}
else
{
if (Unit* creature = GetUnit(loot.guid))
@@ -3212,6 +3283,58 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
// LOG_DEBUG("playerbots", "Spell cast failed. - target name: {}, spellid: {}, bot name: {}, result: {}",
// target->GetName(), spellId, bot->GetName(), result);
// }
if (HasStrategy("debug spell", BOT_STATE_NON_COMBAT))
{
std::ostringstream out;
out << "Spell cast failed - ";
out << "Spell ID: " << spellId << " (" << ChatHelper::FormatSpell(spellInfo) << "), ";
out << "Error Code: " << static_cast<int>(result) << " (0x" << std::hex << static_cast<int>(result) << std::dec << "), ";
out << "Bot: " << bot->GetName() << ", ";
// Check spell target type
if (targets.GetUnitTarget())
{
out << "Target: Unit (" << targets.GetUnitTarget()->GetName()
<< ", Low GUID: " << targets.GetUnitTarget()->GetGUID().GetCounter()
<< ", High GUID: " << static_cast<uint32>(targets.GetUnitTarget()->GetGUID().GetHigh()) << "), ";
}
if (targets.GetGOTarget())
{
out << "Target: GameObject (Low GUID: " << targets.GetGOTarget()->GetGUID().GetCounter()
<< ", High GUID: " << static_cast<uint32>(targets.GetGOTarget()->GetGUID().GetHigh()) << "), ";
}
if (targets.GetItemTarget())
{
out << "Target: Item (Low GUID: " << targets.GetItemTarget()->GetGUID().GetCounter()
<< ", High GUID: " << static_cast<uint32>(targets.GetItemTarget()->GetGUID().GetHigh()) << "), ";
}
// Check if bot is in trade mode
if (bot->GetTradeData())
{
out << "Trade Mode: Active, ";
Item* tradeItem = bot->GetTradeData()->GetTraderData()->GetItem(TRADE_SLOT_NONTRADED);
if (tradeItem)
{
out << "Trade Item: " << tradeItem->GetEntry()
<< " (Low GUID: " << tradeItem->GetGUID().GetCounter()
<< ", High GUID: " << static_cast<uint32>(tradeItem->GetGUID().GetHigh()) << "), ";
}
else
{
out << "Trade Item: None, ";
}
}
else
{
out << "Trade Mode: Inactive, ";
}
TellMasterNoFacing(out);
}
return false;
}
// if (spellInfo->Effects[0].Effect == SPELL_EFFECT_OPEN_LOCK || spellInfo->Effects[0].Effect ==
@@ -3308,7 +3431,7 @@ bool PlayerbotAI::CastSpell(uint32 spellId, float x, float y, float z, Item* ite
Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE);
SpellCastTargets targets;
if (spellInfo->Targets & TARGET_FLAG_ITEM)
if (spellInfo->Targets & TARGET_FLAG_ITEM || spellInfo->Targets & TARGET_FLAG_GAMEOBJECT_ITEM)
{
Item* item = itemTarget ? itemTarget : aiObjectContext->GetValue<Item*>("item for spell", spellId)->Get();
targets.SetItemTarget(item);
@@ -4188,8 +4311,8 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
mod = AutoScaleActivity(mod);
}
uint32 ActivityNumber = GetFixedBotNumer(100,
sPlayerbotAIConfig->botActiveAlone * static_cast<float>(mod) / 100 * 0.01f);
uint32 ActivityNumber =
GetFixedBotNumer(100, sPlayerbotAIConfig->botActiveAlone * static_cast<float>(mod) / 100 * 0.01f);
return ActivityNumber <=
(sPlayerbotAIConfig->botActiveAlone * mod) /
@@ -4248,7 +4371,8 @@ void PlayerbotAI::RemoveShapeshift()
RemoveAura("moonkin form");
RemoveAura("travel form");
RemoveAura("cat form");
RemoveAura("flight form"); bot->RemoveAura(33943); // The latter added for now as RemoveAura("flight form") currently does not work.
RemoveAura("flight form");
bot->RemoveAura(33943); // The latter added for now as RemoveAura("flight form") currently does not work.
RemoveAura("swift flight form");
RemoveAura("aquatic form");
RemoveAura("ghost wolf");
@@ -4835,11 +4959,9 @@ Item* PlayerbotAI::FindAmmo() const
}
// Search inventory for the correct ammo type
return FindItemInInventory([requiredAmmoType](ItemTemplate const* pItemProto) -> bool
{
return pItemProto->Class == ITEM_CLASS_PROJECTILE &&
pItemProto->SubClass == requiredAmmoType;
});
return FindItemInInventory(
[requiredAmmoType](ItemTemplate const* pItemProto) -> bool
{ return pItemProto->Class == ITEM_CLASS_PROJECTILE && pItemProto->SubClass == requiredAmmoType; });
}
return nullptr; // No ranged weapon equipped
@@ -4864,6 +4986,52 @@ Item* PlayerbotAI::FindBandage() const
{ return pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_BANDAGE; });
}
Item* PlayerbotAI::FindOpenableItem() const
{
return FindItemInInventory([this](ItemTemplate const* itemTemplate) -> bool
{
return (itemTemplate->Flags & ITEM_FLAG_HAS_LOOT) &&
(itemTemplate->LockID == 0 || !this->bot->GetItemByEntry(itemTemplate->ItemId)->IsLocked());
});
}
Item* PlayerbotAI::FindLockedItem() const
{
return FindItemInInventory([this](ItemTemplate const* itemTemplate) -> bool
{
if (!this->bot->HasSkill(SKILL_LOCKPICKING)) // Ensure bot has Lockpicking skill
return false;
if (itemTemplate->LockID == 0) // Ensure the item is actually locked
return false;
Item* item = this->bot->GetItemByEntry(itemTemplate->ItemId);
if (!item || !item->IsLocked()) // Ensure item instance is locked
return false;
// Check if bot has enough Lockpicking skill
LockEntry const* lockInfo = sLockStore.LookupEntry(itemTemplate->LockID);
if (!lockInfo)
return false;
for (uint8 j = 0; j < 8; ++j)
{
if (lockInfo->Type[j] == LOCK_KEY_SKILL)
{
uint32 skillId = SkillByLockType(LockType(lockInfo->Index[j]));
if (skillId == SKILL_LOCKPICKING)
{
uint32 requiredSkill = lockInfo->Skill[j];
uint32 botSkill = this->bot->GetSkillValue(SKILL_LOCKPICKING);
return botSkill >= requiredSkill;
}
}
}
return false;
});
}
static const uint32 uPriorizedSharpStoneIds[8] = {ADAMANTITE_SHARPENING_DISPLAYID, FEL_SHARPENING_DISPLAYID,
ELEMENTAL_SHARPENING_DISPLAYID, DENSE_SHARPENING_DISPLAYID,
SOLID_SHARPENING_DISPLAYID, HEAVY_SHARPENING_DISPLAYID,
@@ -6024,9 +6192,10 @@ bool PlayerbotAI::IsHealingSpell(uint32 spellFamilyName, flag96 spellFalimyFlags
return spellFalimyFlags & healingFlags;
}
SpellFamilyNames PlayerbotAI::Class2SpellFamilyName(uint8 cls) {
switch (cls) {
SpellFamilyNames PlayerbotAI::Class2SpellFamilyName(uint8 cls)
{
switch (cls)
{
case CLASS_WARRIOR:
return SPELLFAMILY_WARRIOR;
case CLASS_PALADIN:

View File

@@ -13,8 +13,10 @@
#include "ChatFilter.h"
#include "ChatHelper.h"
#include "Common.h"
#include "CreatureData.h"
#include "Event.h"
#include "Item.h"
#include "NewRpgInfo.h"
#include "NewRpgStrategy.h"
#include "PlayerbotAIBase.h"
#include "PlayerbotAIConfig.h"
@@ -390,6 +392,8 @@ public:
void HandleMasterOutgoingPacket(WorldPacket const& packet);
void HandleTeleportAck();
void ChangeEngine(BotState type);
void ChangeEngineOnCombat();
void ChangeEngineOnNonCombat();
void DoNextAction(bool minimal = false);
virtual bool DoSpecificAction(std::string const name, Event event = Event(), bool silent = false,
std::string const qualifier = "");
@@ -435,7 +439,8 @@ public:
const AreaTableEntry* GetCurrentArea();
const AreaTableEntry* GetCurrentZone();
static std::string GetLocalizedAreaName(const AreaTableEntry* entry);
static std::string GetLocalizedCreatureName(uint32 entry);
static std::string GetLocalizedGameObjectName(uint32 entry);
bool TellMaster(std::ostringstream& stream, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
bool TellMaster(std::string const text, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
bool TellMasterNoFacing(std::ostringstream& stream,
@@ -464,6 +469,8 @@ public:
Item* FindPoison() const;
Item* FindAmmo() const;
Item* FindBandage() const;
Item* FindOpenableItem() const;
Item* FindLockedItem() const;
Item* FindConsumable(uint32 displayId) const;
Item* FindStoneFor(Item* weapon) const;
Item* FindOilFor(Item* weapon) const;
@@ -579,6 +586,8 @@ public:
static bool IsHealingSpell(uint32 spellFamilyName, flag96 spelFalimyFlags);
static SpellFamilyNames Class2SpellFamilyName(uint8 cls);
NewRpgInfo rpgInfo;
NewRpgStatistic rpgStatistic;
std::unordered_set<uint32> lowPriorityQuest;
private:
static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore,

View File

@@ -91,7 +91,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
LOG_DEBUG("playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue());
return;
}
uint32 count = mgr->GetPlayerbotsCount();
uint32 count = mgr->GetPlayerbotsCount() + botLoading.size();
if (count >= sPlayerbotAIConfig->maxAddedBots)
{
allowed = false;
@@ -638,11 +638,22 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
bool isRandomAccount = sPlayerbotAIConfig->IsInRandomAccountList(botAccount);
bool isMasterAccount = (masterAccountId == botAccount);
if (cmd == "add" || cmd == "login")
if (cmd == "add" || cmd == "addaccount" || cmd == "login")
{
if (ObjectAccessor::FindPlayer(guid))
return "player already logged in";
// For addaccount command, verify it's an account name
if (cmd == "addaccount")
{
uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(guid);
if (!accountId)
return "character not found";
if (!sPlayerbotAIConfig->allowAccountBots && accountId != masterAccountId)
return "you can only add bots from your own account";
}
AddPlayerBot(guid, masterAccountId);
return "ok";
}
@@ -816,7 +827,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
if (!*args)
{
messages.push_back("usage: list/reload/tweak/self or add/init/remove PLAYERNAME\n");
messages.push_back("usage: list/reload/tweak/self or add/addaccount/init/remove PLAYERNAME\n");
messages.push_back("usage: addclass CLASSNAME");
return messages;
}
@@ -1130,22 +1141,48 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
{
std::string const s = *i;
uint32 accountId = GetAccountId(s);
if (!accountId)
if (!strcmp(cmd, "addaccount"))
{
bots.insert(s);
continue;
}
QueryResult results = CharacterDatabase.Query("SELECT name FROM characters WHERE account = {}", accountId);
if (results)
{
do
// When using addaccount, first try to get account ID directly
uint32 accountId = GetAccountId(s);
if (!accountId)
{
Field* fields = results->Fetch();
std::string const charName = fields[0].Get<std::string>();
bots.insert(charName);
} while (results->NextRow());
// If not found, try to get account ID from character name
ObjectGuid charGuid = sCharacterCache->GetCharacterGuidByName(s);
if (!charGuid)
{
messages.push_back("Neither account nor character '" + s + "' found");
continue;
}
accountId = sCharacterCache->GetCharacterAccountIdByGuid(charGuid);
if (!accountId)
{
messages.push_back("Could not find account for character '" + s + "'");
continue;
}
}
QueryResult results = CharacterDatabase.Query("SELECT name FROM characters WHERE account = {}", accountId);
if (results)
{
do
{
Field* fields = results->Fetch();
std::string const charName = fields[0].Get<std::string>();
bots.insert(charName);
} while (results->NextRow());
}
}
else
{
// For regular add command, only add the specific character
ObjectGuid charGuid = sCharacterCache->GetCharacterGuidByName(s);
if (!charGuid)
{
messages.push_back("Character '" + s + "' not found");
continue;
}
bots.insert(s);
}
}

View File

@@ -31,6 +31,7 @@
#include "GuildTaskMgr.h"
#include "LFGMgr.h"
#include "MapMgr.h"
#include "NewRpgInfo.h"
#include "NewRpgStrategy.h"
#include "PerformanceMonitor.h"
#include "Player.h"
@@ -49,6 +50,12 @@
#include "World.h"
#include "RandomPlayerbotFactory.h"
struct GuidClassRaceInfo {
ObjectGuid::LowType guid;
uint32 rClass;
uint32 rRace;
};
void PrintStatsThread() { sRandomPlayerbotMgr->PrintStats(); }
void activatePrintStatsThread()
@@ -371,7 +378,7 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/)
sRandomPlayerbotMgr->CheckLfgQueue();
}
if (time(nullptr) > (printStatsTimer + 300))
if (sPlayerbotAIConfig->randomBotAutologin && time(nullptr) > (printStatsTimer + 300))
{
if (!printStatsTimer)
{
@@ -464,9 +471,17 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
{
maxAllowedBotCount -= currentBots.size();
maxAllowedBotCount = std::min(sPlayerbotAIConfig->randomBotsPerInterval, maxAllowedBotCount);
uint32 totalRatio = sPlayerbotAIConfig->randomBotAllianceRatio + sPlayerbotAIConfig->randomBotHordeRatio;
uint32 allowedAllianceCount = maxAllowedBotCount * (sPlayerbotAIConfig->randomBotAllianceRatio) / totalRatio;
uint32 remainder = maxAllowedBotCount * (sPlayerbotAIConfig->randomBotAllianceRatio) % totalRatio;
// Fix #1082: Randomly add one based on reminder
if (remainder && urand(1, totalRatio) <= remainder) {
allowedAllianceCount++;
}
uint32 allowedAllianceCount = maxAllowedBotCount * (sPlayerbotAIConfig->randomBotAllianceRatio) /
(sPlayerbotAIConfig->randomBotAllianceRatio + sPlayerbotAIConfig->randomBotHordeRatio);
uint32 allowedHordeCount = maxAllowedBotCount - allowedAllianceCount;
for (std::vector<uint32>::iterator i = sPlayerbotAIConfig->randomBotAccounts.begin();
@@ -492,11 +507,28 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
PreparedQueryResult result = CharacterDatabase.Query(stmt);
if (!result)
continue;
std::vector<uint32> guids;
do
{
std::vector<GuidClassRaceInfo> allGuidInfos;
do {
Field* fields = result->Fetch();
ObjectGuid::LowType guid = fields[0].Get<uint32>();
GuidClassRaceInfo info;
info.guid = fields[0].Get<uint32>();
info.rClass = fields[1].Get<uint8>();
info.rRace = fields[2].Get<uint8>();
allGuidInfos.push_back(info);
} while (result->NextRow());
// random shuffle for class balance
std::mt19937 rnd(time(0));
std::shuffle(allGuidInfos.begin(), allGuidInfos.end(), rnd);
std::vector<uint32> guids;
for (const auto& info : allGuidInfos) {
ObjectGuid::LowType guid = info.guid;
uint32 rClass = info.rClass;
uint32 rRace = info.rRace;
if (GetEventValue(guid, "add"))
continue;
@@ -509,40 +541,24 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
if (std::find(currentBots.begin(), currentBots.end(), guid) != currentBots.end())
continue;
if (sPlayerbotAIConfig->disableDeathKnightLogin)
{
uint32 rClass = fields[1].Get<uint8>();
if (rClass == CLASS_DEATH_KNIGHT)
{
if (sPlayerbotAIConfig->disableDeathKnightLogin) {
if (rClass == CLASS_DEATH_KNIGHT) {
continue;
}
}
uint32 rRace = fields[2].Get<uint8>();
uint32 isAlliance = IsAlliance(rRace);
if (!allowedAllianceCount && isAlliance)
{
bool factionNotAllowed = (!allowedAllianceCount && isAlliance) || (!allowedHordeCount && !isAlliance);
if (factionNotAllowed)
continue;
}
if (!allowedHordeCount && !isAlliance)
{
continue;
}
if (isAlliance)
{
if (isAlliance) {
allowedAllianceCount--;
}
else
{
} else {
allowedHordeCount--;
}
guids.push_back(guid);
} while (result->NextRow());
std::mt19937 rnd(time(0));
std::shuffle(guids.begin(), guids.end(), rnd);
for (uint32& guid : guids)
{
uint32 add_time = sPlayerbotAIConfig->enablePeriodicOnlineOffline
? urand(sPlayerbotAIConfig->minRandomBotInWorldTime,
sPlayerbotAIConfig->maxRandomBotInWorldTime)
@@ -1466,6 +1482,82 @@ 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}; // 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
}
void RandomPlayerbotMgr::PrepareTeleportCache()
{
uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
@@ -1542,6 +1634,7 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
if (sPlayerbotAIConfig->enableNewRpgStrategy)
{
PrepareZone2LevelBracket();
LOG_INFO("playerbots", "Preparing innkeepers locations for level...");
results = WorldDatabase.Query(
"SELECT "
@@ -1550,8 +1643,7 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
"position_y, "
"position_z, "
"orientation, "
"t.faction, "
"t.entry "
"t.faction "
"FROM "
"creature c "
"INNER JOIN creature_template t on c.id1 = t.entry "
@@ -1573,7 +1665,6 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
float z = fields[3].Get<float>();
float orient = fields[4].Get<float>();
uint32 faction = fields[5].Get<uint32>();
uint32 c_entry = fields[6].Get<uint32>();
const FactionTemplateEntry* entry = sFactionTemplateStore.LookupEntry(faction);
WorldLocation loc(mapId, x + cos(orient) * 5.0f, y + sin(orient) * 5.0f, z + 0.5f, orient + M_PI);
@@ -1581,35 +1672,13 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
Map* map = sMapMgr->FindMap(loc.GetMapId(), 0);
if (!map)
continue;
const AreaTableEntry* area = sAreaTableStore.LookupEntry(map->GetAreaId(1, x, y, z));
const AreaTableEntry* area = sAreaTableStore.LookupEntry(map->GetAreaId(PHASEMASK_NORMAL, x, y, z));
uint32 zoneId = area->zone ? area->zone : area->ID;
uint32 level = area->area_level;
for (int i = 5; i <= maxLevel; i++)
if (zone2LevelBracket.find(zoneId) == zone2LevelBracket.end())
continue;
LevelBracket bracket = zone2LevelBracket[zoneId];
for (int i = bracket.low; i <= bracket.high; i++)
{
std::vector<WorldLocation>& locs = locsPerLevelCache[i];
int counter = 0;
WorldLocation levelLoc;
for (auto& checkLoc : locs)
{
if (loc.GetMapId() != checkLoc.GetMapId())
continue;
if (loc.GetExactDist(checkLoc) > 1500.0f)
continue;
if (zoneId !=
map->GetZoneId(1, checkLoc.GetPositionX(), checkLoc.GetPositionY(), checkLoc.GetPositionZ()))
continue;
counter++;
levelLoc = checkLoc;
if (counter >= 15)
break;
}
if (counter < 15)
continue;
if (!(entry->hostileMask & 4))
{
hordeStarterPerLevelCache[i].push_back(loc);
@@ -1618,12 +1687,10 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
{
allianceStarterPerLevelCache[i].push_back(loc);
}
LOG_DEBUG("playerbots", "Area: {} Level: {} creature_entry: {} add to: {} {}({},{},{},{})", area->ID,
level, c_entry, i, counter, levelLoc.GetPositionX(), levelLoc.GetPositionY(),
levelLoc.GetPositionZ(), levelLoc.GetMapId());
}
} while (results->NextRow());
}
// add all initial position
for (uint32 i = 1; i < MAX_RACES; i++)
{
@@ -2629,9 +2696,8 @@ void RandomPlayerbotMgr::PrintStats()
uint32 engine_noncombat = 0;
uint32 engine_combat = 0;
uint32 engine_dead = 0;
uint32 stateCount[MAX_TRAVEL_STATE + 1] = {0};
std::vector<std::pair<Quest const*, int32>> questCount;
std::unordered_map<NewRpgStatus, int> rpgStatusCount;
NewRpgStatistic rpgStasticTotal;
std::unordered_map<uint32, int> zoneCount;
uint8 maxBotLevel = 0;
for (PlayerBotMap::iterator i = playerBots.begin(); i != playerBots.end(); ++i)
@@ -2706,41 +2772,19 @@ void RandomPlayerbotMgr::PrintStats()
zoneCount[bot->GetZoneId()]++;
if (sPlayerbotAIConfig->enableNewRpgStrategy)
rpgStatusCount[botAI->rpgInfo.status]++;
if (TravelTarget* target = botAI->GetAiObjectContext()->GetValue<TravelTarget*>("travel target")->Get())
{
TravelState state = target->getTravelState();
stateCount[state]++;
Quest const* quest;
if (target->getDestination())
quest = target->getDestination()->GetQuestTemplate();
if (quest)
{
bool found = false;
for (auto& q : questCount)
{
if (q.first != quest)
continue;
q.second++;
found = true;
}
if (!found)
questCount.push_back(std::make_pair(quest, 1));
}
rpgStatusCount[botAI->rpgInfo.status]++;
rpgStasticTotal += botAI->rpgStatistic;
}
}
LOG_INFO("playerbots", "Bots level:");
// uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
uint32 currentAlliance = 0, currentHorde = 0;
uint32 step = std::max(1, (maxBotLevel + 4) / 8);
uint32 from = 1;
uint32_t currentAlliance = 0, currentHorde = 0;
uint32_t step = std::max(1, static_cast<int>((maxBotLevel + 4) / 8));
uint32_t from = 1;
for (uint8 i = 1; i <= maxBotLevel; ++i)
{
currentAlliance += alliance[i];
@@ -2800,19 +2844,18 @@ void RandomPlayerbotMgr::PrintStats()
if (sPlayerbotAIConfig->enableNewRpgStrategy)
{
LOG_INFO("playerbots", "Bots rpg status:", dead);
LOG_INFO("playerbots", " IDLE: {}", rpgStatusCount[NewRpgStatus::IDLE]);
LOG_INFO("playerbots", " REST: {}", rpgStatusCount[NewRpgStatus::REST]);
LOG_INFO("playerbots", " GO_GRIND: {}", rpgStatusCount[NewRpgStatus::GO_GRIND]);
LOG_INFO("playerbots", " GO_INNKEEPER: {}", rpgStatusCount[NewRpgStatus::GO_INNKEEPER]);
LOG_INFO("playerbots", " NEAR_RANDOM: {}", rpgStatusCount[NewRpgStatus::NEAR_RANDOM]);
LOG_INFO("playerbots", " NEAR_NPC: {}", rpgStatusCount[NewRpgStatus::NEAR_NPC]);
LOG_INFO("playerbots", "Bots rpg status:");
LOG_INFO("playerbots", " Idle: {}, Rest: {}, GoGrind: {}, GoInnkeeper: {}, MoveRandom: {}, MoveNpc: {}, DoQuest: {}",
rpgStatusCount[RPG_IDLE], rpgStatusCount[RPG_REST], rpgStatusCount[RPG_GO_GRIND], rpgStatusCount[RPG_GO_INNKEEPER],
rpgStatusCount[RPG_NEAR_RANDOM], rpgStatusCount[RPG_NEAR_NPC], rpgStatusCount[RPG_DO_QUEST]);
LOG_INFO("playerbots", "Bots total quests:");
LOG_INFO("playerbots", " Accepted: {}, Rewarded: {}, Dropped: {}",
rpgStasticTotal.questAccepted, rpgStasticTotal.questRewarded, rpgStasticTotal.questDropped);
}
LOG_INFO("playerbots", "Bots engine:", dead);
LOG_INFO("playerbots", " Non-combat: {}", engine_noncombat);
LOG_INFO("playerbots", " Combat: {}", engine_combat);
LOG_INFO("playerbots", " Dead: {}", engine_dead);
LOG_INFO("playerbots", " Non-combat: {}, Combat: {}, Dead: {}", engine_noncombat, engine_combat, engine_dead);
// LOG_INFO("playerbots", "Bots questing:");
// LOG_INFO("playerbots", " Picking quests: {}",

View File

@@ -171,12 +171,19 @@ 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;
std::map<uint8, std::vector<WorldLocation>> bankerLocsPerLevelCache;
protected:
void OnBotLoginInternal(Player* const bot) override;

View File

@@ -31,6 +31,7 @@
#include "PlayerbotAIConfig.h"
#include "PlayerbotDbStore.h"
#include "Playerbots.h"
#include "QuestDef.h"
#include "RandomItemMgr.h"
#include "RandomPlayerbotFactory.h"
#include "ReputationMgr.h"
@@ -979,30 +980,23 @@ void PlayerbotFactory::ClearSpells()
void PlayerbotFactory::ResetQuests()
{
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{
bot->SetQuestSlot(slot, 0);
}
ObjectMgr::QuestMap const& questTemplates = sObjectMgr->GetQuestTemplates();
for (ObjectMgr::QuestMap::const_iterator i = questTemplates.begin(); i != questTemplates.end(); ++i)
{
Quest const* quest = i->second;
uint32 entry = quest->GetQuestId();
if (bot->GetQuestStatus(entry) == QUEST_STATUS_NONE)
continue;
bot->RemoveRewardedQuest(entry);
bot->RemoveActiveQuest(entry, false);
// remove all quest entries for 'entry' from quest log
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{
uint32 quest = bot->GetQuestSlotQuestId(slot);
if (quest == entry)
{
bot->SetQuestSlot(slot, 0);
}
}
// reset rewarded for restart repeatable quest
bot->getQuestStatusMap().erase(entry);
// bot->getQuestStatusMap()[entry].m_rewarded = false;
// bot->getQuestStatusMap()[entry].m_status = QUEST_STATUS_NONE;
}
// bot->UpdateForQuestWorldObjects();
CharacterDatabase.Execute("DELETE FROM character_queststatus WHERE guid = {}", bot->GetGUID().GetCounter());
}
void PlayerbotFactory::InitSpells() { InitAvailableSpells(); }
@@ -1618,6 +1612,11 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
if (level < 20 && (slot == EQUIPMENT_SLOT_FINGER1 || slot == EQUIPMENT_SLOT_FINGER2))
continue;
if (level < 5 && (slot != EQUIPMENT_SLOT_MAINHAND) && (slot != EQUIPMENT_SLOT_OFFHAND) &&
(slot != EQUIPMENT_SLOT_FEET) && (slot != EQUIPMENT_SLOT_LEGS) && (slot != EQUIPMENT_SLOT_CHEST) &&
(slot != EQUIPMENT_SLOT_RANGED))
continue;
Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
if (second_chance && oldItem)
@@ -1782,6 +1781,10 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
if (level < 20 && (slot == EQUIPMENT_SLOT_FINGER1 || slot == EQUIPMENT_SLOT_FINGER2))
continue;
if (level < 5 && (slot != EQUIPMENT_SLOT_MAINHAND) && (slot != EQUIPMENT_SLOT_OFFHAND) &&
(slot != EQUIPMENT_SLOT_FEET) && (slot != EQUIPMENT_SLOT_LEGS) && (slot != EQUIPMENT_SLOT_CHEST))
continue;
if (Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true);

View File

@@ -87,7 +87,7 @@ bool AcceptQuestAction::Execute(Event event)
{
std::stringstream ss;
ss << "AcceptQuestAction [" << qInfo->GetTitle() << "] - [" << std::to_string(qInfo->GetQuestId()) << "]";
LOG_INFO("playerbots", "{}", ss.str().c_str());
LOG_DEBUG("playerbots", "{}", ss.str().c_str());
// botAI->TellMaster(ss.str());
}

View File

@@ -135,6 +135,7 @@ public:
creators["move to loot"] = &ActionContext::move_to_loot;
creators["open loot"] = &ActionContext::open_loot;
creators["guard"] = &ActionContext::guard;
creators["return to stay position"] = &ActionContext::return_to_stay_position;
creators["move out of enemy contact"] = &ActionContext::move_out_of_enemy_contact;
creators["set facing"] = &ActionContext::set_facing;
creators["set behind"] = &ActionContext::set_behind;
@@ -247,6 +248,7 @@ public:
creators["new rpg go innkeeper"] = &ActionContext::new_rpg_go_innkeeper;
creators["new rpg move random"] = &ActionContext::new_rpg_move_random;
creators["new rpg move npc"] = &ActionContext::new_rpg_move_npc;
creators["new rpg do quest"] = &ActionContext::new_rpg_do_quest;
}
private:
@@ -270,6 +272,7 @@ private:
static Action* drop_target(PlayerbotAI* botAI) { return new DropTargetAction(botAI); }
static Action* attack_duel_opponent(PlayerbotAI* botAI) { return new AttackDuelOpponentAction(botAI); }
static Action* guard(PlayerbotAI* botAI) { return new GuardAction(botAI); }
static Action* return_to_stay_position(PlayerbotAI* botAI) { return new ReturnToStayPositionAction(botAI); }
static Action* open_loot(PlayerbotAI* botAI) { return new OpenLootAction(botAI); }
static Action* move_to_loot(PlayerbotAI* botAI) { return new MoveToLootAction(botAI); }
static Action* _return(PlayerbotAI* botAI) { return new ReturnAction(botAI); }
@@ -428,6 +431,7 @@ private:
static Action* new_rpg_go_innkeeper(PlayerbotAI* ai) { return new NewRpgGoInnKeeperAction(ai); }
static Action* new_rpg_move_random(PlayerbotAI* ai) { return new NewRpgMoveRandomAction(ai); }
static Action* new_rpg_move_npc(PlayerbotAI* ai) { return new NewRpgMoveNpcAction(ai); }
static Action* new_rpg_do_quest(PlayerbotAI* ai) { return new NewRpgDoQuestAction(ai); }
};
#endif

View File

@@ -75,6 +75,8 @@
#include "WhoAction.h"
#include "WtsAction.h"
#include "OpenItemAction.h"
#include "UnlockItemAction.h"
#include "UnlockTradedItemAction.h"
class ChatActionContext : public NamedObjectContext<Action>
{
@@ -82,6 +84,8 @@ public:
ChatActionContext()
{
creators["open items"] = &ChatActionContext::open_items;
creators["unlock items"] = &ChatActionContext::unlock_items;
creators["unlock traded item"] = &ChatActionContext::unlock_traded_item;
creators["range"] = &ChatActionContext::range;
creators["stats"] = &ChatActionContext::stats;
creators["quests"] = &ChatActionContext::quests;
@@ -90,6 +94,7 @@ public:
creators["log"] = &ChatActionContext::log;
creators["los"] = &ChatActionContext::los;
creators["rpg status"] = &ChatActionContext::rpg_status;
creators["rpg do quest"] = &ChatActionContext::rpg_do_quest;
creators["aura"] = &ChatActionContext::aura;
creators["drop"] = &ChatActionContext::drop;
creators["clean quest log"] = &ChatActionContext::clean_quest_log;
@@ -183,6 +188,8 @@ public:
private:
static Action* open_items(PlayerbotAI* botAI) { return new OpenItemAction(botAI); }
static Action* unlock_items(PlayerbotAI* botAI) { return new UnlockItemAction(botAI); }
static Action* unlock_traded_item(PlayerbotAI* botAI) { return new UnlockTradedItemAction(botAI); }
static Action* range(PlayerbotAI* botAI) { return new RangeAction(botAI); }
static Action* flag(PlayerbotAI* botAI) { return new FlagAction(botAI); }
static Action* craft(PlayerbotAI* botAI) { return new SetCraftAction(botAI); }
@@ -261,6 +268,7 @@ private:
static Action* log(PlayerbotAI* botAI) { return new LogLevelAction(botAI); }
static Action* los(PlayerbotAI* botAI) { return new TellLosAction(botAI); }
static Action* rpg_status(PlayerbotAI* botAI) { return new TellRpgStatusAction(botAI); }
static Action* rpg_do_quest(PlayerbotAI* botAI) { return new StartRpgDoQuestAction(botAI); }
static Action* aura(PlayerbotAI* ai) { return new TellAuraAction(ai); }
static Action* ll(PlayerbotAI* botAI) { return new LootStrategyAction(botAI); }
static Action* ss(PlayerbotAI* botAI) { return new SkipSpellsListAction(botAI); }

View File

@@ -10,7 +10,7 @@
#include "Playerbots.h"
#include "PositionValue.h"
void ReturnPositionResetAction::ResetReturnPosition()
void PositionsResetAction::ResetReturnPosition()
{
PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
PositionInfo pos = posMap["return"];
@@ -18,7 +18,7 @@ void ReturnPositionResetAction::ResetReturnPosition()
posMap["return"] = pos;
}
void ReturnPositionResetAction::SetReturnPosition(float x, float y, float z)
void PositionsResetAction::SetReturnPosition(float x, float y, float z)
{
PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
PositionInfo pos = posMap["return"];
@@ -26,6 +26,22 @@ void ReturnPositionResetAction::SetReturnPosition(float x, float y, float z)
posMap["return"] = pos;
}
void PositionsResetAction::ResetStayPosition()
{
PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
PositionInfo pos = posMap["stay"];
pos.Reset();
posMap["stay"] = pos;
}
void PositionsResetAction::SetStayPosition(float x, float y, float z)
{
PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
PositionInfo pos = posMap["stay"];
pos.Set(x, y, z, botAI->GetBot()->GetMapId());
posMap["stay"] = pos;
}
bool FollowChatShortcutAction::Execute(Event event)
{
Player* master = GetMaster();
@@ -34,7 +50,7 @@ bool FollowChatShortcutAction::Execute(Event event)
// botAI->Reset();
botAI->ChangeStrategy("+follow,-passive,-grind,-move from group", BOT_STATE_NON_COMBAT);
botAI->ChangeStrategy("-follow,-passive,-grind,-move from group", BOT_STATE_COMBAT);
botAI->ChangeStrategy("-stay,-follow,-passive,-grind,-move from group", BOT_STATE_COMBAT);
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Reset();
PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
@@ -42,6 +58,10 @@ bool FollowChatShortcutAction::Execute(Event event)
pos.Reset();
posMap["return"] = pos;
pos = posMap["stay"];
pos.Reset();
posMap["stay"] = pos;
if (bot->IsInCombat())
{
Formation* formation = AI_VALUE(Formation*, "formation");
@@ -103,9 +123,10 @@ bool StayChatShortcutAction::Execute(Event event)
botAI->Reset();
botAI->ChangeStrategy("+stay,-passive,-move from group", BOT_STATE_NON_COMBAT);
botAI->ChangeStrategy("-follow,-passive,-move from group", BOT_STATE_COMBAT);
botAI->ChangeStrategy("+stay,-follow,-passive,-move from group", BOT_STATE_COMBAT);
SetReturnPosition(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
SetStayPosition(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
botAI->TellMaster("Staying");
return true;
@@ -133,10 +154,11 @@ bool FleeChatShortcutAction::Execute(Event event)
return false;
botAI->Reset();
botAI->ChangeStrategy("+follow,+passive", BOT_STATE_NON_COMBAT);
botAI->ChangeStrategy("+follow,+passive", BOT_STATE_COMBAT);
botAI->ChangeStrategy("+follow,-stay,+passive", BOT_STATE_NON_COMBAT);
botAI->ChangeStrategy("+follow,-stay,+passive", BOT_STATE_COMBAT);
ResetReturnPosition();
ResetStayPosition();
if (bot->GetMapId() != master->GetMapId() || bot->GetDistance(master) > sPlayerbotAIConfig->sightDistance)
{
@@ -155,10 +177,11 @@ bool GoawayChatShortcutAction::Execute(Event event)
return false;
botAI->Reset();
botAI->ChangeStrategy("+runaway", BOT_STATE_NON_COMBAT);
botAI->ChangeStrategy("+runaway", BOT_STATE_COMBAT);
botAI->ChangeStrategy("+runaway,-stay", BOT_STATE_NON_COMBAT);
botAI->ChangeStrategy("+runaway,-stay", BOT_STATE_COMBAT);
ResetReturnPosition();
ResetStayPosition();
botAI->TellMaster("Running away");
return true;
@@ -171,9 +194,10 @@ bool GrindChatShortcutAction::Execute(Event event)
return false;
botAI->Reset();
botAI->ChangeStrategy("+grind,-passive", BOT_STATE_NON_COMBAT);
botAI->ChangeStrategy("+grind,-passive,-stay", BOT_STATE_NON_COMBAT);
ResetReturnPosition();
ResetStayPosition();
botAI->TellMaster("Grinding");
return true;
@@ -193,6 +217,7 @@ bool TankAttackChatShortcutAction::Execute(Event event)
botAI->ChangeStrategy("-passive", BOT_STATE_COMBAT);
ResetReturnPosition();
ResetStayPosition();
botAI->TellMaster("Attacking");
return true;

View File

@@ -10,13 +10,15 @@
class PlayerbotAI;
class ReturnPositionResetAction : public Action
class PositionsResetAction : public Action
{
public:
ReturnPositionResetAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name) {}
PositionsResetAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name) {}
void ResetReturnPosition();
void SetReturnPosition(float x, float y, float z);
void ResetStayPosition();
void SetStayPosition(float x, float y, float z);
};
class FollowChatShortcutAction : public MovementAction
@@ -27,10 +29,10 @@ public:
bool Execute(Event event) override;
};
class StayChatShortcutAction : public ReturnPositionResetAction
class StayChatShortcutAction : public PositionsResetAction
{
public:
StayChatShortcutAction(PlayerbotAI* botAI) : ReturnPositionResetAction(botAI, "stay chat shortcut") {}
StayChatShortcutAction(PlayerbotAI* botAI) : PositionsResetAction(botAI, "stay chat shortcut") {}
bool Execute(Event event) override;
};
@@ -43,34 +45,34 @@ public:
bool Execute(Event event) override;
};
class FleeChatShortcutAction : public ReturnPositionResetAction
class FleeChatShortcutAction : public PositionsResetAction
{
public:
FleeChatShortcutAction(PlayerbotAI* botAI) : ReturnPositionResetAction(botAI, "flee chat shortcut") {}
FleeChatShortcutAction(PlayerbotAI* botAI) : PositionsResetAction(botAI, "flee chat shortcut") {}
bool Execute(Event event) override;
};
class GoawayChatShortcutAction : public ReturnPositionResetAction
class GoawayChatShortcutAction : public PositionsResetAction
{
public:
GoawayChatShortcutAction(PlayerbotAI* botAI) : ReturnPositionResetAction(botAI, "runaway chat shortcut") {}
GoawayChatShortcutAction(PlayerbotAI* botAI) : PositionsResetAction(botAI, "runaway chat shortcut") {}
bool Execute(Event event) override;
};
class GrindChatShortcutAction : public ReturnPositionResetAction
class GrindChatShortcutAction : public PositionsResetAction
{
public:
GrindChatShortcutAction(PlayerbotAI* botAI) : ReturnPositionResetAction(botAI, "grind chat shortcut") {}
GrindChatShortcutAction(PlayerbotAI* botAI) : PositionsResetAction(botAI, "grind chat shortcut") {}
bool Execute(Event event) override;
};
class TankAttackChatShortcutAction : public ReturnPositionResetAction
class TankAttackChatShortcutAction : public PositionsResetAction
{
public:
TankAttackChatShortcutAction(PlayerbotAI* botAI) : ReturnPositionResetAction(botAI, "tank attack chat shortcut") {}
TankAttackChatShortcutAction(PlayerbotAI* botAI) : PositionsResetAction(botAI, "tank attack chat shortcut") {}
bool Execute(Event event) override;
};

View File

@@ -34,28 +34,17 @@ bool AttackAnythingAction::isUseful()
if (!botAI->AllowActivity(GRIND_ACTIVITY)) // Bot not allowed to be active
return false;
if (!AI_VALUE(bool, "can move around"))
if (botAI->HasStrategy("stay", BOT_STATE_NON_COMBAT))
return false;
if (bot->IsInCombat())
return false;
// if (context->GetValue<TravelTarget*>("travel target")->Get()->isTraveling() &&
// ChooseRpgTargetAction::isFollowValid(
// bot, *context->GetValue<TravelTarget*>("travel target")->Get()->getPosition())) // Bot is traveling
// return false;
Unit* target = GetTarget();
if (!target)
return false;
bool inactiveGrindStatus = botAI->rpgInfo.status == NewRpgStatus::GO_GRIND ||
botAI->rpgInfo.status == NewRpgStatus::NEAR_NPC ||
botAI->rpgInfo.status == NewRpgStatus::REST ||
botAI->rpgInfo.status == NewRpgStatus::GO_INNKEEPER;
if (inactiveGrindStatus && bot->GetDistance(target) > 25.0f)
return false;
std::string const name = std::string(target->GetName());
// Check for invalid targets: Dummy, Charge Target, Melee Target, Ranged Target
if (!name.empty() &&

View File

@@ -132,6 +132,7 @@ bool CleanQuestLogAction::Execute(Event event)
}
// Remove quest
botAI->rpgStatistic.questDropped++;
bot->SetQuestSlot(slot, 0);
bot->TakeQuestSourceItem(questId, false);
bot->SetQuestStatus(questId, QUEST_STATUS_NONE);

View File

@@ -4,64 +4,23 @@
#include "WorldPacket.h"
#include "Player.h"
#include "ObjectMgr.h"
bool OpenItemAction::Execute(Event event)
{
bool foundOpenable = false;
// Check main inventory slots
for (uint8 slot = EQUIPMENT_SLOT_START; slot < INVENTORY_SLOT_ITEM_END; ++slot)
Item* item = botAI->FindOpenableItem();
if (item)
{
Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
uint8 bag = item->GetBagSlot(); // Retrieves the bag slot (0 for main inventory)
uint8 slot = item->GetSlot(); // Retrieves the actual slot inside the bag
if (item && CanOpenItem(item))
{
OpenItem(item, INVENTORY_SLOT_BAG_0, slot);
foundOpenable = true;
}
}
// Check items in the bags
for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag)
{
Bag* bagItem = bot->GetBagByPos(bag);
if (!bagItem)
continue;
for (uint32 slot = 0; slot < bagItem->GetBagSize(); ++slot)
{
Item* item = bot->GetItemByPos(bag, slot);
if (item && CanOpenItem(item))
{
OpenItem(item, bag, slot);
foundOpenable = true;
}
}
}
// If no openable items found
if (!foundOpenable)
{
botAI->TellError("No openable items in inventory.");
OpenItem(item, bag, slot);
foundOpenable = true;
}
return foundOpenable;
}
bool OpenItemAction::CanOpenItem(Item* item)
{
if (!item)
return false;
ItemTemplate const* itemTemplate = item->GetTemplate();
if (!itemTemplate)
return false;
// Check if the item has the openable flag
return itemTemplate->Flags & ITEM_FLAG_HAS_LOOT;
}
void OpenItemAction::OpenItem(Item* item, uint8 bag, uint8 slot)
{
WorldPacket packet(CMSG_OPEN_ITEM);

View File

@@ -21,9 +21,6 @@ public:
bool Execute(Event event) override;
private:
// Checks if the given item can be opened (i.e., has the openable flag)
bool CanOpenItem(Item* item);
// Performs the action of opening the item
void OpenItem(Item* item, uint8 bag, uint8 slot);
};

View File

@@ -159,3 +159,25 @@ bool ReturnAction::isUseful()
PositionInfo pos = context->GetValue<PositionMap&>("position")->Get()[qualifier];
return pos.isSet() && AI_VALUE2(float, "distance", "position_random") > sPlayerbotAIConfig->followDistance;
}
bool ReturnToStayPositionAction::isPossible()
{
PositionMap& posMap = AI_VALUE(PositionMap&, "position");
PositionInfo stayPosition = posMap["stay"];
if (stayPosition.isSet())
{
const float distance = bot->GetDistance(stayPosition.x, stayPosition.y, stayPosition.z);
if (distance > sPlayerbotAIConfig->reactDistance)
{
botAI->TellMaster("The stay position is too far to return. I am going to stay where I am now");
// Set the stay position to current position
stayPosition.Set(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId());
posMap["stay"] = stayPosition;
}
return true;
}
return false;
}

View File

@@ -40,6 +40,13 @@ public:
GuardAction(PlayerbotAI* botAI) : MoveToPositionAction(botAI, "move to position", "guard") {}
};
class ReturnToStayPositionAction : public MoveToPositionAction
{
public:
ReturnToStayPositionAction(PlayerbotAI* ai) : MoveToPositionAction(ai, "move to position", "stay") {}
virtual bool isPossible();
};
class SetReturnPositionAction : public Action
{
public:

View File

@@ -4,9 +4,14 @@
*/
#include "QuestAction.h"
#include <sstream>
#include "Chat.h"
#include "ChatHelper.h"
#include "Event.h"
#include "ItemTemplate.h"
#include "ObjectGuid.h"
#include "ObjectMgr.h"
#include "Playerbots.h"
#include "ReputationMgr.h"
#include "ServerFacade.h"
@@ -258,6 +263,7 @@ bool QuestAction::AcceptQuest(Quest const* quest, ObjectGuid questGiver)
bool QuestUpdateCompleteAction::Execute(Event event)
{
// the action can hardly be triggered
WorldPacket p(event.getPacket());
p.rpos(0);
@@ -265,22 +271,26 @@ bool QuestUpdateCompleteAction::Execute(Event event)
p >> questId;
p.print_storage();
LOG_INFO("playerbots", "Packet: empty{} questId{}", p.empty(), questId);
// LOG_INFO("playerbots", "Packet: empty{} questId{}", p.empty(), questId);
Quest const* qInfo = sObjectMgr->GetQuestTemplate(questId);
if (qInfo)
{
std::map<std::string, std::string> placeholders;
// std::map<std::string, std::string> placeholders;
// placeholders["%quest_link"] = format;
// if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
// {
// LOG_INFO("playerbots", "{} => Quest [ {} ] completed", bot->GetName(), qInfo->GetTitle());
// bot->Say("Quest [ " + format + " ] completed", LANG_UNIVERSAL);
// }
const auto format = ChatHelper::FormatQuest(qInfo);
placeholders["%quest_link"] = format;
if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{
LOG_INFO("playerbots", "{} => Quest [ {} ] completed", bot->GetName(), qInfo->GetTitle());
bot->Say("Quest [ " + format + " ] completed", LANG_UNIVERSAL);
}
botAI->TellMasterNoFacing("Quest completed " + format);
if (botAI->GetMaster())
botAI->TellMasterNoFacing("Quest completed " + format);
BroadcastHelper::BroadcastQuestUpdateComplete(botAI, bot, qInfo);
botAI->rpgStatistic.questCompleted++;
// LOG_DEBUG("playerbots", "[New rpg] {} complete quest {}", bot->GetName(), qInfo->GetQuestId());
// botAI->rpgStatistic.questCompleted++;
}
return true;
@@ -296,39 +306,39 @@ bool QuestUpdateAddKillAction::Execute(Event event)
uint32 entry, questId, available, required;
p >> questId >> entry >> available >> required;
// LOG_INFO("playerbots", "[New rpg] Quest {} -> Creature {} ({}/{})", questId, entry, available, required);
const Quest* qInfo = sObjectMgr->GetQuestTemplate(questId);
if (qInfo && (entry & 0x80000000))
{
entry &= 0x7FFFFFFF;
const GameObjectTemplate* info = sObjectMgr->GetGameObjectTemplate(entry);
if (info)
BroadcastHelper::BroadcastQuestUpdateAddKill(botAI, bot, qInfo, available, required, info->name);
{
std::string infoName = botAI->GetLocalizedGameObjectName(entry);
BroadcastHelper::BroadcastQuestUpdateAddKill(botAI, bot, qInfo, available, required, infoName);
if (botAI->GetMaster())
{
std::ostringstream out;
out << infoName << " " << available << "/" << required << " " << ChatHelper::FormatQuest(qInfo);
botAI->TellMasterNoFacing(out.str());
}
}
}
else if (qInfo)
{
CreatureTemplate const* info = sObjectMgr->GetCreatureTemplate(entry);
if (info)
{
BroadcastHelper::BroadcastQuestUpdateAddKill(botAI, bot, qInfo, available, required, info->Name);
std::string infoName = botAI->GetLocalizedCreatureName(entry);
BroadcastHelper::BroadcastQuestUpdateAddKill(botAI, bot, qInfo, available, required, infoName);
if (botAI->GetMaster())
{
std::ostringstream out;
out << infoName << " " << available << "/" << required << " " << ChatHelper::FormatQuest(qInfo);
botAI->TellMasterNoFacing(out.str());
}
}
}
else
{
std::map<std::string, std::string> placeholders;
placeholders["%quest_id"] = questId;
placeholders["%available"] = available;
placeholders["%required"] = required;
if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_COMBAT) || botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT))
{
LOG_INFO("playerbots", "{} => {}", bot->GetName(), BOT_TEXT2("%available/%required for questId: %quest_id", placeholders));
botAI->Say(BOT_TEXT2("%available/%required for questId: %quest_id", placeholders));
}
botAI->TellMasterNoFacing(BOT_TEXT2("%available/%required for questId: %quest_id", placeholders));
}
}
return false;
}
@@ -363,8 +373,62 @@ bool QuestUpdateAddItemAction::Execute(Event event)
BroadcastHelper::BroadcastQuestUpdateAddItem(botAI, bot, pair.first, availableItemsCount, requiredItemsCount, itemPrototype);
}
}
}
return false;
}
bool QuestItemPushResultAction::Execute(Event event)
{
WorldPacket packet = event.getPacket();
ObjectGuid guid;
uint32 received, created, sendChatMessage, itemSlot, itemEntry, itemSuffixFactory, count, itemCount;
uint8 bagSlot;
int32 itemRandomPropertyId;
packet >> guid >> received >> created >> sendChatMessage;
packet >> bagSlot >> itemSlot >> itemEntry >> itemSuffixFactory >> itemRandomPropertyId;
packet >> count >> itemCount;
if (guid != bot->GetGUID())
return false;
const ItemTemplate* proto = sObjectMgr->GetItemTemplate(itemEntry);
if (!proto)
return false;
for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
uint32 questId = bot->GetQuestSlotQuestId(i);
if (!questId)
continue;
const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
if (!quest)
return false;
const QuestStatusData& q_status = bot->getQuestStatusMap().at(questId);
for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++)
{
uint32 itemId = quest->RequiredItemId[i];
if (!itemId)
continue;
int32 previousCount = itemCount - count;
if (itemId == itemEntry && previousCount < quest->RequiredItemCount[i])
{
if (botAI->GetMaster())
{
std::string itemLink = ChatHelper::FormatItem(proto);
std::ostringstream out;
int32 required = quest->RequiredItemCount[i];
int32 available = std::min((int32)itemCount, required);
out << itemLink << " " << available << "/" << required << " " << ChatHelper::FormatQuest(quest);
botAI->TellMasterNoFacing(out.str());
}
}
}
}
return false;
}

View File

@@ -66,4 +66,11 @@ public:
bool Execute(Event event) override;
};
class QuestItemPushResultAction : public Action
{
public:
QuestItemPushResultAction(PlayerbotAI* ai) : Action(ai, "quest item push result") {}
bool Execute(Event event) override;;
};
#endif

View File

@@ -14,6 +14,12 @@ bool ReachTargetAction::Execute(Event event) { return ReachCombatTo(AI_VALUE(Uni
bool ReachTargetAction::isUseful()
{
// do not move while staying
if (botAI->HasStrategy("stay", botAI->GetState()))
{
return false;
}
// do not move while casting
if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL) != nullptr)
{
@@ -30,6 +36,12 @@ std::string const ReachTargetAction::GetTargetName() { return "current target";
bool CastReachTargetSpellAction::isUseful()
{
// do not move while staying
if (botAI->HasStrategy("stay", botAI->GetState()))
{
return false;
}
return sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "current target"),
(distance + sPlayerbotAIConfig->contactDistance));
}

View File

@@ -11,6 +11,7 @@
#include "Playerbots.h"
#include "RTSCValues.h"
#include "RtscAction.h"
#include "PositionValue.h"
Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z, float o, uint32 entry, Creature* lastWp,
bool important)
@@ -123,6 +124,15 @@ bool SeeSpellAction::MoveToSpell(WorldPosition& spellPosition, bool inFormation)
if (inFormation)
SetFormationOffset(spellPosition);
if (botAI->HasStrategy("stay", botAI->GetState()))
{
PositionMap& posMap = AI_VALUE(PositionMap&, "position");
PositionInfo stayPosition = posMap["stay"];
stayPosition.Set(spellPosition.getX(), spellPosition.getY(), spellPosition.getZ(), spellPosition.getMapId());
posMap["stay"] = stayPosition;
}
if (bot->IsWithinLOS(spellPosition.getX(), spellPosition.getY(), spellPosition.getZ()))
return MoveNear(spellPosition.getMapId(), spellPosition.getX(), spellPosition.getY(), spellPosition.getZ(), 0);

View File

@@ -8,6 +8,7 @@
#include "Event.h"
#include "LastMovementValue.h"
#include "Playerbots.h"
#include "PositionValue.h"
bool StayActionBase::Stay()
{
@@ -42,6 +43,17 @@ bool StayAction::Execute(Event event) { return Stay(); }
bool StayAction::isUseful()
{
// Check if the bots is in stay position
PositionInfo stayPosition = AI_VALUE(PositionMap&, "position")["stay"];
if (stayPosition.isSet())
{
const float distance = bot->GetDistance(stayPosition.x, stayPosition.y, stayPosition.z);
if (sPlayerbotAIConfig->followDistance)
{
return false;
}
}
// move from group takes priority over stay as it's added and removed automatically
// (without removing/adding stay)
if (botAI->HasStrategy("move from group", BOT_STATE_COMBAT) ||

View File

@@ -173,7 +173,6 @@ void TalkToQuestGiverAction::RewardMultipleItem(Quest const* quest, Object* ques
std::ostringstream outid;
if (!botAI->IsAlt() || sPlayerbotAIConfig->autoPickReward == "yes")
{
// Pick the first item of the best rewards.
bestIds = BestRewards(quest);
if (!bestIds.empty())
{

View File

@@ -24,12 +24,17 @@ bool TradeStatusAction::Execute(Event event)
return false;
PlayerbotAI* traderBotAI = GET_PLAYERBOT_AI(trader);
if (trader != master && !traderBotAI)
// Allow the master and group members to trade
if (trader != master && !traderBotAI && (!bot->GetGroup() || !bot->GetGroup()->IsMember(trader->GetGUID())))
{
bot->Whisper("I'm kind of busy now", LANG_UNIVERSAL, trader);
return false;
}
if ((trader != master || !botAI->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_ALLOW_ALL, true, master)) &&
// Allow trades from group members or bots
if ((!bot->GetGroup() || !bot->GetGroup()->IsMember(trader->GetGUID())) &&
(trader != master || !botAI->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_ALLOW_ALL, true, master)) &&
!traderBotAI)
{
WorldPacket p;
@@ -109,9 +114,9 @@ bool TradeStatusAction::Execute(Event event)
bot->SetFacingToObject(trader);
BeginTrade();
return true;
}
return false;
}

View File

@@ -0,0 +1,84 @@
#include "TradeStatusExtendedAction.h"
#include "Event.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "WorldPacket.h"
#include "TradeData.h"
bool TradeStatusExtendedAction::Execute(Event event)
{
Player* trader = bot->GetTrader();
if (!trader)
return false;
TradeData* tradeData = trader->GetTradeData();
if (!tradeData)
return false;
WorldPacket p(event.getPacket());
p.rpos(0);
uint8 isTraderData;
uint32 unknown1, slotCount1, slotCount2, tradeGold, spellCast;
p >> isTraderData;
p >> unknown1;
p >> slotCount1;
p >> slotCount2;
p >> tradeGold;
p >> spellCast;
for (uint8 i = 0; i < TRADE_SLOT_COUNT; ++i)
{
uint8 tradeSlot;
p >> tradeSlot;
if (tradeSlot >= TRADE_SLOT_COUNT)
break; // End of packet
uint32 itemId, displayId, count, wrapped, lockId;
uint64 giftCreator, creator;
uint32 permEnchant, gem1, gem2, gem3;
uint32 spellCharges, suffixFactor, randomProp, maxDurability, durability;
p >> itemId;
p >> displayId;
p >> count;
p >> wrapped;
p >> giftCreator;
p >> permEnchant;
p >> gem1;
p >> gem2;
p >> gem3;
p >> creator;
p >> spellCharges;
p >> suffixFactor;
p >> randomProp;
p >> lockId;
p >> maxDurability;
p >> durability;
// Check for locked items in "Do Not Trade" slot
if (tradeSlot == TRADE_SLOT_NONTRADED && lockId > 0)
{
// Get the actual item reference from TradeData
Item* lockbox = tradeData->GetItem(TRADE_SLOT_NONTRADED);
if (!lockbox)
{
return false;
}
if (bot->getClass() == CLASS_ROGUE && bot->HasSpell(1804) && lockbox->IsLocked()) // Pick Lock spell
{
// botAI->CastSpell(1804, bot, lockbox); // Attempt to cast Pick Lock on the lockbox
botAI->DoSpecificAction("unlock traded item");
botAI->SetNextCheckDelay(4000); // Delay before accepting trade
}
else
{
botAI->TellMaster("I can't unlock this item.");
}
}
}
return true;
}

View File

@@ -0,0 +1,17 @@
#ifndef _PLAYERBOT_TRADESTATUSEXTENDEDACTION_H
#define _PLAYERBOT_TRADESTATUSEXTENDEDACTION_H
#include "QueryItemUsageAction.h"
class Player;
class PlayerbotAI;
class TradeStatusExtendedAction : public QueryItemUsageAction
{
public:
TradeStatusExtendedAction(PlayerbotAI* botAI) : QueryItemUsageAction(botAI, "trade status extended") {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,38 @@
#include "UnlockItemAction.h"
#include "PlayerbotAI.h"
#include "ItemTemplate.h"
#include "WorldPacket.h"
#include "Player.h"
#include "ObjectMgr.h"
#include "SpellInfo.h"
#define PICK_LOCK_SPELL_ID 1804
bool UnlockItemAction::Execute(Event event)
{
bool foundLockedItem = false;
Item* item = botAI->FindLockedItem();
if (item)
{
UnlockItem(item);
foundLockedItem = true;
}
return foundLockedItem;
}
void UnlockItemAction::UnlockItem(Item* item)
{
// Use CastSpell to unlock the item
if (botAI->CastSpell(PICK_LOCK_SPELL_ID, bot, item))
{
std::ostringstream out;
out << "Used Pick Lock on: " << item->GetTemplate()->Name1;
botAI->TellMaster(out.str());
}
else
{
botAI->TellError("Failed to cast Pick Lock.");
}
}

View File

@@ -0,0 +1,19 @@
#ifndef _PLAYERBOT_UNLOCKITEMACTION_H
#define _PLAYERBOT_UNLOCKITEMACTION_H
#include "Action.h"
class PlayerbotAI;
class UnlockItemAction : public Action
{
public:
UnlockItemAction(PlayerbotAI* botAI) : Action(botAI, "unlock item") { }
bool Execute(Event event) override;
private:
void UnlockItem(Item* item);
};
#endif

View File

@@ -0,0 +1,98 @@
#include "UnlockTradedItemAction.h"
#include "Playerbots.h"
#include "TradeData.h"
#include "SpellInfo.h"
#define PICK_LOCK_SPELL_ID 1804
bool UnlockTradedItemAction::Execute(Event event)
{
Player* trader = bot->GetTrader();
if (!trader)
return false; // No active trade session
TradeData* tradeData = trader->GetTradeData();
if (!tradeData)
return false; // No trade data available
Item* lockbox = tradeData->GetItem(TRADE_SLOT_NONTRADED);
if (!lockbox)
{
botAI->TellError("No item in the Do Not Trade slot.");
return false;
}
if (!CanUnlockItem(lockbox))
{
botAI->TellError("Cannot unlock this item.");
return false;
}
UnlockItem(lockbox);
return true;
}
bool UnlockTradedItemAction::CanUnlockItem(Item* item)
{
if (!item)
return false;
ItemTemplate const* itemTemplate = item->GetTemplate();
if (!itemTemplate)
return false;
// Ensure the bot is a rogue and has Lockpicking skill
if (bot->getClass() != CLASS_ROGUE || !botAI->HasSkill(SKILL_LOCKPICKING))
return false;
// Ensure the item is actually locked
if (itemTemplate->LockID == 0 || !item->IsLocked())
return false;
// Check if the bot's Lockpicking skill is high enough
uint32 lockId = itemTemplate->LockID;
LockEntry const* lockInfo = sLockStore.LookupEntry(lockId);
if (!lockInfo)
return false;
uint32 botSkill = bot->GetSkillValue(SKILL_LOCKPICKING);
for (uint8 j = 0; j < 8; ++j)
{
if (lockInfo->Type[j] == LOCK_KEY_SKILL && SkillByLockType(LockType(lockInfo->Index[j])) == SKILL_LOCKPICKING)
{
uint32 requiredSkill = lockInfo->Skill[j];
if (botSkill >= requiredSkill)
return true;
else
{
std::ostringstream out;
out << "Lockpicking skill too low (" << botSkill << "/" << requiredSkill << ") to unlock: "
<< item->GetTemplate()->Name1;
botAI->TellMaster(out.str());
}
}
}
return false;
}
void UnlockTradedItemAction::UnlockItem(Item* item)
{
if (!bot->HasSpell(PICK_LOCK_SPELL_ID))
{
botAI->TellError("Cannot unlock, Pick Lock spell is missing.");
return;
}
// Use CastSpell to unlock the item
if (botAI->CastSpell(PICK_LOCK_SPELL_ID, bot->GetTrader(), item)) // Unit target is trader
{
std::ostringstream out;
out << "Picking Lock on traded item: " << item->GetTemplate()->Name1;
botAI->TellMaster(out.str());
}
else
{
botAI->TellError("Failed to cast Pick Lock.");
}
}

View File

@@ -0,0 +1,20 @@
#ifndef _PLAYERBOT_UNLOCKTRADEDITEMACTION_H
#define _PLAYERBOT_UNLOCKTRADEDITEMACTION_H
#include "Action.h"
class PlayerbotAI;
class UnlockTradedItemAction : public Action
{
public:
UnlockTradedItemAction(PlayerbotAI* botAI) : Action(botAI, "unlock traded item") {}
bool Execute(Event event) override;
private:
bool CanUnlockItem(Item* item);
void UnlockItem(Item* item);
};
#endif

View File

@@ -11,6 +11,7 @@
#include "GridNotifiersImpl.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "PositionValue.h"
bool UseMeetingStoneAction::Execute(Event event)
{
@@ -224,6 +225,16 @@ bool SummonAction::Teleport(Player* summoner, Player* player)
player->GetMotionMaster()->Clear();
AI_VALUE(LastMovement&, "last movement").clear();
player->TeleportTo(mapId, x, y, z, 0);
if (botAI->HasStrategy("stay", botAI->GetState()))
{
PositionMap& posMap = AI_VALUE(PositionMap&, "position");
PositionInfo stayPosition = posMap["stay"];
stayPosition.Set(x,y, z, mapId);
posMap["stay"] = stayPosition;
}
return true;
}
}

View File

@@ -37,6 +37,7 @@
#include "TellCastFailedAction.h"
#include "TellMasterAction.h"
#include "TradeStatusAction.h"
#include "TradeStatusExtendedAction.h"
#include "UseMeetingStoneAction.h"
#include "NamedObjectContext.h"
@@ -65,6 +66,7 @@ public:
creators["check mount state"] = &WorldPacketActionContext::check_mount_state;
creators["remember taxi"] = &WorldPacketActionContext::remember_taxi;
creators["accept trade"] = &WorldPacketActionContext::accept_trade;
creators["trade status extended"] = &WorldPacketActionContext::trade_status_extended;
creators["store loot"] = &WorldPacketActionContext::store_loot;
// quest
@@ -79,7 +81,8 @@ public:
creators["quest update failed timer"] = &WorldPacketActionContext::quest_update_failed_timer;
creators["quest update complete"] = &WorldPacketActionContext::quest_update_complete;
creators["turn in query quest"] = &WorldPacketActionContext::turn_in_query_quest;
creators["quest item push result"] = &WorldPacketActionContext::quest_item_push_result;
creators["party command"] = &WorldPacketActionContext::party_command;
creators["tell cast failed"] = &WorldPacketActionContext::tell_cast_failed;
creators["accept duel"] = &WorldPacketActionContext::accept_duel;
@@ -117,6 +120,7 @@ private:
static Action* party_command(PlayerbotAI* botAI) { return new PartyCommandAction(botAI); }
static Action* store_loot(PlayerbotAI* botAI) { return new StoreLootAction(botAI); }
static Action* accept_trade(PlayerbotAI* botAI) { return new TradeStatusAction(botAI); }
static Action* trade_status_extended(PlayerbotAI* botAI) { return new TradeStatusExtendedAction(botAI); }
static Action* remember_taxi(PlayerbotAI* botAI) { return new RememberTaxiAction(botAI); }
static Action* check_mount_state(PlayerbotAI* botAI) { return new CheckMountStateAction(botAI); }
static Action* area_trigger(PlayerbotAI* botAI) { return new AreaTriggerAction(botAI); }
@@ -139,6 +143,7 @@ private:
static Action* quest_update_failed(PlayerbotAI* ai) { return new QuestUpdateFailedAction(ai); }
static Action* quest_update_failed_timer(PlayerbotAI* ai) { return new QuestUpdateFailedTimerAction(ai); }
static Action* quest_update_complete(PlayerbotAI* botAI) { return new QuestUpdateCompleteAction(botAI); }
static Action* quest_item_push_result(PlayerbotAI* ai) { return new QuestItemPushResultAction(ai); }
static Action* turn_in_quest(PlayerbotAI* botAI) { return new TalkToQuestGiverAction(botAI); }
static Action* accept_quest(PlayerbotAI* botAI) { return new AcceptQuestAction(botAI); }

View File

@@ -96,6 +96,10 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
new TriggerNode("open items", NextAction::array(0, new NextAction("open items", relevance), nullptr)));
triggers.push_back(
new TriggerNode("qi", NextAction::array(0, new NextAction("query item usage", relevance), nullptr)));
triggers.push_back(
new TriggerNode("unlock items", NextAction::array(0, new NextAction("unlock items", relevance), nullptr)));
triggers.push_back(
new TriggerNode("unlock traded item", NextAction::array(0, new NextAction("unlock traded item", relevance), nullptr)));
}
ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)
@@ -109,6 +113,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
supported.push_back("log");
supported.push_back("los");
supported.push_back("rpg status");
supported.push_back("rpg do quest");
supported.push_back("aura");
supported.push_back("drop");
supported.push_back("share");
@@ -171,4 +176,6 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
supported.push_back("calc");
supported.push_back("open items");
supported.push_back("qi");
supported.push_back("unlock items");
supported.push_back("unlock traded item");
}

View File

@@ -12,8 +12,9 @@ void CombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("enemy out of spell",
NextAction::array(0, new NextAction("reach spell", ACTION_HIGH), nullptr)));
// drop target relevance 99 (lower than Worldpacket triggers)
triggers.push_back(
new TriggerNode("invalid target", NextAction::array(0, new NextAction("drop target", 100), nullptr)));
new TriggerNode("invalid target", NextAction::array(0, new NextAction("drop target", 99), nullptr)));
triggers.push_back(
new TriggerNode("mounted", NextAction::array(0, new NextAction("check mount state", 54), nullptr)));
// triggers.push_back(new TriggerNode("out of react range", NextAction::array(0, new NextAction("flee to master",

View File

@@ -7,14 +7,19 @@
#include "Playerbots.h"
NextAction** GrindingStrategy::getDefaultActions() { return nullptr; }
NextAction** GrindingStrategy::getDefaultActions()
{
return NextAction::array(0,
new NextAction("drink", 4.2f),
new NextAction("food", 4.1f),
nullptr);
}
void GrindingStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("timer", NextAction::array(0, new NextAction("drink", 10.2f), nullptr)));
triggers.push_back(new TriggerNode("timer", NextAction::array(0, new NextAction("food", 10.1f), nullptr)));
// reduce lower than loot
triggers.push_back(
new TriggerNode("no target", NextAction::array(0, new NextAction("attack anything", 10.0f), nullptr)));
new TriggerNode("no target", NextAction::array(0, new NextAction("attack anything", 4.0f), nullptr)));
}
void MoveRandomStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)

View File

@@ -13,13 +13,13 @@ void LootNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(
new TriggerNode("far from loot target", NextAction::array(0, new NextAction("move to loot", 7.0f), nullptr)));
triggers.push_back(new TriggerNode("can loot", NextAction::array(0, new NextAction("open loot", 8.0f), nullptr)));
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("add all loot", 1.0f), nullptr)));
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("add all loot", 5.0f), nullptr)));
}
void GatherStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("timer", NextAction::array(0, new NextAction("add gathering loot", 2.0f), nullptr)));
new TriggerNode("timer", NextAction::array(0, new NextAction("add gathering loot", 5.0f), nullptr)));
}
void RevealStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)

View File

@@ -7,6 +7,13 @@
#include "Playerbots.h"
void StayStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode(
"return to stay position",
NextAction::array(0, new NextAction("return to stay position", ACTION_MOVE), nullptr)));
}
NextAction** StayStrategy::getDefaultActions() { return NextAction::array(0, new NextAction("stay", 1.0f), nullptr); }
void SitStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)

View File

@@ -10,12 +10,13 @@
class PlayerbotAI;
class StayStrategy : public NonCombatStrategy
class StayStrategy : public Strategy
{
public:
StayStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI) {}
StayStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "stay"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
NextAction** getDefaultActions() override;
};

View File

@@ -35,10 +35,15 @@ void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
new NextAction("taxi", relevance), nullptr)));
triggers.push_back(new TriggerNode("taxi done", NextAction::array(0, new NextAction("taxi", relevance), nullptr)));
triggers.push_back(new TriggerNode("trade status", NextAction::array(0, new NextAction("accept trade", relevance), new NextAction("equip upgrades", relevance), nullptr)));
triggers.push_back(new TriggerNode("trade status extended", NextAction::array(0, new NextAction("trade status extended", relevance), nullptr)));
triggers.push_back(new TriggerNode("area trigger", NextAction::array(0, new NextAction("reach area trigger", relevance), nullptr)));
triggers.push_back(new TriggerNode("within area trigger", NextAction::array(0, new NextAction("area trigger", relevance), nullptr)));
triggers.push_back(new TriggerNode("loot response", NextAction::array(0, new NextAction("store loot", relevance), nullptr)));
triggers.push_back(new TriggerNode("item push result", NextAction::array(0, new NextAction("query item usage", relevance), new NextAction("equip upgrades", relevance), nullptr)));
triggers.push_back(new TriggerNode("item push result", NextAction::array(0, new NextAction("unlock items", relevance),
new NextAction("open items", relevance),
new NextAction("query item usage", relevance),
new NextAction("equip upgrades", relevance), nullptr)));
triggers.push_back(new TriggerNode("item push result", NextAction::array(0, new NextAction("quest item push result", relevance), nullptr)));
triggers.push_back(new TriggerNode("ready check finished", NextAction::array(0, new NextAction("finish ready check", relevance), nullptr)));
// triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("security check", relevance), new NextAction("check mail", relevance), nullptr)));
triggers.push_back(new TriggerNode("guild invite", NextAction::array(0, new NextAction("guild accept", relevance), nullptr)));
@@ -79,7 +84,7 @@ WorldPacketHandlerStrategy::WorldPacketHandlerStrategy(PlayerbotAI* botAI) : Pas
// quests
supported.push_back("quest update add kill");
supported.push_back("quest update add item");
// supported.push_back("quest update add item");
supported.push_back("quest update failed");
supported.push_back("quest update failed timer");
supported.push_back("quest update complete");

View File

@@ -8,8 +8,18 @@
#include "Event.h"
#include "ObjectGuid.h"
#include "Player.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
bool CastStealthAction::isUseful()
{
Unit* target = AI_VALUE(Unit*, "current target");
if (target && bot->GetDistance(target) >= sPlayerbotAIConfig->spellDistance)
return false;
return true;
}
bool CastStealthAction::isPossible()
{
// do not use with WSG flag or EYE flag

View File

@@ -44,7 +44,7 @@ public:
CastStealthAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "stealth") {}
std::string const GetTargetName() override { return "self target"; }
bool isUseful() override;
bool isPossible() override;
};

View File

@@ -2,18 +2,32 @@
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include "ChatHelper.h"
#include "G3D/Vector2.h"
#include "GossipDef.h"
#include "IVMapMgr.h"
#include "NewRpgInfo.h"
#include "NewRpgStrategy.h"
#include "Object.h"
#include "ObjectAccessor.h"
#include "ObjectDefines.h"
#include "ObjectGuid.h"
#include "ObjectMgr.h"
#include "PathGenerator.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "Position.h"
#include "QuestDef.h"
#include "Random.h"
#include "RandomPlayerbotMgr.h"
#include "SharedDefines.h"
#include "StatsWeightCalculator.h"
#include "Timer.h"
#include "TravelMgr.h"
#include "BroadcastHelper.h"
#include "World.h"
bool TellRpgStatusAction::Execute(Event event)
@@ -26,118 +40,156 @@ bool TellRpgStatusAction::Execute(Event event)
return true;
}
bool StartRpgDoQuestAction::Execute(Event event)
{
Player* owner = event.getOwner();
if (!owner)
return false;
std::string const text = event.getParam();
PlayerbotChatHandler ch(owner);
uint32 questId = ch.extractQuestId(text);
const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
if (quest)
{
botAI->rpgInfo.ChangeToDoQuest(questId, quest);
bot->Whisper("Start to do quest " + std::to_string(questId), LANG_UNIVERSAL, owner);
return true;
}
bot->Whisper("Invalid quest " + text, LANG_UNIVERSAL, owner);
return false;
}
bool NewRpgStatusUpdateAction::Execute(Event event)
{
NewRpgInfo& info = botAI->rpgInfo;
/// @TODO: Refactor by transition probability
switch (info.status)
{
case NewRpgStatus::IDLE:
case RPG_IDLE:
{
uint32 roll = urand(1, 100);
// IDLE -> NEAR_NPC
// if ((!info.lastNearNpc || info.lastNearNpc + setNpcInterval < getMSTime()) && roll <= 30)
if (roll <= 30)
{
GuidVector possibleTargets = AI_VALUE(GuidVector, "possible rpg targets");
if (!possibleTargets.empty())
GuidVector possibleTargets = AI_VALUE(GuidVector, "possible new rpg targets");
if (possibleTargets.size() >= 3)
{
info.Reset();
info.lastNearNpc = getMSTime();
info.status = NewRpgStatus::NEAR_NPC;
info.ChangeToNearNpc();
return true;
}
}
// IDLE -> GO_INNKEEPER
else if (roll <= 45)
{
WorldPosition pos = SelectRandomInnKeeperPos();
WorldPosition pos = SelectRandomInnKeeperPos(bot);
if (pos != WorldPosition() && bot->GetExactDist(pos) > 50.0f)
{
info.Reset();
info.lastGoInnKeeper = getMSTime();
info.status = NewRpgStatus::GO_INNKEEPER;
info.innKeeperPos = pos;
info.ChangeToGoInnkeeper(pos);
return true;
}
}
// IDLE -> GO_GRIND
else if (roll <= 90)
else if (roll <= 100)
{
WorldPosition pos = SelectRandomGrindPos();
if (roll >= 60)
{
std::vector<uint32> availableQuests;
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{
uint32 questId = bot->GetQuestSlotQuestId(slot);
if (botAI->lowPriorityQuest.find(questId) != botAI->lowPriorityQuest.end())
continue;
std::vector<POIInfo> poiInfo;
if (GetQuestPOIPosAndObjectiveIdx(questId, poiInfo, true))
{
availableQuests.push_back(questId);
}
}
if (availableQuests.size())
{
uint32 questId = availableQuests[urand(0, availableQuests.size() - 1)];
const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
if (quest)
{
// IDLE -> DO_QUEST
info.ChangeToDoQuest(questId, quest);
return true;
}
}
}
WorldPosition pos = SelectRandomGrindPos(bot);
if (pos != WorldPosition())
{
info.Reset();
info.lastGoGrind = getMSTime();
info.status = NewRpgStatus::GO_GRIND;
info.grindPos = pos;
info.ChangeToGoGrind(pos);
return true;
}
}
// IDLE -> REST
info.Reset();
info.status = NewRpgStatus::REST;
info.lastRest = getMSTime();
info.ChangeToRest();
bot->SetStandState(UNIT_STAND_STATE_SIT);
return true;
}
case NewRpgStatus::GO_GRIND:
case RPG_GO_GRIND:
{
WorldPosition& originalPos = info.grindPos;
assert(info.grindPos != WorldPosition());
WorldPosition& originalPos = info.go_grind.pos;
assert(info.go_grind.pos != WorldPosition());
// GO_GRIND -> NEAR_RANDOM
if (bot->GetExactDist(originalPos) < 10.0f)
{
info.Reset();
info.status = NewRpgStatus::NEAR_RANDOM;
info.lastNearRandom = getMSTime();
info.grindPos = WorldPosition();
info.ChangeToNearRandom();
return true;
}
break;
}
case NewRpgStatus::GO_INNKEEPER:
case RPG_GO_INNKEEPER:
{
WorldPosition& originalPos = info.innKeeperPos;
assert(info.innKeeperPos != WorldPosition());
WorldPosition& originalPos = info.go_innkeeper.pos;
assert(info.go_innkeeper.pos != WorldPosition());
// GO_INNKEEPER -> NEAR_NPC
if (bot->GetExactDist(originalPos) < 10.0f)
{
info.Reset();
info.lastNearNpc = getMSTime();
info.status = NewRpgStatus::NEAR_NPC;
info.innKeeperPos = WorldPosition();
info.ChangeToNearNpc();
return true;
}
break;
}
case NewRpgStatus::NEAR_RANDOM:
case RPG_NEAR_RANDOM:
{
// NEAR_RANDOM -> IDLE
if (info.lastNearRandom + statusNearRandomDuration < getMSTime())
if (info.HasStatusPersisted(statusNearRandomDuration))
{
info.Reset();
info.status = NewRpgStatus::IDLE;
info.ChangeToIdle();
return true;
}
break;
}
case NewRpgStatus::NEAR_NPC:
case RPG_DO_QUEST:
{
if (info.lastNearNpc + statusNearNpcDuration < getMSTime())
// DO_QUEST -> IDLE
if (info.HasStatusPersisted(statusDoQuestDuration))
{
info.Reset();
info.status = NewRpgStatus::IDLE;
info.ChangeToIdle();
return true;
}
break;
}
case NewRpgStatus::REST:
case RPG_NEAR_NPC:
{
if (info.HasStatusPersisted(statusNearNpcDuration))
{
info.ChangeToIdle();
return true;
}
break;
}
case RPG_REST:
{
// REST -> IDLE
if (info.lastRest + statusRestDuration < getMSTime())
if (info.HasStatusPersisted(statusRestDuration))
{
info.Reset();
info.status = NewRpgStatus::IDLE;
info.ChangeToIdle();
return true;
}
break;
@@ -148,258 +200,260 @@ bool NewRpgStatusUpdateAction::Execute(Event event)
return false;
}
WorldPosition NewRpgStatusUpdateAction::SelectRandomGrindPos()
bool NewRpgGoGrindAction::Execute(Event event)
{
const std::vector<WorldLocation>& locs = sRandomPlayerbotMgr->locsPerLevelCache[bot->GetLevel()];
std::vector<WorldLocation> lo_prepared_locs, hi_prepared_locs;
for (auto& loc : locs)
{
if (bot->GetMapId() != loc.GetMapId())
continue;
if (SearchQuestGiverAndAcceptOrReward())
return true;
if (bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) !=
bot->GetZoneId())
continue;
if (bot->GetExactDist(loc) < 500.0f)
{
hi_prepared_locs.push_back(loc);
}
if (bot->GetExactDist(loc) < 2500.0f)
{
lo_prepared_locs.push_back(loc);
}
}
WorldPosition dest{};
if (urand(1, 100) <= 50 && !hi_prepared_locs.empty())
{
uint32 idx = urand(0, hi_prepared_locs.size() - 1);
dest = hi_prepared_locs[idx];
}
else if (!lo_prepared_locs.empty())
{
uint32 idx = urand(0, lo_prepared_locs.size() - 1);
dest = lo_prepared_locs[idx];
}
LOG_DEBUG("playerbots", "[New Rpg] Bot {} select random grind pos Map:{} X:{} Y:{} Z:{} ({}+{} available in {})",
bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(),
hi_prepared_locs.size(), lo_prepared_locs.size() - hi_prepared_locs.size(), locs.size());
return dest;
return MoveFarTo(botAI->rpgInfo.go_grind.pos);
}
WorldPosition NewRpgStatusUpdateAction::SelectRandomInnKeeperPos()
bool NewRpgGoInnKeeperAction::Execute(Event event)
{
const std::vector<WorldLocation>& locs = IsAlliance(bot->getRace())
? sRandomPlayerbotMgr->allianceStarterPerLevelCache[bot->GetLevel()]
: sRandomPlayerbotMgr->hordeStarterPerLevelCache[bot->GetLevel()];
std::vector<WorldLocation> prepared_locs;
for (auto& loc : locs)
{
if (bot->GetMapId() != loc.GetMapId())
continue;
if (bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) !=
bot->GetZoneId())
continue;
float range = bot->GetLevel() <= 5 ? 500.0f : 2500.0f;
if (bot->GetExactDist(loc) < range)
{
prepared_locs.push_back(loc);
}
}
WorldPosition dest{};
if (!prepared_locs.empty())
{
uint32 idx = urand(0, prepared_locs.size() - 1);
dest = prepared_locs[idx];
}
LOG_DEBUG("playerbots", "[New Rpg] Bot {} select random inn keeper pos Map:{} X:{} Y:{} Z:{} ({} available in {})",
bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(),
prepared_locs.size(), locs.size());
return dest;
if (SearchQuestGiverAndAcceptOrReward())
return true;
return MoveFarTo(botAI->rpgInfo.go_innkeeper.pos);
}
bool NewRpgGoFarAwayPosAction::MoveFarTo(WorldPosition dest)
{
if (dest == WorldPosition())
return false;
float dis = bot->GetExactDist(dest);
if (dis < pathFinderDis)
{
return MoveTo(dest.getMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), false, false,
false, true);
}
// performance optimization
if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL))
{
return false;
}
// stuck check
float disToDest = bot->GetDistance(dest);
if (disToDest + 1.0f < botAI->rpgInfo.nearestMoveFarDis)
{
botAI->rpgInfo.nearestMoveFarDis = disToDest;
botAI->rpgInfo.stuckTs = getMSTime();
botAI->rpgInfo.stuckAttempts = 0;
}
else if (++botAI->rpgInfo.stuckAttempts >= 10 && botAI->rpgInfo.stuckTs + stuckTime < getMSTime())
{
// Unfortunately we've been stuck here for over 5 mins, fallback to teleporting directly to the destination
botAI->rpgInfo.stuckTs = getMSTime();
botAI->rpgInfo.stuckAttempts = 0;
const AreaTableEntry* entry = sAreaTableStore.LookupEntry(bot->GetZoneId());
std::string zone_name = PlayerbotAI::GetLocalizedAreaName(entry);
LOG_DEBUG("playerbots", "[New Rpg] Teleport {} from ({},{},{},{}) to ({},{},{},{}) as it stuck when moving far - Zone: {} ({})", bot->GetName(),
bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId(),
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), dest.getMapId(), bot->GetZoneId(), zone_name);
return bot->TeleportTo(dest);
}
float minDelta = M_PI;
const float x = bot->GetPositionX();
const float y = bot->GetPositionY();
const float z = bot->GetPositionZ();
float rx, ry, rz;
bool found = false;
int attempt = 3;
while (--attempt)
{
float angle = bot->GetAngle(&dest);
float delta = urand(1, 100) <= 75 ? (rand_norm() - 0.5) * M_PI * 0.5 : (rand_norm() - 0.5) * M_PI * 2;
angle += delta;
float dis = rand_norm() * pathFinderDis;
float dx = x + cos(angle) * dis;
float dy = y + sin(angle) * dis;
float dz = z + 0.5f;
PathGenerator path(bot);
path.CalculatePath(dx, dy, dz);
PathType type = path.GetPathType();
uint32 typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY;
bool canReach = !(type & (~typeOk));
if (canReach && fabs(delta) <= minDelta)
{
found = true;
const G3D::Vector3& endPos = path.GetActualEndPosition();
rx = endPos.x;
ry = endPos.y;
rz = endPos.z;
minDelta = fabs(delta);
}
}
if (found)
{
return MoveTo(bot->GetMapId(), rx, ry, rz, false, false, false, true);
}
return false;
}
bool NewRpgGoGrindAction::Execute(Event event) { return MoveFarTo(botAI->rpgInfo.grindPos); }
bool NewRpgGoInnKeeperAction::Execute(Event event) { return MoveFarTo(botAI->rpgInfo.innKeeperPos); }
bool NewRpgMoveRandomAction::Execute(Event event)
{
float distance = rand_norm() * moveStep;
Map* map = bot->GetMap();
const float x = bot->GetPositionX();
const float y = bot->GetPositionY();
const float z = bot->GetPositionZ();
int attempts = 5;
while (--attempts)
{
float angle = (float)rand_norm() * 2 * static_cast<float>(M_PI);
float dx = x + distance * cos(angle);
float dy = y + distance * sin(angle);
float dz = z;
if (!map->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
dx, dy, dz))
continue;
if (map->IsInWater(bot->GetPhaseMask(), dx, dy, dz, bot->GetCollisionHeight()))
continue;
bool moved = MoveTo(bot->GetMapId(), dx, dy, dz, false, false, false, true);
if (moved)
return true;
}
return false;
if (SearchQuestGiverAndAcceptOrReward())
return true;
return MoveRandomNear();
}
bool NewRpgMoveNpcAction::Execute(Event event)
{
NewRpgInfo& info = botAI->rpgInfo;
if (!info.npcPos)
if (!info.near_npc.npcOrGo)
{
GuidVector possibleTargets = AI_VALUE(GuidVector, "possible rpg targets");
if (possibleTargets.empty())
return false;
int idx = urand(0, possibleTargets.size() - 1);
ObjectGuid guid = possibleTargets[idx];
Unit* unit = botAI->GetUnit(guid);
if (unit)
// No npc can be found, switch to IDLE
ObjectGuid npcOrGo = ChooseNpcOrGameObjectToInteract();
if (npcOrGo.IsEmpty())
{
info.npcPos = GuidPosition(unit);
info.lastReachNpc = 0;
info.ChangeToIdle();
return true;
}
else
return false;
info.near_npc.npcOrGo = npcOrGo;
info.near_npc.lastReach = 0;
return true;
}
if (bot->GetDistance(info.npcPos) <= INTERACTION_DISTANCE)
WorldObject* object = ObjectAccessor::GetWorldObject(*bot, info.near_npc.npcOrGo);
if (object && bot->GetDistance(object) <= INTERACTION_DISTANCE)
{
if (!info.lastReachNpc)
if (!info.near_npc.lastReach)
{
info.lastReachNpc = getMSTime();
info.near_npc.lastReach = getMSTime();
if (bot->CanInteractWithQuestGiver(object))
InteractWithNpcOrGameObjectForQuest(info.near_npc.npcOrGo);
return true;
}
if (info.lastReachNpc && info.lastReachNpc + stayTime > getMSTime())
if (info.near_npc.lastReach && GetMSTimeDiffToNow(info.near_npc.lastReach) < npcStayTime)
return false;
info.npcPos = GuidPosition();
info.lastReachNpc = 0;
// has reached the npc for more than `npcStayTime`, select the next target
info.near_npc.npcOrGo = ObjectGuid();
info.near_npc.lastReach = 0;
}
else
{
assert(info.npcPos);
Unit* unit = botAI->GetUnit(info.npcPos);
if (!unit)
return false;
float x = unit->GetPositionX();
float y = unit->GetPositionY();
float z = unit->GetPositionZ();
float mapId = unit->GetMapId();
float angle = 0.f;
if (bot->IsWithinLOS(x, y, z))
{
if (!unit->isMoving())
angle = unit->GetAngle(bot) + (M_PI * irand(-25, 25) / 100.0); // Closest 45 degrees towards the target
else
angle = unit->GetOrientation() +
(M_PI * irand(-25, 25) / 100.0); // 45 degrees infront of target (leading it's movement)
}
else
angle = 2 * M_PI * rand_norm(); // A circle around the target.
float rnd = rand_norm();
x += cos(angle) * INTERACTION_DISTANCE * rnd;
y += sin(angle) * INTERACTION_DISTANCE * rnd;
// bool exact = true;
if (!unit->GetMap()->CheckCollisionAndGetValidCoords(unit, unit->GetPositionX(), unit->GetPositionY(),
unit->GetPositionZ(), x, y, z))
{
x = unit->GetPositionX();
y = unit->GetPositionY();
z = unit->GetPositionZ();
// exact = false;
}
return MoveTo(mapId, x, y, z, false, false, false, true);
return MoveWorldObjectTo(info.near_npc.npcOrGo);
}
return true;
}
}
bool NewRpgDoQuestAction::Execute(Event event)
{
if (SearchQuestGiverAndAcceptOrReward())
return true;
NewRpgInfo& info = botAI->rpgInfo;
uint32 questId = RPG_INFO(quest, questId);
const Quest* quest = RPG_INFO(quest, quest);
uint8 questStatus = bot->GetQuestStatus(questId);
switch (questStatus)
{
case QUEST_STATUS_INCOMPLETE:
return DoIncompleteQuest();
case QUEST_STATUS_COMPLETE:
return DoCompletedQuest();
default:
break;
}
botAI->rpgInfo.ChangeToIdle();
return true;
}
bool NewRpgDoQuestAction::DoIncompleteQuest()
{
uint32 questId = RPG_INFO(do_quest, questId);
if (botAI->rpgInfo.do_quest.pos != WorldPosition())
{
/// @TODO: extract to a new function
int32 currentObjective = botAI->rpgInfo.do_quest.objectiveIdx;
// check if the objective has completed
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
const QuestStatusData& q_status = bot->getQuestStatusMap().at(questId);
bool completed = true;
if (currentObjective < QUEST_OBJECTIVES_COUNT)
{
if (q_status.CreatureOrGOCount[currentObjective] < quest->RequiredNpcOrGoCount[currentObjective])
completed = false;
}
else if (currentObjective < QUEST_OBJECTIVES_COUNT + QUEST_ITEM_OBJECTIVES_COUNT)
{
if (q_status.ItemCount[currentObjective - QUEST_OBJECTIVES_COUNT] <
quest->RequiredItemCount[currentObjective - QUEST_OBJECTIVES_COUNT])
completed = false;
}
// the current objective is completed, clear and find a new objective later
if (completed)
{
botAI->rpgInfo.do_quest.lastReachPOI = 0;
botAI->rpgInfo.do_quest.pos = WorldPosition();
botAI->rpgInfo.do_quest.objectiveIdx = 0;
}
}
if (botAI->rpgInfo.do_quest.pos == WorldPosition())
{
std::vector<POIInfo> poiInfo;
if (!GetQuestPOIPosAndObjectiveIdx(questId, poiInfo))
{
// can't find a poi pos to go, stop doing quest for now
botAI->rpgInfo.ChangeToIdle();
return true;
}
uint32 rndIdx = urand(0, poiInfo.size() - 1);
G3D::Vector2 nearestPoi = poiInfo[rndIdx].pos;
int32 objectiveIdx = poiInfo[rndIdx].objectiveIdx;
float dx = nearestPoi.x, dy = nearestPoi.y;
// z = MAX_HEIGHT as we do not know accurate z
float dz = std::max(bot->GetMap()->GetHeight(dx, dy, MAX_HEIGHT), bot->GetMap()->GetWaterLevel(dx, dy));
// double check for GetQuestPOIPosAndObjectiveIdx
if (dz == INVALID_HEIGHT || dz == VMAP_INVALID_HEIGHT_VALUE)
return false;
WorldPosition pos(bot->GetMapId(), dx, dy, dz);
botAI->rpgInfo.do_quest.lastReachPOI = 0;
botAI->rpgInfo.do_quest.pos = pos;
botAI->rpgInfo.do_quest.objectiveIdx = objectiveIdx;
}
if (bot->GetDistance(botAI->rpgInfo.do_quest.pos) > 10.0f && !botAI->rpgInfo.do_quest.lastReachPOI)
{
return MoveFarTo(botAI->rpgInfo.do_quest.pos);
}
// Now we are near the quest objective
// kill mobs and looting quest should be done automatically by grind strategy
if (!botAI->rpgInfo.do_quest.lastReachPOI)
{
botAI->rpgInfo.do_quest.lastReachPOI = getMSTime();
return true;
}
// stayed at this POI for more than 5 minutes
if (GetMSTimeDiffToNow(botAI->rpgInfo.do_quest.lastReachPOI) >= poiStayTime)
{
bool hasProgression = false;
int32 currentObjective = botAI->rpgInfo.do_quest.objectiveIdx;
// check if the objective has progression
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
const QuestStatusData& q_status = bot->getQuestStatusMap().at(questId);
if (currentObjective < QUEST_OBJECTIVES_COUNT)
{
if (q_status.CreatureOrGOCount[currentObjective] != 0 && quest->RequiredNpcOrGoCount[currentObjective])
hasProgression = true;
}
else if (currentObjective < QUEST_OBJECTIVES_COUNT + QUEST_ITEM_OBJECTIVES_COUNT)
{
if (q_status.ItemCount[currentObjective - QUEST_OBJECTIVES_COUNT] != 0 &&
quest->RequiredItemCount[currentObjective - QUEST_OBJECTIVES_COUNT])
hasProgression = true;
}
if (!hasProgression)
{
// we has reach the poi for more than 5 mins but no progession
// may not be able to complete this quest, marked as abandoned
/// @TODO: It may be better to make lowPriorityQuest a global set shared by all bots (or saved in db)
botAI->lowPriorityQuest.insert(questId);
botAI->rpgStatistic.questAbandoned++;
LOG_DEBUG("playerbots", "[New rpg] {} marked as abandoned quest {}", bot->GetName(), questId);
botAI->rpgInfo.ChangeToIdle();
return true;
}
// clear and select another poi later
botAI->rpgInfo.do_quest.lastReachPOI = 0;
botAI->rpgInfo.do_quest.pos = WorldPosition();
botAI->rpgInfo.do_quest.objectiveIdx = 0;
return true;
}
return MoveRandomNear(20.0f);
}
bool NewRpgDoQuestAction::DoCompletedQuest()
{
uint32 questId = RPG_INFO(quest, questId);
const Quest* quest = RPG_INFO(quest, quest);
if (RPG_INFO(quest, objectiveIdx) != -1)
{
// if quest is completed, back to poi with -1 idx to reward
BroadcastHelper::BroadcastQuestUpdateComplete(botAI, bot, quest);
botAI->rpgStatistic.questCompleted++;
std::vector<POIInfo> poiInfo;
if (!GetQuestPOIPosAndObjectiveIdx(questId, poiInfo, true))
{
// can't find a poi pos to reward, stop doing quest for now
botAI->rpgInfo.ChangeToIdle();
return false;
}
assert(poiInfo.size() > 0);
// now we get the place to get rewarded
float dx = poiInfo[0].pos.x, dy = poiInfo[0].pos.y;
// z = MAX_HEIGHT as we do not know accurate z
float dz = std::max(bot->GetMap()->GetHeight(dx, dy, MAX_HEIGHT), bot->GetMap()->GetWaterLevel(dx, dy));
// double check for GetQuestPOIPosAndObjectiveIdx
if (dz == INVALID_HEIGHT || dz == VMAP_INVALID_HEIGHT_VALUE)
return false;
WorldPosition pos(bot->GetMapId(), dx, dy, dz);
botAI->rpgInfo.do_quest.lastReachPOI = 0;
botAI->rpgInfo.do_quest.pos = pos;
botAI->rpgInfo.do_quest.objectiveIdx = -1;
}
if (botAI->rpgInfo.do_quest.pos == WorldPosition())
return false;
if (bot->GetDistance(botAI->rpgInfo.do_quest.pos) > 10.0f && !botAI->rpgInfo.do_quest.lastReachPOI)
return MoveFarTo(botAI->rpgInfo.do_quest.pos);
// Now we are near the qoi of reward
// the quest should be rewarded by SearchQuestGiverAndAcceptOrReward
if (!botAI->rpgInfo.do_quest.lastReachPOI)
{
botAI->rpgInfo.do_quest.lastReachPOI = getMSTime();
return true;
}
// stayed at this POI for more than 5 minutes
if (GetMSTimeDiffToNow(botAI->rpgInfo.do_quest.lastReachPOI) >= poiStayTime)
{
// e.g. Can not reward quest to gameobjects
/// @TODO: It may be better to make lowPriorityQuest a global set shared by all bots (or saved in db)
botAI->lowPriorityQuest.insert(questId);
botAI->rpgStatistic.questAbandoned++;
LOG_DEBUG("playerbots", "[New rpg] {} marked as abandoned quest {}", bot->GetName(), questId);
botAI->rpgInfo.ChangeToIdle();
return true;
}
return false;
}

View File

@@ -3,9 +3,15 @@
#include "Duration.h"
#include "MovementActions.h"
#include "NewRpgInfo.h"
#include "NewRpgStrategy.h"
#include "Object.h"
#include "ObjectDefines.h"
#include "ObjectGuid.h"
#include "QuestDef.h"
#include "TravelMgr.h"
#include "PlayerbotAI.h"
#include "NewRpgBaseAction.h"
class TellRpgStatusAction : public Action
{
@@ -15,65 +21,78 @@ public:
bool Execute(Event event) override;
};
class NewRpgStatusUpdateAction : public Action
class StartRpgDoQuestAction : public Action
{
public:
NewRpgStatusUpdateAction(PlayerbotAI* botAI) : Action(botAI, "new rpg status update") {}
StartRpgDoQuestAction(PlayerbotAI* botAI) : Action(botAI, "start rpg do quest") {}
bool Execute(Event event) override;
};
class NewRpgStatusUpdateAction : public NewRpgBaseAction
{
public:
NewRpgStatusUpdateAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg status update")
{
// int statusCount = RPG_STATUS_END - 1;
// transitionMat.resize(statusCount, std::vector<int>(statusCount, 0));
// transitionMat[RPG_IDLE][RPG_GO_GRIND] = 20;
// transitionMat[RPG_IDLE][RPG_GO_INNKEEPER] = 15;
// transitionMat[RPG_IDLE][RPG_NEAR_NPC] = 30;
// transitionMat[RPG_IDLE][RPG_DO_QUEST] = 35;
}
bool Execute(Event event) override;
protected:
// const int32 setGrindInterval = 5 * 60 * 1000;
// const int32 setNpcInterval = 1 * 60 * 1000;
// static NewRpgStatusTransitionProb transitionMat;
const int32 statusNearNpcDuration = 5 * 60 * 1000;
const int32 statusNearRandomDuration = 5 * 60 * 1000;
const int32 statusRestDuration = 30 * 1000;
WorldPosition SelectRandomGrindPos();
WorldPosition SelectRandomInnKeeperPos();
const int32 statusDoQuestDuration = 30 * 60 * 1000;
};
class NewRpgGoFarAwayPosAction : public MovementAction
class NewRpgGoGrindAction : public NewRpgBaseAction
{
public:
NewRpgGoFarAwayPosAction(PlayerbotAI* botAI, std::string name) : MovementAction(botAI, name) {}
// bool Execute(Event event) override;
bool MoveFarTo(WorldPosition dest);
protected:
// WorldPosition dest;
const float pathFinderDis = 70.0f; // path finder
const uint32 stuckTime = 5 * 60 * 1000;
};
class NewRpgGoGrindAction : public NewRpgGoFarAwayPosAction
{
public:
NewRpgGoGrindAction(PlayerbotAI* botAI) : NewRpgGoFarAwayPosAction(botAI, "new rpg go grind") {}
NewRpgGoGrindAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg go grind") {}
bool Execute(Event event) override;
};
class NewRpgGoInnKeeperAction : public NewRpgGoFarAwayPosAction
class NewRpgGoInnKeeperAction : public NewRpgBaseAction
{
public:
NewRpgGoInnKeeperAction(PlayerbotAI* botAI) : NewRpgGoFarAwayPosAction(botAI, "new rpg go innkeeper") {}
NewRpgGoInnKeeperAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg go innkeeper") {}
bool Execute(Event event) override;
};
class NewRpgMoveRandomAction : public MovementAction
class NewRpgMoveRandomAction : public NewRpgBaseAction
{
public:
NewRpgMoveRandomAction(PlayerbotAI* botAI) : MovementAction(botAI, "new rpg move random") {}
NewRpgMoveRandomAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg move random") {}
bool Execute(Event event) override;
};
class NewRpgMoveNpcAction : public NewRpgBaseAction
{
public:
NewRpgMoveNpcAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg move npcs") {}
bool Execute(Event event) override;
const uint32 npcStayTime = 8 * 1000;
};
class NewRpgDoQuestAction : public NewRpgBaseAction
{
public:
NewRpgDoQuestAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg do quest") {}
bool Execute(Event event) override;
protected:
const float moveStep = 50.0f;
};
class NewRpgMoveNpcAction : public MovementAction
{
public:
NewRpgMoveNpcAction(PlayerbotAI* botAI) : MovementAction(botAI, "new rpg move npcs") {}
bool Execute(Event event) override;
protected:
const uint32 stayTime = 8 * 1000;
bool DoIncompleteQuest();
bool DoCompletedQuest();
const uint32 poiStayTime = 5 * 60 * 1000;
};
#endif

View File

@@ -0,0 +1,801 @@
#include "NewRpgBaseAction.h"
#include "ChatHelper.h"
#include "G3D/Vector2.h"
#include "GameObject.h"
#include "GossipDef.h"
#include "GridTerrainData.h"
#include "IVMapMgr.h"
#include "NewRpgInfo.h"
#include "NewRpgStrategy.h"
#include "Object.h"
#include "ObjectAccessor.h"
#include "ObjectDefines.h"
#include "ObjectGuid.h"
#include "ObjectMgr.h"
#include "PathGenerator.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "Position.h"
#include "QuestDef.h"
#include "Random.h"
#include "RandomPlayerbotMgr.h"
#include "SharedDefines.h"
#include "StatsWeightCalculator.h"
#include "Timer.h"
#include "TravelMgr.h"
#include "BroadcastHelper.h"
bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
{
if (dest == WorldPosition())
return false;
if (dest != botAI->rpgInfo.moveFarPos)
{
// clear stuck information if it's a new dest
botAI->rpgInfo.SetMoveFarTo(dest);
}
float dis = bot->GetExactDist(dest);
if (dis < pathFinderDis)
{
return MoveTo(dest.getMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), false, false,
false, true);
}
// performance optimization
if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL))
{
return false;
}
// stuck check
float disToDest = bot->GetDistance(dest);
if (disToDest + 1.0f < botAI->rpgInfo.nearestMoveFarDis)
{
botAI->rpgInfo.nearestMoveFarDis = disToDest;
botAI->rpgInfo.stuckTs = getMSTime();
botAI->rpgInfo.stuckAttempts = 0;
}
else if (++botAI->rpgInfo.stuckAttempts >= 10 && GetMSTimeDiffToNow(botAI->rpgInfo.stuckTs) >= stuckTime)
{
// Unfortunately we've been stuck here for over 5 mins, fallback to teleporting directly to the destination
botAI->rpgInfo.stuckTs = getMSTime();
botAI->rpgInfo.stuckAttempts = 0;
const AreaTableEntry* entry = sAreaTableStore.LookupEntry(bot->GetZoneId());
std::string zone_name = PlayerbotAI::GetLocalizedAreaName(entry);
LOG_DEBUG("playerbots", "[New Rpg] Teleport {} from ({},{},{},{}) to ({},{},{},{}) as it stuck when moving far - Zone: {} ({})", bot->GetName(),
bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId(),
dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), dest.getMapId(), bot->GetZoneId(), zone_name);
return bot->TeleportTo(dest);
}
float minDelta = M_PI;
const float x = bot->GetPositionX();
const float y = bot->GetPositionY();
const float z = bot->GetPositionZ();
float rx, ry, rz;
bool found = false;
int attempt = 3;
while (--attempt)
{
float angle = bot->GetAngle(&dest);
float delta = urand(1, 100) <= 75 ? (rand_norm() - 0.5) * M_PI * 0.5 : (rand_norm() - 0.5) * M_PI * 2;
angle += delta;
float dis = rand_norm() * pathFinderDis;
float dx = x + cos(angle) * dis;
float dy = y + sin(angle) * dis;
float dz = z + 0.5f;
PathGenerator path(bot);
path.CalculatePath(dx, dy, dz);
PathType type = path.GetPathType();
uint32 typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY;
bool canReach = !(type & (~typeOk));
if (canReach && fabs(delta) <= minDelta)
{
found = true;
const G3D::Vector3& endPos = path.GetActualEndPosition();
rx = endPos.x;
ry = endPos.y;
rz = endPos.z;
minDelta = fabs(delta);
}
}
if (found)
{
return MoveTo(bot->GetMapId(), rx, ry, rz, false, false, false, true);
}
return false;
}
bool NewRpgBaseAction::MoveWorldObjectTo(ObjectGuid guid, float distance)
{
if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL))
{
return false;
}
WorldObject* object = botAI->GetWorldObject(guid);
if (!object)
return false;
float x = object->GetPositionX();
float y = object->GetPositionY();
float z = object->GetPositionZ();
float mapId = object->GetMapId();
float angle = 0.f;
if (!object->ToUnit() || !object->ToUnit()->isMoving())
angle = object->GetAngle(bot) + (M_PI * irand(-25, 25) / 100.0); // Closest 45 degrees towards the target
else
angle = object->GetOrientation() +
(M_PI * irand(-25, 25) / 100.0); // 45 degrees infront of target (leading it's movement)
float rnd = rand_norm();
x += cos(angle) * distance * rnd;
y += sin(angle) * distance * rnd;
if (!object->GetMap()->CheckCollisionAndGetValidCoords(object, object->GetPositionX(), object->GetPositionY(),
object->GetPositionZ(), x, y, z))
{
x = object->GetPositionX();
y = object->GetPositionY();
z = object->GetPositionZ();
}
return MoveTo(mapId, x, y, z, false, false, false, true);
}
bool NewRpgBaseAction::MoveRandomNear(float moveStep, MovementPriority priority)
{
if (IsWaitingForLastMove(priority))
{
return false;
}
float distance = rand_norm() * moveStep;
Map* map = bot->GetMap();
const float x = bot->GetPositionX();
const float y = bot->GetPositionY();
const float z = bot->GetPositionZ();
int attempts = 5;
while (--attempts)
{
float angle = (float)rand_norm() * 2 * static_cast<float>(M_PI);
float dx = x + distance * cos(angle);
float dy = y + distance * sin(angle);
float dz = z;
PathGenerator path(bot);
path.CalculatePath(dx, dy, dz);
PathType type = path.GetPathType();
uint32 typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY;
bool canReach = !(type & (~typeOk));
if (!canReach)
continue;
if (!map->CanReachPositionAndGetValidCoords(bot, dx, dy, dz))
continue;
if (map->IsInWater(bot->GetPhaseMask(), dx, dy, dz, bot->GetCollisionHeight()))
continue;
bool moved = MoveTo(bot->GetMapId(), dx, dy, dz, false, false, false, true, priority);
if (moved)
return true;
}
return false;
}
bool NewRpgBaseAction::ForceToWait(uint32 duration, MovementPriority priority)
{
AI_VALUE(LastMovement&, "last movement").Set(bot->GetMapId(),
bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation(), duration, priority);
return true;
}
/// @TODO: Fix redundant code
/// Quest related method refer to TalkToQuestGiverAction.h
bool NewRpgBaseAction::InteractWithNpcOrGameObjectForQuest(ObjectGuid guid)
{
WorldObject* object = ObjectAccessor::GetWorldObject(*bot, guid);
if (!object || !bot->CanInteractWithQuestGiver(object))
return false;
// Creature* creature = bot->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_NONE);
// if (creature)
// {
// WorldPacket packet(CMSG_GOSSIP_HELLO);
// packet << guid;
// bot->GetSession()->HandleGossipHelloOpcode(packet);
// }
bot->PrepareQuestMenu(guid);
const QuestMenu &menu = bot->PlayerTalkClass->GetQuestMenu();
if (menu.Empty())
return true;
for (uint8 idx = 0; idx < menu.GetMenuItemCount(); idx++)
{
const QuestMenuItem &item = menu.GetItem(idx);
const Quest* quest = sObjectMgr->GetQuestTemplate(item.QuestId);
if (!quest)
continue;
const QuestStatus &status = bot->GetQuestStatus(item.QuestId);
if (status == QUEST_STATUS_NONE && bot->CanTakeQuest(quest, false) &&
bot->CanAddQuest(quest, false) && IsQuestWorthDoing(quest) && IsQuestCapableDoing(quest))
{
AcceptQuest(quest, guid);
if (botAI->GetMaster())
botAI->TellMasterNoFacing("Quest accepted " + ChatHelper::FormatQuest(quest));
BroadcastHelper::BroadcastQuestAccepted(botAI, bot, quest);
botAI->rpgStatistic.questAccepted++;
LOG_DEBUG("playerbots", "[New rpg] {} accept quest {}", bot->GetName(), quest->GetQuestId());
}
if (status == QUEST_STATUS_COMPLETE && bot->CanRewardQuest(quest, 0, false))
{
TurnInQuest(quest, guid);
if (botAI->GetMaster())
botAI->TellMasterNoFacing("Quest rewarded " + ChatHelper::FormatQuest(quest));
BroadcastHelper::BroadcastQuestTurnedIn(botAI, bot, quest);
botAI->rpgStatistic.questRewarded++;
LOG_DEBUG("playerbots", "[New rpg] {} turned in quest {}", bot->GetName(), quest->GetQuestId());
}
}
return true;
}
bool NewRpgBaseAction::AcceptQuest(Quest const* quest, ObjectGuid guid)
{
WorldPacket p(CMSG_QUESTGIVER_ACCEPT_QUEST);
uint32 unk1 = 0;
p << guid << quest->GetQuestId() << unk1;
p.rpos(0);
bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(p);
return true;
}
bool NewRpgBaseAction::TurnInQuest(Quest const* quest, ObjectGuid guid)
{
uint32 questID = quest->GetQuestId();
if (bot->GetQuestRewardStatus(questID))
{
return false;
}
if (!bot->CanRewardQuest(quest, false))
{
return false;
}
bot->PlayDistanceSound(621);
WorldPacket p(CMSG_QUESTGIVER_CHOOSE_REWARD);
p << guid << quest->GetQuestId();
if (quest->GetRewChoiceItemsCount() <= 1)
{
p << 0;
bot->GetSession()->HandleQuestgiverChooseRewardOpcode(p);
}
else
{
uint32 bestId = BestReward(quest);
p << bestId;
bot->GetSession()->HandleQuestgiverChooseRewardOpcode(p);
}
return true;
}
uint32 NewRpgBaseAction::BestReward(Quest const* quest)
{
ItemIds returnIds;
ItemUsage bestUsage = ITEM_USAGE_NONE;
if (quest->GetRewChoiceItemsCount() <= 1)
return 0;
else
{
for (uint8 i = 0; i < quest->GetRewChoiceItemsCount(); ++i)
{
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", quest->RewardChoiceItemId[i]);
if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE)
bestUsage = ITEM_USAGE_EQUIP;
else if (usage == ITEM_USAGE_BAD_EQUIP && bestUsage != ITEM_USAGE_EQUIP)
bestUsage = usage;
else if (usage != ITEM_USAGE_NONE && bestUsage == ITEM_USAGE_NONE)
bestUsage = usage;
}
StatsWeightCalculator calc(bot);
uint32 best = 0;
float bestScore = 0;
for (uint8 i = 0; i < quest->GetRewChoiceItemsCount(); ++i)
{
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", quest->RewardChoiceItemId[i]);
if (usage == bestUsage || usage == ITEM_USAGE_REPLACE)
{
float score = calc.CalculateItem(quest->RewardChoiceItemId[i]);
if (score > bestScore)
{
bestScore = score;
best = i;
}
}
}
return best;
}
}
bool NewRpgBaseAction::IsQuestWorthDoing(Quest const* quest)
{
bool isLowLevelQuest = bot->GetLevel() > (bot->GetQuestLevel(quest) + sWorld->getIntConfig(CONFIG_QUEST_LOW_LEVEL_HIDE_DIFF));
if (isLowLevelQuest)
return false;
if (quest->IsRepeatable())
return false;
if (quest->IsSeasonal())
return false;
return true;
}
bool NewRpgBaseAction::IsQuestCapableDoing(Quest const* quest)
{
bool highLevelQuest = bot->GetLevel() + 3 < bot->GetQuestLevel(quest);
if (highLevelQuest)
return false;
// Elite quest and dungeon quest etc
if (quest->GetType() != 0)
return false;
// now we only capable of doing solo quests
if (quest->GetSuggestedPlayers() >= 2)
return false;
return true;
}
bool NewRpgBaseAction::OrganizeQuestLog()
{
int32 freeSlotNum = 0;
for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
uint32 questId = bot->GetQuestSlotQuestId(i);
if (!questId)
freeSlotNum++;
}
// it's ok if we have two more free slots
if (freeSlotNum >= 2)
return false;
int32 dropped = 0;
// remove quests that not worth doing or not capable of doing
for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
uint32 questId = bot->GetQuestSlotQuestId(i);
if (!questId)
continue;
const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
if (!IsQuestWorthDoing(quest) ||
!IsQuestCapableDoing(quest) ||
bot->GetQuestStatus(questId) == QUEST_STATUS_FAILED)
{
LOG_DEBUG("playerbots", "[New rpg] {} drop quest {}", bot->GetName(), questId);
WorldPacket packet(CMSG_QUESTLOG_REMOVE_QUEST);
packet << (uint8)i;
bot->GetSession()->HandleQuestLogRemoveQuest(packet);
if (botAI->GetMaster())
botAI->TellMasterNoFacing("Quest dropped " + ChatHelper::FormatQuest(quest));
botAI->rpgStatistic.questDropped++;
dropped++;
}
}
// drop more than 8 quests at once to avoid repeated accept and drop
if (dropped >= 8)
return true;
// remove festival/class quests and quests in different zone
for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
uint32 questId = bot->GetQuestSlotQuestId(i);
if (!questId)
continue;
const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
if (quest->GetZoneOrSort() < 0 ||
(quest->GetZoneOrSort() > 0 && quest->GetZoneOrSort() != bot->GetZoneId()))
{
LOG_DEBUG("playerbots", "[New rpg] {} drop quest {}", bot->GetName(), questId);
WorldPacket packet(CMSG_QUESTLOG_REMOVE_QUEST);
packet << (uint8)i;
bot->GetSession()->HandleQuestLogRemoveQuest(packet);
if (botAI->GetMaster())
botAI->TellMasterNoFacing("Quest dropped " + ChatHelper::FormatQuest(quest));
botAI->rpgStatistic.questDropped++;
dropped++;
}
}
if (dropped >= 8)
return true;
// clear quests log
for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
uint32 questId = bot->GetQuestSlotQuestId(i);
if (!questId)
continue;
const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
LOG_DEBUG("playerbots", "[New rpg] {} drop quest {}", bot->GetName(), questId);
WorldPacket packet(CMSG_QUESTLOG_REMOVE_QUEST);
packet << (uint8)i;
bot->GetSession()->HandleQuestLogRemoveQuest(packet);
if (botAI->GetMaster())
botAI->TellMasterNoFacing("Quest dropped " + ChatHelper::FormatQuest(quest));
botAI->rpgStatistic.questDropped++;
}
return true;
}
bool NewRpgBaseAction::SearchQuestGiverAndAcceptOrReward()
{
OrganizeQuestLog();
if (ObjectGuid npcOrGo = ChooseNpcOrGameObjectToInteract(true, 80.0f))
{
WorldObject* object = ObjectAccessor::GetWorldObject(*bot, npcOrGo);
if (bot->CanInteractWithQuestGiver(object))
{
InteractWithNpcOrGameObjectForQuest(npcOrGo);
ForceToWait(5000);
return true;
}
return MoveWorldObjectTo(npcOrGo);
}
return false;
}
ObjectGuid NewRpgBaseAction::ChooseNpcOrGameObjectToInteract(bool questgiverOnly, float distanceLimit)
{
GuidVector possibleTargets = AI_VALUE(GuidVector, "possible new rpg targets");
GuidVector possibleGameObjects = AI_VALUE(GuidVector, "possible new rpg game objects");
if (possibleTargets.empty() && possibleGameObjects.empty())
return ObjectGuid();
WorldObject* nearestObject = nullptr;
for (ObjectGuid& guid: possibleTargets)
{
WorldObject* object = ObjectAccessor::GetWorldObject(*bot, guid);
if (!object || !object->IsInWorld())
continue;
if (distanceLimit && bot->GetDistance(object) > distanceLimit)
continue;
if (HasQuestToAcceptOrReward(object))
{
if (!nearestObject || bot->GetExactDist(nearestObject) > bot->GetExactDist(object))
nearestObject = object;
break;
}
}
for (ObjectGuid& guid: possibleGameObjects)
{
WorldObject* object = ObjectAccessor::GetWorldObject(*bot, guid);
if (!object || !object->IsInWorld())
continue;
if (distanceLimit && bot->GetDistance(object) > distanceLimit)
continue;
if (HasQuestToAcceptOrReward(object))
{
if (!nearestObject || bot->GetExactDist(nearestObject) > bot->GetExactDist(object))
nearestObject = object;
break;
}
}
if (nearestObject)
return nearestObject->GetGUID();
// No questgiver to accept or reward
if (questgiverOnly)
return ObjectGuid();
if (possibleTargets.empty())
return ObjectGuid();
int idx = urand(0, possibleTargets.size() - 1);
ObjectGuid guid = possibleTargets[idx];
WorldObject* object = ObjectAccessor::GetCreatureOrPetOrVehicle(*bot, guid);
if (!object)
object = ObjectAccessor::GetGameObject(*bot, guid);
if (object && object->IsInWorld())
{
return object->GetGUID();
}
return ObjectGuid();
}
bool NewRpgBaseAction::HasQuestToAcceptOrReward(WorldObject* object)
{
ObjectGuid guid = object->GetGUID();
bot->PrepareQuestMenu(guid);
const QuestMenu &menu = bot->PlayerTalkClass->GetQuestMenu();
if (menu.Empty())
return false;
for (uint8 idx = 0; idx < menu.GetMenuItemCount(); idx++)
{
const QuestMenuItem &item = menu.GetItem(idx);
const Quest* quest = sObjectMgr->GetQuestTemplate(item.QuestId);
if (!quest)
continue;
const QuestStatus &status = bot->GetQuestStatus(item.QuestId);
if (status == QUEST_STATUS_COMPLETE && bot->CanRewardQuest(quest, 0, false))
{
return true;
}
}
for (uint8 idx = 0; idx < menu.GetMenuItemCount(); idx++)
{
const QuestMenuItem &item = menu.GetItem(idx);
const Quest* quest = sObjectMgr->GetQuestTemplate(item.QuestId);
if (!quest)
continue;
const QuestStatus &status = bot->GetQuestStatus(item.QuestId);
if (status == QUEST_STATUS_NONE && bot->CanTakeQuest(quest, false) &&
bot->CanAddQuest(quest, false) && IsQuestWorthDoing(quest) && IsQuestCapableDoing(quest))
{
return true;
}
}
return false;
}
static std::vector<float> GenerateRandomWeights(int n) {
std::vector<float> weights(n);
float sum = 0.0;
for (int i = 0; i < n; ++i) {
weights[i] = rand_norm();
sum += weights[i];
}
for (int i = 0; i < n; ++i) {
weights[i] /= sum;
}
return weights;
}
bool NewRpgBaseAction::GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector<POIInfo> &poiInfo, bool toComplete)
{
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
if (!quest)
return false;
const QuestPOIVector* poiVector = sObjectMgr->GetQuestPOIVector(questId);
if (!poiVector)
{
return false;
}
const QuestStatusData& q_status = bot->getQuestStatusMap().at(questId);
if (toComplete && q_status.Status == QUEST_STATUS_COMPLETE)
{
for (const QuestPOI &qPoi : *poiVector)
{
if (qPoi.MapId != bot->GetMapId())
continue;
// not the poi pos to reward quest
if (qPoi.ObjectiveIndex != -1)
continue;
if (qPoi.points.size() == 0)
continue;
float dx = 0, dy = 0;
std::vector<float> weights = GenerateRandomWeights(qPoi.points.size());
for (size_t i = 0; i < qPoi.points.size(); i++)
{
const QuestPOIPoint &point = qPoi.points[i];
dx += point.x * weights[i];
dy += point.y * weights[i];
}
if (bot->GetDistance2d(dx, dy) >= 1500.0f)
continue;
float dz = std::max(bot->GetMap()->GetHeight(dx, dy, MAX_HEIGHT), bot->GetMap()->GetWaterLevel(dx, dy));
if (dz == INVALID_HEIGHT || dz == VMAP_INVALID_HEIGHT_VALUE)
continue;
if (bot->GetZoneId() != bot->GetMap()->GetZoneId(bot->GetPhaseMask(), dx, dy, dz))
continue;
poiInfo.push_back({{dx, dy}, qPoi.ObjectiveIndex});
}
if (poiInfo.empty())
return false;
return true;
}
if (q_status.Status != QUEST_STATUS_INCOMPLETE)
return false;
// Get incomplete quest objective index
std::vector<int32> incompleteObjectiveIdx;
for (int i = 0; i < QUEST_OBJECTIVES_COUNT; i++)
{
int32 npcOrGo = quest->RequiredNpcOrGo[i];
if (!npcOrGo)
continue;
if (q_status.CreatureOrGOCount[i] < quest->RequiredNpcOrGoCount[i])
incompleteObjectiveIdx.push_back(i);
}
for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++)
{
uint32 itemId = quest->RequiredItemId[i];
if (!itemId)
continue;
if (q_status.ItemCount[i] < quest->RequiredItemCount[i])
incompleteObjectiveIdx.push_back(QUEST_OBJECTIVES_COUNT + i);
}
// Get POIs to go
for (const QuestPOI &qPoi : *poiVector)
{
if (qPoi.MapId != bot->GetMapId())
continue;
bool inComplete = false;
for (uint32 objective : incompleteObjectiveIdx)
{
if (qPoi.ObjectiveIndex == objective)
{
inComplete = true;
break;
}
}
if (!inComplete)
continue;
if (qPoi.points.size() == 0)
continue;
float dx = 0, dy = 0;
std::vector<float> weights = GenerateRandomWeights(qPoi.points.size());
for (size_t i = 0; i < qPoi.points.size(); i++)
{
const QuestPOIPoint &point = qPoi.points[i];
dx += point.x * weights[i];
dy += point.y * weights[i];
}
if (bot->GetDistance2d(dx, dy) >= 1500.0f)
continue;
float dz = std::max(bot->GetMap()->GetHeight(dx, dy, MAX_HEIGHT), bot->GetMap()->GetWaterLevel(dx, dy));
if (dz == INVALID_HEIGHT || dz == VMAP_INVALID_HEIGHT_VALUE)
continue;
if (bot->GetZoneId() != bot->GetMap()->GetZoneId(bot->GetPhaseMask(), dx, dy, dz))
continue;
poiInfo.push_back({{dx, dy}, qPoi.ObjectiveIndex});
}
if (poiInfo.size() == 0) {
// LOG_DEBUG("playerbots", "[New rpg] {}: No available poi can be found for quest {}", bot->GetName(), questId);
return false;
}
return true;
}
WorldPosition NewRpgBaseAction::SelectRandomGrindPos(Player* bot)
{
const std::vector<WorldLocation>& locs = sRandomPlayerbotMgr->locsPerLevelCache[bot->GetLevel()];
float hiRange = 500.0f;
float loRange = 2500.0f;
if (bot->GetLevel() < 5)
{
hiRange /= 10;
loRange /= 10;
}
std::vector<WorldLocation> lo_prepared_locs, hi_prepared_locs;
for (auto& loc : locs)
{
if (bot->GetMapId() != loc.GetMapId())
continue;
if (bot->GetExactDist(loc) > 2500.0f)
continue;
if (bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) !=
bot->GetZoneId())
continue;
if (bot->GetExactDist(loc) < 500.0f)
{
hi_prepared_locs.push_back(loc);
}
if (bot->GetExactDist(loc) < 2500.0f)
{
lo_prepared_locs.push_back(loc);
}
}
WorldPosition dest{};
if (urand(1, 100) <= 50 && !hi_prepared_locs.empty())
{
uint32 idx = urand(0, hi_prepared_locs.size() - 1);
dest = hi_prepared_locs[idx];
}
else if (!lo_prepared_locs.empty())
{
uint32 idx = urand(0, lo_prepared_locs.size() - 1);
dest = lo_prepared_locs[idx];
}
LOG_DEBUG("playerbots", "[New Rpg] Bot {} select random grind pos Map:{} X:{} Y:{} Z:{} ({}+{} available in {})",
bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(),
hi_prepared_locs.size(), lo_prepared_locs.size() - hi_prepared_locs.size(), locs.size());
return dest;
}
WorldPosition NewRpgBaseAction::SelectRandomInnKeeperPos(Player* bot)
{
const std::vector<WorldLocation>& locs = IsAlliance(bot->getRace())
? sRandomPlayerbotMgr->allianceStarterPerLevelCache[bot->GetLevel()]
: sRandomPlayerbotMgr->hordeStarterPerLevelCache[bot->GetLevel()];
std::vector<WorldLocation> prepared_locs;
for (auto& loc : locs)
{
if (bot->GetMapId() != loc.GetMapId())
continue;
float range = bot->GetLevel() <= 5 ? 500.0f : 2500.0f;
if (bot->GetExactDist(loc) > range)
continue;
if (bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) !=
bot->GetZoneId())
continue;
prepared_locs.push_back(loc);
}
WorldPosition dest{};
if (!prepared_locs.empty())
{
uint32 idx = urand(0, prepared_locs.size() - 1);
dest = prepared_locs[idx];
}
LOG_DEBUG("playerbots", "[New Rpg] Bot {} select random inn keeper pos Map:{} X:{} Y:{} Z:{} ({} available in {})",
bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(),
prepared_locs.size(), locs.size());
return dest;
}

View File

@@ -0,0 +1,58 @@
#ifndef _PLAYERBOT_NEWRPGBASEACTION_H
#define _PLAYERBOT_NEWRPGBASEACTION_H
#include "Duration.h"
#include "LastMovementValue.h"
#include "MovementActions.h"
#include "NewRpgStrategy.h"
#include "Object.h"
#include "ObjectDefines.h"
#include "ObjectGuid.h"
#include "QuestDef.h"
#include "TravelMgr.h"
#include "PlayerbotAI.h"
struct POIInfo {
G3D::Vector2 pos;
int32 objectiveIdx;
};
/// A base (composition) class for all new rpg actions
/// All functions that may be shared by multiple actions should be declared here
/// And we should make all actions composable instead of inheritable
class NewRpgBaseAction : public MovementAction
{
public:
NewRpgBaseAction(PlayerbotAI* botAI, std::string name) : MovementAction(botAI, name) {}
protected:
// MOVEMENT RELATED
bool MoveFarTo(WorldPosition dest);
bool MoveWorldObjectTo(ObjectGuid guid, float distance = INTERACTION_DISTANCE);
bool MoveRandomNear(float moveStep = 50.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
bool ForceToWait(uint32 duration, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
// QUEST RELATED
bool SearchQuestGiverAndAcceptOrReward();
ObjectGuid ChooseNpcOrGameObjectToInteract(bool questgiverOnly = false, float distanceLimit = 0.0f);
bool HasQuestToAcceptOrReward(WorldObject* object);
bool InteractWithNpcOrGameObjectForQuest(ObjectGuid guid);
bool AcceptQuest(Quest const* quest, ObjectGuid guid);
bool TurnInQuest(Quest const* quest, ObjectGuid guid);
uint32 BestReward(Quest const* quest);
bool IsQuestWorthDoing(Quest const* quest);
bool IsQuestCapableDoing(Quest const* quest);
bool OrganizeQuestLog();
protected:
bool GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector<POIInfo> &poiInfo, bool toComplete = false);
static WorldPosition SelectRandomGrindPos(Player* bot);
static WorldPosition SelectRandomInnKeeperPos(Player* bot);
protected:
// WorldPosition dest;
const float pathFinderDis = 70.0f; // path finder
const uint32 stuckTime = 5 * 60 * 1000;
};
#endif

View File

@@ -0,0 +1,119 @@
#include "NewRpgInfo.h"
#include "Timer.h"
void NewRpgInfo::ChangeToGoGrind(WorldPosition pos)
{
Reset();
status = RPG_GO_GRIND;
go_grind = GoGrind();
go_grind.pos = pos;
}
void NewRpgInfo::ChangeToGoInnkeeper(WorldPosition pos)
{
Reset();
status = RPG_GO_INNKEEPER;
go_innkeeper = GoInnkeeper();
go_innkeeper.pos = pos;
}
void NewRpgInfo::ChangeToNearNpc()
{
Reset();
status = RPG_NEAR_NPC;
near_npc = NearNpc();
}
void NewRpgInfo::ChangeToNearRandom()
{
Reset();
status = RPG_NEAR_RANDOM;
near_random = NearRandom();
}
void NewRpgInfo::ChangeToDoQuest(uint32 questId, const Quest* quest)
{
Reset();
status = RPG_DO_QUEST;
do_quest = DoQuest();
do_quest.questId = questId;
do_quest.quest = quest;
}
void NewRpgInfo::ChangeToRest()
{
Reset();
status = RPG_REST;
rest = Rest();
}
void NewRpgInfo::ChangeToIdle()
{
Reset();
status = RPG_IDLE;
}
bool NewRpgInfo::CanChangeTo(NewRpgStatus status)
{
return true;
}
void NewRpgInfo::Reset()
{
*this = NewRpgInfo();
startT = getMSTime();
}
void NewRpgInfo::SetMoveFarTo(WorldPosition pos)
{
nearestMoveFarDis = FLT_MAX;
stuckTs = 0;
stuckAttempts = 0;
moveFarPos = pos;
}
std::string NewRpgInfo::ToString()
{
std::stringstream out;
out << "Status: ";
switch (status)
{
case RPG_GO_GRIND:
out << "GO_GRIND";
out << "\nGrindPos: " << go_grind.pos.GetMapId() << " " << go_grind.pos.GetPositionX() << " " << go_grind.pos.GetPositionY() << " " << go_grind.pos.GetPositionZ();
out << "\nlastGoGrind: " << startT;
break;
case RPG_GO_INNKEEPER:
out << "GO_INNKEEPER";
out << "\nInnKeeperPos: " << go_innkeeper.pos.GetMapId() << " " << go_innkeeper.pos.GetPositionX() << " " << go_innkeeper.pos.GetPositionY() << " " << go_innkeeper.pos.GetPositionZ();
out << "\nlastGoInnKeeper: " << startT;
break;
case RPG_NEAR_NPC:
out << "NEAR_NPC";
out << "\nnpcOrGoEntry: " << near_npc.npcOrGo.GetCounter();
out << "\nlastNearNpc: " << startT;
out << "\nlastReachNpcOrGo: " << near_npc.lastReach;
break;
case RPG_NEAR_RANDOM:
out << "NEAR_RANDOM";
out << "\nlastNearRandom: " << startT;
break;
case RPG_IDLE:
out << "IDLE";
break;
case RPG_REST:
out << "REST";
out << "\nlastRest: " << startT;
break;
case RPG_DO_QUEST:
out << "DO_QUEST";
out << "\nquestId: " << do_quest.questId;
out << "\nobjectiveIdx: " << do_quest.objectiveIdx;
out << "\npoiPos: " << do_quest.pos.GetMapId() << " " << do_quest.pos.GetPositionX() << " " << do_quest.pos.GetPositionY() << " " << do_quest.pos.GetPositionZ();
out << "\nlastReachPOI: " << do_quest.lastReachPOI;
break;
default:
out << "UNKNOWN";
}
return out.str();
}

View File

@@ -0,0 +1,136 @@
#ifndef _PLAYERBOT_NEWRPGINFO_H
#define _PLAYERBOT_NEWRPGINFO_H
#include "Define.h"
#include "ObjectGuid.h"
#include "ObjectMgr.h"
#include "QuestDef.h"
#include "Strategy.h"
#include "Timer.h"
#include "TravelMgr.h"
enum NewRpgStatus: int
{
RPG_STATUS_START = 0,
// Going to far away place
RPG_GO_GRIND = 0,
RPG_GO_INNKEEPER = 1,
// Exploring nearby
RPG_NEAR_RANDOM = 2,
RPG_NEAR_NPC = 3,
// Do Quest (based on quest status)
RPG_DO_QUEST = 4,
// Taking a break
RPG_REST = 5,
// Initial status
RPG_IDLE = 6,
RPG_STATUS_END = 7
};
using NewRpgStatusTransitionProb = std::vector<std::vector<int>>;
struct NewRpgInfo
{
NewRpgInfo() {}
// RPG_GO_GRIND
struct GoGrind {
GoGrind() = default;
WorldPosition pos{};
};
// RPG_GO_INNKEEPER
struct GoInnkeeper {
GoInnkeeper() = default;
WorldPosition pos{};
};
// RPG_NEAR_NPC
struct NearNpc {
NearNpc() = default;
ObjectGuid npcOrGo{};
uint32 lastReach{0};
};
// RPG_NEAR_RANDOM
struct NearRandom {
NearRandom() = default;
};
// NewRpgStatus::QUESTING
struct DoQuest {
const Quest* quest{nullptr};
uint32 questId{0};
int32 objectiveIdx{0};
WorldPosition pos{};
uint32 lastReachPOI{0};
};
// RPG_REST
struct Rest {
Rest() = default;
};
struct Idle {
};
NewRpgStatus status{RPG_IDLE};
uint32 startT{0}; // start timestamp of the current status
// MOVE_FAR
float nearestMoveFarDis{FLT_MAX};
uint32 stuckTs{0};
uint32 stuckAttempts{0};
WorldPosition moveFarPos;
// END MOVE_FAR
union {
GoGrind go_grind;
GoInnkeeper go_innkeeper;
NearNpc near_npc;
NearRandom near_random;
DoQuest do_quest;
Rest rest;
DoQuest quest;
};
bool HasStatusPersisted(uint32 maxDuration) { return GetMSTimeDiffToNow(startT) > maxDuration; }
void ChangeToGoGrind(WorldPosition pos);
void ChangeToGoInnkeeper(WorldPosition pos);
void ChangeToNearNpc();
void ChangeToNearRandom();
void ChangeToDoQuest(uint32 questId, const Quest* quest);
void ChangeToRest();
void ChangeToIdle();
bool CanChangeTo(NewRpgStatus status);
void Reset();
void SetMoveFarTo(WorldPosition pos);
std::string ToString();
};
struct NewRpgStatistic
{
uint32 questAccepted{0};
uint32 questCompleted{0};
uint32 questAbandoned{0};
uint32 questRewarded{0};
uint32 questDropped{0};
NewRpgStatistic operator+(const NewRpgStatistic& other) const
{
NewRpgStatistic result;
result.questAccepted = this->questAccepted + other.questAccepted;
result.questCompleted = this->questCompleted + other.questCompleted;
result.questAbandoned = this->questAbandoned + other.questAbandoned;
result.questRewarded = this->questRewarded + other.questRewarded;
result.questDropped = this->questDropped + other.questDropped;
return result;
}
NewRpgStatistic& operator+=(const NewRpgStatistic& other)
{
this->questAccepted += other.questAccepted;
this->questCompleted += other.questCompleted;
this->questAbandoned += other.questAbandoned;
this->questRewarded += other.questRewarded;
this->questDropped += other.questDropped;
return *this;
}
};
// not sure is it necessary but keep it for now
#define RPG_INFO(x, y) botAI->rpgInfo.x.y
#endif

View File

@@ -11,22 +11,28 @@ NewRpgStrategy::NewRpgStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
NextAction** NewRpgStrategy::getDefaultActions()
{
return NextAction::array(0, new NextAction("new rpg status update", 5.0f), nullptr);
// the releavance should be greater than grind
return NextAction::array(0,
new NextAction("new rpg status update", 11.0f),
nullptr);
}
void NewRpgStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("go grind status", NextAction::array(0, new NextAction("new rpg go grind", 1.0f), nullptr)));
new TriggerNode("go grind status", NextAction::array(0, new NextAction("new rpg go grind", 3.0f), nullptr)));
triggers.push_back(
new TriggerNode("go innkeeper status", NextAction::array(0, new NextAction("new rpg go innkeeper", 1.0f), nullptr)));
new TriggerNode("go innkeeper status", NextAction::array(0, new NextAction("new rpg go innkeeper", 3.0f), nullptr)));
triggers.push_back(
new TriggerNode("near random status", NextAction::array(0, new NextAction("new rpg move random", 1.0f), nullptr)));
new TriggerNode("near random status", NextAction::array(0, new NextAction("new rpg move random", 3.0f), nullptr)));
triggers.push_back(
new TriggerNode("near npc status", NextAction::array(0, new NextAction("new rpg move npc", 1.0f), nullptr)));
new TriggerNode("near npc status", NextAction::array(0, new NextAction("new rpg move npc", 3.0f), nullptr)));
triggers.push_back(
new TriggerNode("do quest status", NextAction::array(0, new NextAction("new rpg do quest", 3.0f), nullptr)));
}
void NewRpgStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)

View File

@@ -6,91 +6,12 @@
#ifndef _PLAYERBOT_NEWRPGSTRATEGY_H
#define _PLAYERBOT_NEWRPGSTRATEGY_H
#include <cstdint>
#include "Strategy.h"
#include "TravelMgr.h"
#include "NewRpgInfo.h"
class PlayerbotAI;
enum class NewRpgStatus
{
// Going to far away place
GO_GRIND,
GO_INNKEEPER,
// Exploring nearby
NEAR_RANDOM,
NEAR_NPC,
// Taking a break
REST,
// Initial status
IDLE
};
struct NewRpgInfo
{
NewRpgStatus status{NewRpgStatus::IDLE};
// NewRpgStatus::GO_GRIND
WorldPosition grindPos{};
uint32 lastGoGrind{0};
// NewRpgStatus::GO_INNKEEPER
WorldPosition innKeeperPos{};
uint32 lastGoInnKeeper{0};
// NewRpgStatus::NEAR_NPC
GuidPosition npcPos{};
uint32 lastNearNpc{0};
uint32 lastReachNpc{0};
// NewRpgStatus::NEAR_RANDOM
uint32 lastNearRandom{0};
// NewRpgStatus::REST
uint32 lastRest{0};
// MOVE_FAR
float nearestMoveFarDis{FLT_MAX};
uint32 stuckTs{0};
uint32 stuckAttempts{0};
std::string ToString()
{
std::stringstream out;
out << "Status: ";
switch (status)
{
case NewRpgStatus::GO_GRIND:
out << "GO_GRIND";
out << "\nGrindPos: " << grindPos.GetMapId() << " " << grindPos.GetPositionX() << " " << grindPos.GetPositionY() << " " << grindPos.GetPositionZ();
out << "\nlastGoGrind: " << lastGoGrind;
break;
case NewRpgStatus::GO_INNKEEPER:
out << "GO_INNKEEPER";
out << "\nInnKeeperPos: " << innKeeperPos.GetMapId() << " " << innKeeperPos.GetPositionX() << " " << innKeeperPos.GetPositionY() << " " << innKeeperPos.GetPositionZ();
out << "\nlastGoInnKeeper: " << lastGoInnKeeper;
break;
case NewRpgStatus::NEAR_NPC:
out << "NEAR_NPC";
out << "\nNpcPos: " << npcPos.GetMapId() << " " << npcPos.GetPositionX() << " " << npcPos.GetPositionY() << " " << npcPos.GetPositionZ();
out << "\nlastNearNpc: " << lastNearNpc;
out << "\nlastReachNpc: " << lastReachNpc;
break;
case NewRpgStatus::NEAR_RANDOM:
out << "NEAR_RANDOM";
out << "\nlastNearRandom: " << lastNearRandom;
break;
case NewRpgStatus::IDLE:
out << "IDLE";
break;
case NewRpgStatus::REST:
out << "REST";
out << "\nlastRest: " << lastRest;
break;
default:
out << "UNKNOWN";
}
return out.str();
}
void Reset()
{
*this = NewRpgInfo();
}
};
class NewRpgStrategy : public Strategy
{
public:

View File

@@ -7,7 +7,7 @@
class NewRpgStatusTrigger : public Trigger
{
public:
NewRpgStatusTrigger(PlayerbotAI* botAI, NewRpgStatus status = NewRpgStatus::IDLE)
NewRpgStatusTrigger(PlayerbotAI* botAI, NewRpgStatus status = RPG_IDLE)
: Trigger(botAI, "new rpg status"), status(status)
{
}

View File

@@ -17,6 +17,8 @@ public:
ChatTriggerContext()
{
creators["open items"] = &ChatTriggerContext::open_items;
creators["unlock items"] = &ChatTriggerContext::unlock_items;
creators["unlock traded item"] = &ChatTriggerContext::unlock_traded_item;
creators["quests"] = &ChatTriggerContext::quests;
creators["stats"] = &ChatTriggerContext::stats;
creators["leave"] = &ChatTriggerContext::leave;
@@ -25,6 +27,7 @@ public:
creators["log"] = &ChatTriggerContext::log;
creators["los"] = &ChatTriggerContext::los;
creators["rpg status"] = &ChatTriggerContext::rpg_status;
creators["rpg do quest"] = &ChatTriggerContext::rpg_do_quest;
creators["aura"] = &ChatTriggerContext::aura;
creators["drop"] = &ChatTriggerContext::drop;
creators["share"] = &ChatTriggerContext::share;
@@ -132,6 +135,8 @@ public:
private:
static Trigger* open_items(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "open items"); }
static Trigger* unlock_items(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "unlock items"); }
static Trigger* unlock_traded_item(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "unlock traded item"); }
static Trigger* ra(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "ra"); }
static Trigger* range(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "range"); }
static Trigger* flag(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "flag"); }
@@ -214,6 +219,7 @@ private:
static Trigger* log(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "log"); }
static Trigger* los(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "los"); }
static Trigger* rpg_status(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "rpg status"); }
static Trigger* rpg_do_quest(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "rpg do quest"); }
static Trigger* aura(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "aura"); }
static Trigger* loot_all(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "add all loot"); }
static Trigger* release(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "release"); }

View File

@@ -15,6 +15,7 @@
#include "ObjectGuid.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "PositionValue.h"
#include "SharedDefines.h"
#include "TemporarySummon.h"
#include "ThreatMgr.h"
@@ -507,11 +508,22 @@ bool IsBehindTargetTrigger::IsActive()
bool IsNotBehindTargetTrigger::IsActive()
{
if (botAI->HasStrategy("stay", botAI->GetState()))
{
return false;
}
Unit* target = AI_VALUE(Unit*, "current target");
return target && !AI_VALUE2(bool, "behind", "current target");
}
bool IsNotFacingTargetTrigger::IsActive() { return !AI_VALUE2(bool, "facing", "current target"); }
bool IsNotFacingTargetTrigger::IsActive()
{
if (botAI->HasStrategy("stay", botAI->GetState()))
{
return false;
}
return !AI_VALUE2(bool, "facing", "current target");
}
bool HasCcTargetTrigger::IsActive()
{
@@ -599,6 +611,18 @@ bool NewPlayerNearbyTrigger::IsActive() { return AI_VALUE(ObjectGuid, "new playe
bool CollisionTrigger::IsActive() { return AI_VALUE2(bool, "collision", "self target"); }
bool ReturnToStayPositionTrigger::IsActive()
{
PositionInfo stayPosition = AI_VALUE(PositionMap&, "position")["stay"];
if (stayPosition.isSet())
{
const float distance = bot->GetDistance(stayPosition.x, stayPosition.y, stayPosition.z);
return distance > sPlayerbotAIConfig->followDistance;
}
return false;
}
bool GiveItemTrigger::IsActive()
{
return AI_VALUE2(Unit*, "party member without item", item) && AI_VALUE2(uint32, "item count", item);

View File

@@ -827,6 +827,14 @@ public:
SitTrigger(PlayerbotAI* botAI) : StayTimeTrigger(botAI, sPlayerbotAIConfig->sitDelay, "sit") {}
};
class ReturnToStayPositionTrigger : public Trigger
{
public:
ReturnToStayPositionTrigger(PlayerbotAI* ai) : Trigger(ai, "return to stay position", 2) {}
virtual bool IsActive() override;
};
class ReturnTrigger : public StayTimeTrigger
{
public:

View File

@@ -12,10 +12,10 @@
bool LootAvailableTrigger::IsActive()
{
return AI_VALUE(bool, "has available loot") &&
// if loot target if empty, always pass distance check
(sServerFacade->IsDistanceLessOrEqualThan(AI_VALUE2(float, "distance", "loot target"),
INTERACTION_DISTANCE - 2.0f) ||
AI_VALUE(GuidVector, "all targets").empty()) &&
!AI_VALUE2(bool, "combat", "self target");
AI_VALUE(GuidVector, "all targets").empty());
}
bool FarFromCurrentLootTrigger::IsActive()

View File

@@ -30,6 +30,7 @@ public:
{
creators["return"] = &TriggerContext::_return;
creators["sit"] = &TriggerContext::sit;
creators["return to stay position"] = &TriggerContext::return_to_stay_position;
creators["collision"] = &TriggerContext::collision;
creators["timer"] = &TriggerContext::Timer;
@@ -220,6 +221,7 @@ public:
creators["go innkeeper status"] = &TriggerContext::go_innkeeper_status;
creators["near random status"] = &TriggerContext::near_random_status;
creators["near npc status"] = &TriggerContext::near_npc_status;
creators["do quest status"] = &TriggerContext::do_quest_status;
}
private:
@@ -227,6 +229,7 @@ private:
static Trigger* give_water(PlayerbotAI* botAI) { return new GiveWaterTrigger(botAI); }
static Trigger* no_rti(PlayerbotAI* botAI) { return new NoRtiTrigger(botAI); }
static Trigger* _return(PlayerbotAI* botAI) { return new ReturnTrigger(botAI); }
static Trigger* return_to_stay_position(PlayerbotAI* ai) { return new ReturnToStayPositionTrigger(ai); }
static Trigger* sit(PlayerbotAI* botAI) { return new SitTrigger(botAI); }
static Trigger* far_from_rpg_target(PlayerbotAI* botAI) { return new FarFromRpgTargetTrigger(botAI); }
static Trigger* near_rpg_target(PlayerbotAI* botAI) { return new NearRpgTargetTrigger(botAI); }
@@ -410,10 +413,11 @@ private:
static Trigger* rpg_craft(PlayerbotAI* botAI) { return new RpgCraftTrigger(botAI); }
static Trigger* rpg_trade_useful(PlayerbotAI* botAI) { return new RpgTradeUsefulTrigger(botAI); }
static Trigger* rpg_duel(PlayerbotAI* botAI) { return new RpgDuelTrigger(botAI); }
static Trigger* go_grind_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, NewRpgStatus::GO_GRIND); }
static Trigger* go_innkeeper_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, NewRpgStatus::GO_INNKEEPER); }
static Trigger* near_random_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, NewRpgStatus::NEAR_RANDOM); }
static Trigger* near_npc_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, NewRpgStatus::NEAR_NPC); }
static Trigger* go_grind_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_GO_GRIND); }
static Trigger* go_innkeeper_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_GO_INNKEEPER); }
static Trigger* near_random_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_NEAR_RANDOM); }
static Trigger* near_npc_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_NEAR_NPC); }
static Trigger* do_quest_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_DO_QUEST); }
};
#endif

View File

@@ -29,6 +29,7 @@ public:
creators["check mount state"] = &WorldPacketTriggerContext::check_mount_state;
creators["activate taxi"] = &WorldPacketTriggerContext::taxi;
creators["trade status"] = &WorldPacketTriggerContext::trade_status;
creators["trade status extended"] = &WorldPacketTriggerContext::trade_status_extended;
creators["loot response"] = &WorldPacketTriggerContext::loot_response;
creators["out of react range"] = &WorldPacketTriggerContext::out_of_react_range;
@@ -108,6 +109,7 @@ private:
static Trigger* out_of_react_range(PlayerbotAI* botAI) { return new OutOfReactRangeTrigger(botAI); }
static Trigger* loot_response(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "loot response"); }
static Trigger* trade_status(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "trade status"); }
static Trigger* trade_status_extended(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "trade status extended"); }
static Trigger* cannot_equip(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "cannot equip"); }
static Trigger* check_mount_state(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "check mount state"); }
static Trigger* area_trigger(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "area trigger"); }

View File

@@ -26,5 +26,5 @@ bool CanLootValue::Calculate()
{
LootObject loot = AI_VALUE(LootObject, "loot target");
return !loot.IsEmpty() && loot.GetWorldObject(bot) && loot.IsLootPossible(bot) &&
sServerFacade->IsDistanceLessOrEqualThan(AI_VALUE2(float, "distance", "loot target"), INTERACTION_DISTANCE);
sServerFacade->IsDistanceLessOrEqualThan(AI_VALUE2(float, "distance", "loot target"), INTERACTION_DISTANCE - 2);
}

View File

@@ -5,6 +5,7 @@
#include "GrindTargetValue.h"
#include "NewRpgInfo.h"
#include "Playerbots.h"
#include "ReputationMgr.h"
#include "SharedDefines.h"
@@ -52,7 +53,7 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
float distance = 0;
Unit* result = nullptr;
// std::unordered_map<uint32, bool> needForQuestMap;
std::unordered_map<uint32, bool> needForQuestMap;
for (ObjectGuid const guid : targets)
{
@@ -99,19 +100,6 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
if (!bot->InBattleground() && (int)unit->GetLevel() - (int)bot->GetLevel() > 4 && !unit->GetGUID().IsPlayer())
continue;
// if (needForQuestMap.find(unit->GetEntry()) == needForQuestMap.end())
// needForQuestMap[unit->GetEntry()] = needForQuest(unit);
// if (!needForQuestMap[unit->GetEntry()])
// {
// Creature* creature = dynamic_cast<Creature*>(unit);
// if ((urand(0, 100) < 60 || (context->GetValue<TravelTarget*>("travel target")->Get()->isWorking() &&
// context->GetValue<TravelTarget*>("travel target")->Get()->getDestination()->getName() != "GrindTravelDestination")))
// {
// continue;
// }
// }
if (Creature* creature = unit->ToCreature())
if (CreatureTemplate const* CreatureTemplate = creature->GetCreatureTemplate())
if (CreatureTemplate->rank > CREATURE_ELITE_NORMAL && !AI_VALUE(bool, "can fight elite"))
@@ -122,6 +110,26 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
continue;
}
bool inactiveGrindStatus = botAI->rpgInfo.status == RPG_GO_GRIND ||
botAI->rpgInfo.status == RPG_NEAR_NPC ||
botAI->rpgInfo.status == RPG_REST ||
botAI->rpgInfo.status == RPG_GO_INNKEEPER ||
botAI->rpgInfo.status == RPG_DO_QUEST;
bool notHostile = !bot->IsHostileTo(unit); /*|| (unit->ToCreature() && unit->ToCreature()->IsCivilian());*/
float aggroRange = 30.0f;
if (unit->ToCreature())
aggroRange = std::min(30.0f, unit->ToCreature()->GetAggroRange(bot) + 10.0f);
bool outOfAggro = unit->ToCreature() && bot->GetDistance(unit) > aggroRange;
if (inactiveGrindStatus && (outOfAggro || notHostile))
{
if (needForQuestMap.find(unit->GetEntry()) == needForQuestMap.end())
needForQuestMap[unit->GetEntry()] = needForQuest(unit);
if (!needForQuestMap[unit->GetEntry()])
continue;
}
if (group)
{
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
@@ -155,8 +163,6 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
bool GrindTargetValue::needForQuest(Unit* target)
{
bool justCheck = (bot->GetGUID() == target->GetGUID());
QuestStatusMap& questMap = bot->getQuestStatusMap();
for (auto& quest : questMap)
{
@@ -170,16 +176,9 @@ bool GrindTargetValue::needForQuest(Unit* target)
QuestStatus status = bot->GetQuestStatus(questId);
if ((status == QUEST_STATUS_COMPLETE && !bot->GetQuestRewardStatus(questId)))
if (status == QUEST_STATUS_INCOMPLETE)
{
if (!justCheck && !target->hasInvolvedQuest(questId))
continue;
return true;
}
else if (status == QUEST_STATUS_INCOMPLETE)
{
QuestStatusData* questStatus = sTravelMgr->getQuestStatus(bot, questId);
const QuestStatusData* questStatus = &bot->getQuestStatusMap()[questId];
if (questTemplate->GetQuestLevel() > bot->GetLevel() + 5)
continue;
@@ -193,33 +192,18 @@ bool GrindTargetValue::needForQuest(Unit* target)
int required = questTemplate->RequiredNpcOrGoCount[j];
int available = questStatus->CreatureOrGOCount[j];
if (required && available < required && (target->GetEntry() == entry || justCheck))
if (required && available < required && target->GetEntry() == entry)
return true;
}
if (justCheck)
{
int32 itemId = questTemplate->RequiredItemId[j];
if (itemId && itemId > 0)
{
uint32 required = questTemplate->RequiredItemCount[j];
uint32 available = questStatus->ItemCount[j];
if (required && available < required)
return true;
}
}
}
if (!justCheck)
if (CreatureTemplate const* data = sObjectMgr->GetCreatureTemplate(target->GetEntry()))
{
if (CreatureTemplate const* data = sObjectMgr->GetCreatureTemplate(target->GetEntry()))
if (uint32 lootId = data->lootid)
{
if (uint32 lootId = data->lootid)
if (LootTemplates_Creature.HaveQuestLootForPlayer(lootId, bot))
{
if (LootTemplates_Creature.HaveQuestLootForPlayer(lootId, bot))
return true;
return true;
}
}
}

View File

@@ -71,6 +71,9 @@ Item* ItemForSpellValue::Calculate()
if (!strcmpi(spellInfo->SpellName[0], "disenchant"))
return nullptr;
if (!strcmpi(spellInfo->SpellName[0], "pick lock"))
return nullptr;
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++)
{
itemForSpell = GetItemFitsToSpellRequirements(slot, spellInfo);

View File

@@ -17,6 +17,7 @@ class Unit;
enum class MovementPriority
{
MOVEMENT_IDLE,
MOVEMENT_WANDER,
MOVEMENT_NORMAL,
MOVEMENT_COMBAT,
MOVEMENT_FORCED

View File

@@ -12,24 +12,6 @@
#include "SharedDefines.h"
#include "SpellMgr.h"
class AnyGameObjectInObjectRangeCheck
{
public:
AnyGameObjectInObjectRangeCheck(WorldObject const* obj, float range) : i_obj(obj), i_range(range) {}
WorldObject const& GetFocusObject() const { return *i_obj; }
bool operator()(GameObject* u)
{
if (u && i_obj->IsWithinDistInMap(u, i_range) && u->isSpawned() && u->GetGOInfo())
return true;
return false;
}
private:
WorldObject const* i_obj;
float i_range;
};
GuidVector NearestGameObjects::Calculate()
{
std::list<GameObject*> targets;

View File

@@ -8,15 +8,34 @@
#include "PlayerbotAIConfig.h"
#include "Value.h"
#include "GameObject.h"
class PlayerbotAI;
class AnyGameObjectInObjectRangeCheck
{
public:
AnyGameObjectInObjectRangeCheck(WorldObject const* obj, float range) : i_obj(obj), i_range(range) {}
WorldObject const& GetFocusObject() const { return *i_obj; }
bool operator()(GameObject* u)
{
if (u && i_obj->IsWithinDistInMap(u, i_range) && u->isSpawned() && u->GetGOInfo())
return true;
return false;
}
private:
WorldObject const* i_obj;
float i_range;
};
class NearestGameObjects : public ObjectGuidListCalculatedValue
{
public:
NearestGameObjects(PlayerbotAI* botAI, float range = sPlayerbotAIConfig->sightDistance, bool ignoreLos = false,
std::string const name = "nearest game objects")
: ObjectGuidListCalculatedValue(botAI, name, 2 * 1000), range(range), ignoreLos(ignoreLos)
: ObjectGuidListCalculatedValue(botAI, name, 1 * 1000), range(range), ignoreLos(ignoreLos)
{
}

View File

@@ -63,3 +63,25 @@ bool PositionValue::Load(std::string const text)
}
WorldPosition CurrentPositionValue::Calculate() { return WorldPosition(bot); }
PositionInfo SinglePositionValue::Calculate()
{
PositionMap& posMap = AI_VALUE(PositionMap&, "position");
return posMap[getQualifier()];
}
void SinglePositionValue::Set(PositionInfo value)
{
PositionMap& posMap = AI_VALUE(PositionMap&, "position");
PositionInfo pos = posMap[getQualifier()];
pos = value;
posMap[getQualifier()] = pos;
}
void SinglePositionValue::Reset()
{
PositionMap& posMap = AI_VALUE(PositionMap&, "position");
PositionInfo pos = posMap[getQualifier()];
pos.Reset();
posMap[getQualifier()] = pos;
}

View File

@@ -6,6 +6,7 @@
#ifndef _PLAYERBOT_POSITIONVALUE_H
#define _PLAYERBOT_POSITIONVALUE_H
#include "NamedObjectContext.h"
#include "TravelMgr.h"
#include "Value.h"
@@ -15,6 +16,10 @@ class PositionInfo
{
public:
PositionInfo() : valueSet(false), x(0), y(0), z(0), mapId(0) {}
PositionInfo(float x, float y, float z, uint32 mapId, bool valueSet = true)
: valueSet(valueSet), x(x), y(y), z(z), mapId(mapId)
{
}
PositionInfo(PositionInfo const& other)
: valueSet(other.valueSet), x(other.x), y(other.y), z(other.z), mapId(other.mapId)
{
@@ -72,4 +77,13 @@ public:
WorldPosition Calculate() override;
};
class SinglePositionValue : public CalculatedValue<PositionInfo>, public Qualified
{
public:
SinglePositionValue(PlayerbotAI* ai, std::string name = "pos") : CalculatedValue(ai, name), Qualified() {};
virtual PositionInfo Calculate() override;
virtual void Set(PositionInfo value) override;
virtual void Reset() override;
};
#endif

View File

@@ -8,8 +8,11 @@
#include "CellImpl.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "ObjectGuid.h"
#include "Playerbots.h"
#include "ServerFacade.h"
#include "SharedDefines.h"
#include "NearestGameObjects.h"
std::vector<uint32> PossibleRpgTargetsValue::allowedNpcFlags;
@@ -78,3 +81,125 @@ bool PossibleRpgTargetsValue::AcceptUnit(Unit* unit)
return false;
}
std::vector<uint32> PossibleNewRpgTargetsValue::allowedNpcFlags;
PossibleNewRpgTargetsValue::PossibleNewRpgTargetsValue(PlayerbotAI* botAI, float range)
: NearestUnitsValue(botAI, "possible new rpg targets", range, true)
{
if (allowedNpcFlags.empty())
{
allowedNpcFlags.push_back(UNIT_NPC_FLAG_INNKEEPER);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_GOSSIP);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_QUESTGIVER);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_FLIGHTMASTER);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_BANKER);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_GUILD_BANKER);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_TRAINER_CLASS);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_TRAINER_PROFESSION);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_VENDOR_AMMO);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_VENDOR_FOOD);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_VENDOR_POISON);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_VENDOR_REAGENT);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_AUCTIONEER);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_STABLEMASTER);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_PETITIONER);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_TABARDDESIGNER);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_BATTLEMASTER);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_TRAINER);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_VENDOR);
allowedNpcFlags.push_back(UNIT_NPC_FLAG_REPAIR);
}
}
GuidVector PossibleNewRpgTargetsValue::Calculate()
{
std::list<Unit*> targets;
FindUnits(targets);
GuidVector results;
std::vector<std::pair<ObjectGuid, float>> guidDistancePairs;
for (Unit* unit : targets)
{
if (AcceptUnit(unit) && (ignoreLos || bot->IsWithinLOSInMap(unit)))
guidDistancePairs.push_back({unit->GetGUID(), bot->GetExactDist(unit)});
}
// Override to sort by distance
std::sort(guidDistancePairs.begin(), guidDistancePairs.end(), [](const auto& a, const auto& b) {
return a.second < b.second;
});
for (const auto& pair : guidDistancePairs) {
results.push_back(pair.first);
}
return results;
}
void PossibleNewRpgTargetsValue::FindUnits(std::list<Unit*>& targets)
{
Acore::AnyUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
}
bool PossibleNewRpgTargetsValue::AcceptUnit(Unit* unit)
{
if (unit->IsHostileTo(bot) || unit->GetTypeId() == TYPEID_PLAYER)
return false;
if (unit->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPIRITHEALER))
return false;
for (uint32 npcFlag : allowedNpcFlags)
{
if (unit->HasFlag(UNIT_NPC_FLAGS, npcFlag))
return true;
}
return false;
}
std::vector<GameobjectTypes> PossibleNewRpgGameObjectsValue::allowedGOFlags;
GuidVector PossibleNewRpgGameObjectsValue::Calculate()
{
std::list<GameObject*> targets;
AnyGameObjectInObjectRangeCheck u_check(bot, range);
Acore::GameObjectListSearcher<AnyGameObjectInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
std::vector<std::pair<ObjectGuid, float>> guidDistancePairs;
for (GameObject* go : targets)
{
bool flagCheck = false;
for (uint32 goFlag : allowedGOFlags)
{
if (go->GetGoType() == goFlag)
{
flagCheck = true;
break;
}
}
if (!flagCheck)
continue;
if (!ignoreLos && !bot->IsWithinLOSInMap(go))
continue;
guidDistancePairs.push_back({go->GetGUID(), bot->GetExactDist(go)});
}
GuidVector results;
// Sort by distance
std::sort(guidDistancePairs.begin(), guidDistancePairs.end(), [](const auto& a, const auto& b) {
return a.second < b.second;
});
for (const auto& pair : guidDistancePairs) {
results.push_back(pair.first);
}
return results;
}

View File

@@ -6,8 +6,10 @@
#ifndef _PLAYERBOT_POSSIBLERPGTARGETSVALUE_H
#define _PLAYERBOT_POSSIBLERPGTARGETSVALUE_H
#include "NearestGameObjects.h"
#include "NearestUnitsValue.h"
#include "PlayerbotAIConfig.h"
#include "SharedDefines.h"
class PlayerbotAI;
@@ -23,4 +25,36 @@ protected:
bool AcceptUnit(Unit* unit) override;
};
class PossibleNewRpgTargetsValue : public NearestUnitsValue
{
public:
PossibleNewRpgTargetsValue(PlayerbotAI* botAI, float range = 150.0f);
static std::vector<uint32> allowedNpcFlags;
GuidVector Calculate() override;
protected:
void FindUnits(std::list<Unit*>& targets) override;
bool AcceptUnit(Unit* unit) override;
};
class PossibleNewRpgGameObjectsValue : public ObjectGuidListCalculatedValue
{
public:
PossibleNewRpgGameObjectsValue(PlayerbotAI* botAI, float range = 150.0f, bool ignoreLos = true)
: ObjectGuidListCalculatedValue(botAI, "possible new rpg game objects"), range(range), ignoreLos(ignoreLos)
{
if (allowedGOFlags.empty())
{
allowedGOFlags.push_back(GAMEOBJECT_TYPE_QUESTGIVER);
}
}
static std::vector<GameobjectTypes> allowedGOFlags;
GuidVector Calculate() override;
private:
float range;
bool ignoreLos;
};
#endif

View File

@@ -118,6 +118,8 @@ public:
creators["prioritized targets"] = &ValueContext::prioritized_targets;
creators["all targets"] = &ValueContext::all_targets;
creators["possible rpg targets"] = &ValueContext::possible_rpg_targets;
creators["possible new rpg targets"] = &ValueContext::possible_new_rpg_targets;
creators["possible new rpg game objects"] = &ValueContext::possible_new_rpg_game_objects;
creators["nearest adds"] = &ValueContext::nearest_adds;
creators["nearest corpses"] = &ValueContext::nearest_corpses;
creators["log level"] = &ValueContext::log_level;
@@ -192,6 +194,7 @@ public:
creators["rti cc"] = &ValueContext::rti_cc;
creators["rti"] = &ValueContext::rti;
creators["position"] = &ValueContext::position;
creators["pos"] = &ValueContext::pos;
creators["current position"] = &ValueContext::current_position;
creators["threat"] = &ValueContext::threat;
@@ -340,6 +343,7 @@ private:
static UntypedValue* attackers(PlayerbotAI* botAI) { return new AttackersValue(botAI); }
static UntypedValue* position(PlayerbotAI* botAI) { return new PositionValue(botAI); }
static UntypedValue* pos(PlayerbotAI* ai) { return new SinglePositionValue(ai); }
static UntypedValue* current_position(PlayerbotAI* botAI) { return new CurrentPositionValue(botAI); }
static UntypedValue* rti(PlayerbotAI* botAI) { return new RtiValue(botAI); }
static UntypedValue* rti_cc(PlayerbotAI* botAI) { return new RtiCcValue(botAI); }
@@ -406,6 +410,8 @@ private:
static UntypedValue* nearest_enemy_players(PlayerbotAI* botAI) { return new NearestEnemyPlayersValue(botAI); }
static UntypedValue* nearest_corpses(PlayerbotAI* botAI) { return new NearestCorpsesValue(botAI); }
static UntypedValue* possible_rpg_targets(PlayerbotAI* botAI) { return new PossibleRpgTargetsValue(botAI); }
static UntypedValue* possible_new_rpg_targets(PlayerbotAI* botAI) { return new PossibleNewRpgTargetsValue(botAI); }
static UntypedValue* possible_new_rpg_game_objects(PlayerbotAI* botAI) { return new PossibleNewRpgGameObjectsValue(botAI); }
static UntypedValue* possible_targets(PlayerbotAI* botAI) { return new PossibleTargetsValue(botAI); }
static UntypedValue* possible_triggers(PlayerbotAI* botAI) { return new PossibleTriggersValue(botAI); }
static UntypedValue* possible_targets_no_los(PlayerbotAI* botAI)