mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Auto do quest feature (new rpg strategy) (#1034)
* New rpg startup speed up and refactor * New rpg do quest * Fix invalid height in quest poi * Add quest accept and reward limitation * New rpg quest improvement * Organize quest log, reward quests and fix grind target * Quest dropped statistic and remove redundant code * Decrease grind relevance lower than loot * Fix new rpg drop quest * Go to reward quest instead of innkeeper when quest completed * Fix incorrect logic in do quest reward * Fix reset quests in factory * Fix crash on grind target value Co-authored-by: SaW <swerkhoven@outlook.com> * Fix a minor error in DoCompletedQuest * Let bots get rid of impossible quests faster * Increase loot fluency (especially for caster) * Remove seasonal quests from auto accept * Enhance quest accept condition check * Add questgiver check (limit acceptation of quest 7946) * Questgiver check and localization * Near npc fix * Fix quest item report * Add lowPriorityQuest set for quests can not be done * Improve gameobjects loot * Do complete quest * FIx move far to teleport check * Accept or reward quest from game objects * Fix possible crash in rpg game objects * Fix ChooseNpcOrGameObjectToInteract crash --------- Co-authored-by: SaW <swerkhoven@outlook.com>
This commit is contained in:
@@ -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)) {}
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -204,7 +208,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");
|
||||
}
|
||||
|
||||
@@ -2287,10 +2291,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()
|
||||
|
||||
@@ -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"
|
||||
@@ -435,7 +437,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,
|
||||
@@ -579,6 +582,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,
|
||||
|
||||
@@ -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"
|
||||
@@ -371,7 +372,7 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/)
|
||||
sRandomPlayerbotMgr->CheckLfgQueue();
|
||||
}
|
||||
|
||||
if (time(nullptr) > (printStatsTimer + 300))
|
||||
if (sPlayerbotAIConfig->randomBotAutologin && time(nullptr) > (printStatsTimer + 300))
|
||||
{
|
||||
if (!printStatsTimer)
|
||||
{
|
||||
@@ -1466,6 +1467,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 +1619,7 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
|
||||
|
||||
if (sPlayerbotAIConfig->enableNewRpgStrategy)
|
||||
{
|
||||
PrepareZone2LevelBracket();
|
||||
LOG_INFO("playerbots", "Preparing innkeepers locations for level...");
|
||||
results = WorldDatabase.Query(
|
||||
"SELECT "
|
||||
@@ -1550,8 +1628,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 +1650,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 +1657,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 +1672,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 +2681,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 +2757,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 +2829,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: {}",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -247,6 +247,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:
|
||||
@@ -428,6 +429,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
|
||||
|
||||
@@ -90,6 +90,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;
|
||||
@@ -261,6 +262,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); }
|
||||
|
||||
@@ -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() &&
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -79,7 +79,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;
|
||||
@@ -139,6 +140,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); }
|
||||
|
||||
@@ -109,6 +109,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");
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -39,6 +39,8 @@ void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
|
||||
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("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 +81,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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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,259 @@ 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.npc)
|
||||
{
|
||||
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 npc = ChooseNpcOrGameObjectToInteract();
|
||||
if (npc.IsEmpty())
|
||||
{
|
||||
info.npcPos = GuidPosition(unit);
|
||||
info.lastReachNpc = 0;
|
||||
info.ChangeToIdle();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
info.near_npc.npc = npc;
|
||||
info.near_npc.lastReach = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bot->GetDistance(info.npcPos) <= INTERACTION_DISTANCE)
|
||||
Unit* unit = botAI->GetUnit(info.near_npc.npc);
|
||||
if (unit && bot->GetDistance(unit) <= INTERACTION_DISTANCE)
|
||||
{
|
||||
if (!info.lastReachNpc)
|
||||
if (!info.near_npc.lastReach)
|
||||
{
|
||||
info.lastReachNpc = getMSTime();
|
||||
info.near_npc.lastReach = getMSTime();
|
||||
InteractWithNpcOrGameObjectForQuest(info.near_npc.npc);
|
||||
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.npc = 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 MoveNpcTo(info.near_npc.npc);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
801
src/strategy/rpg/NewRpgBaseAction.cpp
Normal file
801
src/strategy/rpg/NewRpgBaseAction.cpp
Normal 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::MoveNpcTo(ObjectGuid guid, float distance)
|
||||
{
|
||||
if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Unit* unit = botAI->GetUnit(guid);
|
||||
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 (!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)
|
||||
|
||||
float rnd = rand_norm();
|
||||
x += cos(angle) * distance * rnd;
|
||||
y += sin(angle) * distance * rnd;
|
||||
if (!unit->GetMap()->CheckCollisionAndGetValidCoords(unit, unit->GetPositionX(), unit->GetPositionY(),
|
||||
unit->GetPositionZ(), x, y, z))
|
||||
{
|
||||
x = unit->GetPositionX();
|
||||
y = unit->GetPositionY();
|
||||
z = unit->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 npc = ChooseNpcOrGameObjectToInteract(true, 80.0f))
|
||||
{
|
||||
const WorldObject* object = ObjectAccessor::GetWorldObject(*bot, npc);
|
||||
if (bot->GetDistance(object) <= INTERACTION_DISTANCE)
|
||||
{
|
||||
InteractWithNpcOrGameObjectForQuest(npc);
|
||||
ForceToWait(5000);
|
||||
return true;
|
||||
}
|
||||
return MoveNpcTo(npc);
|
||||
}
|
||||
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;
|
||||
}
|
||||
58
src/strategy/rpg/NewRpgBaseAction.h
Normal file
58
src/strategy/rpg/NewRpgBaseAction.h
Normal 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 MoveNpcTo(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
|
||||
119
src/strategy/rpg/NewRpgInfo.cpp
Normal file
119
src/strategy/rpg/NewRpgInfo.cpp
Normal 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 << "\nnpcEntry: " << near_npc.npc.GetCounter();
|
||||
out << "\nlastNearNpc: " << startT;
|
||||
out << "\nlastReachNpc: " << 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();
|
||||
}
|
||||
136
src/strategy/rpg/NewRpgInfo.h
Normal file
136
src/strategy/rpg/NewRpgInfo.h
Normal 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 npc{};
|
||||
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
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -25,6 +25,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;
|
||||
@@ -214,6 +215,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"); }
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -220,6 +220,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:
|
||||
@@ -410,10 +411,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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ class Unit;
|
||||
enum class MovementPriority
|
||||
{
|
||||
MOVEMENT_IDLE,
|
||||
MOVEMENT_WANDER,
|
||||
MOVEMENT_NORMAL,
|
||||
MOVEMENT_COMBAT,
|
||||
MOVEMENT_FORCED
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -406,6 +408,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)
|
||||
|
||||
Reference in New Issue
Block a user