mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
feat(Core/RPG): MoveFarTo flow, quest-pursuit at POI, MoveRandomNear retries
This commit is contained in:
parent
c7175d67c2
commit
e5dacf8bed
@ -6,10 +6,10 @@
|
|||||||
#include "CheckValuesAction.h"
|
#include "CheckValuesAction.h"
|
||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
|
#include "ObjectGuid.h"
|
||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
|
|
||||||
#include "PlayerbotAI.h"
|
#include "PlayerbotAI.h"
|
||||||
#include "TravelNode.h"
|
|
||||||
#include "AiObjectContext.h"
|
#include "AiObjectContext.h"
|
||||||
|
|
||||||
CheckValuesAction::CheckValuesAction(PlayerbotAI* botAI) : Action(botAI, "check values") {}
|
CheckValuesAction::CheckValuesAction(PlayerbotAI* botAI) : Action(botAI, "check values") {}
|
||||||
@ -21,11 +21,6 @@ bool CheckValuesAction::Execute(Event /*event*/)
|
|||||||
botAI->Ping(bot->GetPositionX(), bot->GetPositionY());
|
botAI->Ping(bot->GetPositionX(), bot->GetPositionY());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (botAI->HasStrategy("map", BOT_STATE_NON_COMBAT) || botAI->HasStrategy("map full", BOT_STATE_NON_COMBAT))
|
|
||||||
{
|
|
||||||
TravelNodeMap::instance().manageNodes(bot, botAI->HasStrategy("map full", BOT_STATE_NON_COMBAT));
|
|
||||||
}
|
|
||||||
|
|
||||||
GuidVector possible_targets = *context->GetValue<GuidVector>("possible targets");
|
GuidVector possible_targets = *context->GetValue<GuidVector>("possible targets");
|
||||||
GuidVector all_targets = *context->GetValue<GuidVector>("all targets");
|
GuidVector all_targets = *context->GetValue<GuidVector>("all targets");
|
||||||
GuidVector npcs = *context->GetValue<GuidVector>("nearest npcs");
|
GuidVector npcs = *context->GetValue<GuidVector>("nearest npcs");
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
#include "PathGenerator.h"
|
#include "PathGenerator.h"
|
||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
#include "PlayerbotAI.h"
|
#include "PlayerbotAI.h"
|
||||||
|
#include "Playerbots.h"
|
||||||
#include "QuestDef.h"
|
#include "QuestDef.h"
|
||||||
#include "Random.h"
|
#include "Random.h"
|
||||||
#include "SharedDefines.h"
|
#include "SharedDefines.h"
|
||||||
@ -120,17 +121,8 @@ bool NewRpgStatusUpdateAction::Execute(Event /*event*/)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RPG_TRAVEL_FLIGHT:
|
// RPG_TRAVEL_FLIGHT arrival is handled inside NewRpgTravelFlightAction
|
||||||
{
|
// so the flight action owns both take-off and landing transitions.
|
||||||
auto& data = std::get<NewRpgInfo::TravelFlight>(info.data);
|
|
||||||
if (data.inFlight && !bot->IsInFlight())
|
|
||||||
{
|
|
||||||
// flight arrival
|
|
||||||
info.ChangeToIdle();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case RPG_REST:
|
case RPG_REST:
|
||||||
{
|
{
|
||||||
// REST -> IDLE
|
// REST -> IDLE
|
||||||
@ -301,6 +293,9 @@ bool NewRpgDoQuestAction::DoIncompleteQuest(NewRpgInfo::DoQuest& data)
|
|||||||
data.lastReachPOI = 0;
|
data.lastReachPOI = 0;
|
||||||
data.pos = WorldPosition();
|
data.pos = WorldPosition();
|
||||||
data.objectiveIdx = 0;
|
data.objectiveIdx = 0;
|
||||||
|
data.pursuedLootGO.Clear();
|
||||||
|
data.pursuedUseGO.Clear();
|
||||||
|
data.pursuedUseTarget.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (data.pos == WorldPosition())
|
if (data.pos == WorldPosition())
|
||||||
@ -329,15 +324,31 @@ bool NewRpgDoQuestAction::DoIncompleteQuest(NewRpgInfo::DoQuest& data)
|
|||||||
data.lastReachPOI = 0;
|
data.lastReachPOI = 0;
|
||||||
data.pos = pos;
|
data.pos = pos;
|
||||||
data.objectiveIdx = objectiveIdx;
|
data.objectiveIdx = objectiveIdx;
|
||||||
|
data.pursuedLootGO.Clear();
|
||||||
|
data.pursuedUseGO.Clear();
|
||||||
|
data.pursuedUseTarget.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bot->GetDistance(data.pos) > 10.0f && !data.lastReachPOI)
|
if (bot->GetDistance(data.pos) > 10.0f && !data.lastReachPOI)
|
||||||
{
|
{
|
||||||
|
// yield to attack-anything if a quest mob is right next to us
|
||||||
|
if (HasNearbyQuestMob(15.0f))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Note: previously yielded ~10%/tick when any hostile was
|
||||||
|
// within 25y. That overrode the do-quest multiplier in
|
||||||
|
// practice (combined with bots getting aggroed on the way,
|
||||||
|
// which ALSO bypasses the multiplier via combat engine) and
|
||||||
|
// bots ended up grinding their way to POIs instead of
|
||||||
|
// travelling. Quest-mob exception above is kept so we don't
|
||||||
|
// walk past a quest target while gathering. Anything else
|
||||||
|
// hostile is the multiplier's job to throttle — and bots
|
||||||
|
// that DO get aggroed switch to combat engine where the
|
||||||
|
// class strategy handles it.
|
||||||
|
|
||||||
if (MoveFarTo(data.pos))
|
if (MoveFarTo(data.pos))
|
||||||
return true;
|
return true;
|
||||||
// Long-range sampler couldn't land a candidate — nudge the
|
// sampler found nothing — nudge so next tick tries a new pos
|
||||||
// bot a short distance so the next tick retries from a
|
|
||||||
// different position instead of sitting idle.
|
|
||||||
return MoveRandomNear(10.0f);
|
return MoveRandomNear(10.0f);
|
||||||
}
|
}
|
||||||
// Now we are near the quest objective
|
// Now we are near the quest objective
|
||||||
@ -382,14 +393,72 @@ bool NewRpgDoQuestAction::DoIncompleteQuest(NewRpgInfo::DoQuest& data)
|
|||||||
data.lastReachPOI = 0;
|
data.lastReachPOI = 0;
|
||||||
data.pos = WorldPosition();
|
data.pos = WorldPosition();
|
||||||
data.objectiveIdx = 0;
|
data.objectiveIdx = 0;
|
||||||
|
data.pursuedLootGO.Clear();
|
||||||
|
data.pursuedUseGO.Clear();
|
||||||
|
data.pursuedUseTarget.Clear();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// At the POI: keep the bot actively placed but avoid large
|
// at POI: drive toward specific objectives first
|
||||||
// random 20yd hops that look like pacing back and forth. A small
|
if (TryUseQuestItem(data.pursuedUseGO, data.pursuedUseTarget))
|
||||||
// ~8yd wander reads as the bot looking around while grind/loot
|
return true;
|
||||||
// strategies do their work.
|
if (TryLootQuestGO(data.pursuedLootGO))
|
||||||
return MoveRandomNear(8.0f);
|
return true;
|
||||||
|
if (TryUseQuestGO(data.pursuedUseGO))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// gather quests: roam for spawns. kill quests: yield to grind.
|
||||||
|
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
|
||||||
|
if (quest)
|
||||||
|
{
|
||||||
|
int32 obj = data.objectiveIdx;
|
||||||
|
bool isGatherObjective = false;
|
||||||
|
if (obj < QUEST_OBJECTIVES_COUNT)
|
||||||
|
{
|
||||||
|
int32 entry = quest->RequiredNpcOrGo[obj];
|
||||||
|
if (entry < 0) // GO objective
|
||||||
|
isGatherObjective = true;
|
||||||
|
if (entry == 0 && obj < QUEST_ITEM_OBJECTIVES_COUNT && quest->RequiredItemId[obj])
|
||||||
|
isGatherObjective = true;
|
||||||
|
}
|
||||||
|
else if (obj < QUEST_OBJECTIVES_COUNT + QUEST_ITEM_OBJECTIVES_COUNT)
|
||||||
|
{
|
||||||
|
isGatherObjective = true;
|
||||||
|
}
|
||||||
|
// source-item quest: need to find the target to use it on
|
||||||
|
if (quest->GetSrcItemId())
|
||||||
|
isGatherObjective = true;
|
||||||
|
|
||||||
|
if (isGatherObjective)
|
||||||
|
return MoveRandomNear(20.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// kill quest: walk toward the marker before handing off to grind.
|
||||||
|
// lastReachPOI trips at ~10y so without this the bot fights on the
|
||||||
|
// edge and never reaches the dense cluster. Skip if a quest mob is
|
||||||
|
// in sight (might be the target) or a hostile is mid-pull.
|
||||||
|
if (bot->GetDistance(data.pos) > 5.0f)
|
||||||
|
{
|
||||||
|
if (HasNearbyQuestMob(30.0f))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
GuidVector nearby = AI_VALUE(GuidVector, "possible targets");
|
||||||
|
bool hostileClose = false;
|
||||||
|
for (ObjectGuid guid : nearby)
|
||||||
|
{
|
||||||
|
Unit* u = botAI->GetUnit(guid);
|
||||||
|
if (u && u->IsAlive() && bot->GetDistance(u) < 15.0f)
|
||||||
|
{
|
||||||
|
hostileClose = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hostileClose)
|
||||||
|
return MoveFarTo(data.pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// yield to grind
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NewRpgDoQuestAction::DoCompletedQuest(NewRpgInfo::DoQuest& data)
|
bool NewRpgDoQuestAction::DoCompletedQuest(NewRpgInfo::DoQuest& data)
|
||||||
@ -423,6 +492,15 @@ bool NewRpgDoQuestAction::DoCompletedQuest(NewRpgInfo::DoQuest& data)
|
|||||||
data.lastReachPOI = 0;
|
data.lastReachPOI = 0;
|
||||||
data.pos = pos;
|
data.pos = pos;
|
||||||
data.objectiveIdx = -1;
|
data.objectiveIdx = -1;
|
||||||
|
|
||||||
|
// Drop the spline + lastPath that DoIncompleteQuest committed
|
||||||
|
// to the now-completed objective. Without this, MoveFarTo on
|
||||||
|
// the next tick hits the bot->isMoving() / lastPath-reuse
|
||||||
|
// early-exits at the top of MoveFarTo and rides the stale
|
||||||
|
// path instead of replanning toward the turn-in POI. (This
|
||||||
|
// is what `.playerbot bot self` masks by recreating the AI.)
|
||||||
|
bot->GetMotionMaster()->Clear();
|
||||||
|
AI_VALUE(LastMovement&, "last movement").clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.pos == WorldPosition())
|
if (data.pos == WorldPosition())
|
||||||
@ -453,7 +531,9 @@ bool NewRpgDoQuestAction::DoCompletedQuest(NewRpgInfo::DoQuest& data)
|
|||||||
botAI->rpgInfo.ChangeToIdle();
|
botAI->rpgInfo.ChangeToIdle();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
// waiting for SearchQuestGiverAndAcceptOrReward to pick up the NPC;
|
||||||
|
// wander instead of false so we don't fall through to grind
|
||||||
|
return MoveRandomNear(15.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NewRpgTravelFlightAction::Execute(Event /*event*/)
|
bool NewRpgTravelFlightAction::Execute(Event /*event*/)
|
||||||
@ -464,6 +544,22 @@ bool NewRpgTravelFlightAction::Execute(Event /*event*/)
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto& data = *dataPtr;
|
auto& data = *dataPtr;
|
||||||
|
|
||||||
|
// Arrival: we had boarded a flight (data.inFlight) and we're no longer in
|
||||||
|
// it → we just landed. Special-case Rut'theran: walk to the portal GO so
|
||||||
|
// it teleports the bot into Darnassus, flipping the zone to AREA_DARNASSUS
|
||||||
|
// so this branch falls through to ChangeToIdle on the next tick.
|
||||||
|
if (data.inFlight && !bot->IsInFlight())
|
||||||
|
{
|
||||||
|
if (bot->GetZoneId() == AREA_TELDRASSIL)
|
||||||
|
{
|
||||||
|
static WorldPosition const rutTheranPortalEntrance(1, 8799.41f, 969.787f, 26.2409f, 0.0f);
|
||||||
|
return MoveFarTo(rutTheranPortalEntrance);
|
||||||
|
}
|
||||||
|
info.ChangeToIdle();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (bot->IsInFlight())
|
if (bot->IsInFlight())
|
||||||
{
|
{
|
||||||
data.inFlight = true;
|
data.inFlight = true;
|
||||||
@ -479,19 +575,9 @@ bool NewRpgTravelFlightAction::Execute(Event /*event*/)
|
|||||||
info.ChangeToIdle();
|
info.ChangeToIdle();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (bot->GetDistance(flightMaster) > INTERACTION_DISTANCE)
|
|
||||||
return MoveFarTo(flightMaster);
|
|
||||||
|
|
||||||
std::vector<uint32> nodes = data.path;
|
if (!TakeFlight(data.path, flightMaster))
|
||||||
|
|
||||||
botAI->RemoveShapeshift();
|
|
||||||
if (bot->IsMounted())
|
|
||||||
bot->Dismount();
|
|
||||||
|
|
||||||
if (!bot->ActivateTaxiPathTo(nodes, flightMaster, 0))
|
|
||||||
{
|
{
|
||||||
LOG_DEBUG("playerbots", "[New RPG] {} active taxi path {} (from {} to {}) failed", bot->GetName(),
|
|
||||||
flightMaster->GetEntry(), nodes[0], nodes[nodes.size() - 1]);
|
|
||||||
info.ChangeToIdle();
|
info.ChangeToIdle();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -33,6 +33,7 @@ protected:
|
|||||||
bool MoveWorldObjectTo(ObjectGuid guid, float distance = INTERACTION_DISTANCE);
|
bool MoveWorldObjectTo(ObjectGuid guid, float distance = INTERACTION_DISTANCE);
|
||||||
bool MoveRandomNear(float moveStep = 50.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, WorldObject* center = nullptr);
|
bool MoveRandomNear(float moveStep = 50.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, WorldObject* center = nullptr);
|
||||||
bool ForceToWait(uint32 duration, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
bool ForceToWait(uint32 duration, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||||
|
bool TakeFlight(std::vector<uint32> const& taxiNodes, Creature* flightMaster);
|
||||||
|
|
||||||
/* QUEST RELATED CHECK */
|
/* QUEST RELATED CHECK */
|
||||||
ObjectGuid ChooseNpcOrGameObjectToInteract(bool questgiverOnly = false, float distanceLimit = 0.0f);
|
ObjectGuid ChooseNpcOrGameObjectToInteract(bool questgiverOnly = false, float distanceLimit = 0.0f);
|
||||||
@ -50,6 +51,23 @@ protected:
|
|||||||
bool TurnInQuest(Quest const* quest, ObjectGuid guid);
|
bool TurnInQuest(Quest const* quest, ObjectGuid guid);
|
||||||
bool OrganizeQuestLog();
|
bool OrganizeQuestLog();
|
||||||
|
|
||||||
|
/* QUEST PROGRESSION HELPERS (at POI) */
|
||||||
|
// Walk to a GO that drops a needed quest item. The loot strategy
|
||||||
|
// opens and loots it once in range.
|
||||||
|
bool TryLootQuestGO(ObjectGuid& pursuedGO, float searchRange = 60.0f);
|
||||||
|
|
||||||
|
// Walk to / use a GO that is itself the objective (rune, lever,
|
||||||
|
// altar, coffin — RequiredNpcOrGo with a negative entry).
|
||||||
|
bool TryUseQuestGO(ObjectGuid& pursuedGO, float searchRange = 60.0f);
|
||||||
|
|
||||||
|
// Fire a quest item's OnUse spell at the right target: a spell-focus
|
||||||
|
// GO (moonwell), a required creature, or the bot itself.
|
||||||
|
bool TryUseQuestItem(ObjectGuid& pursuedGO, ObjectGuid& pursuedTarget, float searchRange = 60.0f);
|
||||||
|
|
||||||
|
// True when a quest-relevant mob is within range — used during
|
||||||
|
// travel so we yield to attack-anything instead of running past.
|
||||||
|
bool HasNearbyQuestMob(float range = 20.0f);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector<POIInfo>& poiInfo, bool toComplete = false);
|
bool GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector<POIInfo>& poiInfo, bool toComplete = false);
|
||||||
static WorldPosition SelectRandomGrindPos(Player* bot);
|
static WorldPosition SelectRandomGrindPos(Player* bot);
|
||||||
@ -60,15 +78,17 @@ protected:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
/* FOR MOVE FAR */
|
/* FOR MOVE FAR */
|
||||||
const float pathFinderDis = 70.0f;
|
// Distance at which MoveFarTo considers the travel-node graph as
|
||||||
// Time without real progress toward dest before MoveFarTo
|
// a routing option. Below this, the move is short enough that
|
||||||
// falls back to teleport recovery. Kept short enough that a
|
// mmap handles it directly. Above this, mmap is *still probed
|
||||||
// bot truly oscillating around an unreachable destination
|
// first* via the 40-step chained pathfinder; the node graph
|
||||||
// (mmap returning non-progressing partial paths, or NOPATH +
|
// only takes over if mmap can't get within spellDistance of
|
||||||
// cone fallback wandering) doesn't spin for 5 minutes before
|
// the destination.
|
||||||
// the teleport fires, but long enough that a genuine long
|
const float nodeFirstDis = 75.0f;
|
||||||
// walk that is slowly making progress never triggers it.
|
|
||||||
const uint32 stuckTime = 90 * 1000;
|
private:
|
||||||
|
void StartTravelPlan(WorldPosition dest);
|
||||||
|
bool UpdateTravelPlan();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -6,31 +6,31 @@
|
|||||||
|
|
||||||
void NewRpgInfo::ChangeToGoGrind(WorldPosition pos)
|
void NewRpgInfo::ChangeToGoGrind(WorldPosition pos)
|
||||||
{
|
{
|
||||||
startT = getMSTime();
|
Reset();
|
||||||
data = GoGrind{pos};
|
data = GoGrind{pos};
|
||||||
}
|
}
|
||||||
|
|
||||||
void NewRpgInfo::ChangeToGoCamp(WorldPosition pos)
|
void NewRpgInfo::ChangeToGoCamp(WorldPosition pos)
|
||||||
{
|
{
|
||||||
startT = getMSTime();
|
Reset();
|
||||||
data = GoCamp{pos};
|
data = GoCamp{pos};
|
||||||
}
|
}
|
||||||
|
|
||||||
void NewRpgInfo::ChangeToWanderNpc()
|
void NewRpgInfo::ChangeToWanderNpc()
|
||||||
{
|
{
|
||||||
startT = getMSTime();
|
Reset();
|
||||||
data = WanderNpc{};
|
data = WanderNpc{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void NewRpgInfo::ChangeToWanderRandom()
|
void NewRpgInfo::ChangeToWanderRandom()
|
||||||
{
|
{
|
||||||
startT = getMSTime();
|
Reset();
|
||||||
data = WanderRandom{};
|
data = WanderRandom{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void NewRpgInfo::ChangeToDoQuest(uint32 questId, const Quest* quest)
|
void NewRpgInfo::ChangeToDoQuest(uint32 questId, const Quest* quest)
|
||||||
{
|
{
|
||||||
startT = getMSTime();
|
Reset();
|
||||||
DoQuest do_quest;
|
DoQuest do_quest;
|
||||||
do_quest.questId = questId;
|
do_quest.questId = questId;
|
||||||
do_quest.quest = quest;
|
do_quest.quest = quest;
|
||||||
@ -39,7 +39,7 @@ void NewRpgInfo::ChangeToDoQuest(uint32 questId, const Quest* quest)
|
|||||||
|
|
||||||
void NewRpgInfo::ChangeToTravelFlight(uint32 flightMasterEntry, WorldPosition flightMasterPos, std::vector<uint32> path)
|
void NewRpgInfo::ChangeToTravelFlight(uint32 flightMasterEntry, WorldPosition flightMasterPos, std::vector<uint32> path)
|
||||||
{
|
{
|
||||||
startT = getMSTime();
|
Reset();
|
||||||
TravelFlight flight;
|
TravelFlight flight;
|
||||||
flight.flightMasterEntry = flightMasterEntry;
|
flight.flightMasterEntry = flightMasterEntry;
|
||||||
flight.flightMasterPos = flightMasterPos;
|
flight.flightMasterPos = flightMasterPos;
|
||||||
@ -58,13 +58,13 @@ void NewRpgInfo::ChangeToOutdoorPvp(ObjectGuid::LowType capturePointSpawnId)
|
|||||||
|
|
||||||
void NewRpgInfo::ChangeToRest()
|
void NewRpgInfo::ChangeToRest()
|
||||||
{
|
{
|
||||||
startT = getMSTime();
|
Reset();
|
||||||
data = Rest{};
|
data = Rest{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void NewRpgInfo::ChangeToIdle()
|
void NewRpgInfo::ChangeToIdle()
|
||||||
{
|
{
|
||||||
startT = getMSTime();
|
Reset();
|
||||||
data = Idle{};
|
data = Idle{};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,14 +77,7 @@ void NewRpgInfo::Reset()
|
|||||||
{
|
{
|
||||||
data = Idle{};
|
data = Idle{};
|
||||||
startT = getMSTime();
|
startT = getMSTime();
|
||||||
}
|
ClearTravel();
|
||||||
|
|
||||||
void NewRpgInfo::SetMoveFarTo(WorldPosition pos)
|
|
||||||
{
|
|
||||||
nearestMoveFarDis = FLT_MAX;
|
|
||||||
stuckTs = 0;
|
|
||||||
stuckAttempts = 0;
|
|
||||||
moveFarPos = pos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NewRpgStatus NewRpgInfo::GetStatus()
|
NewRpgStatus NewRpgInfo::GetStatus()
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
#ifndef _PLAYERBOT_NEWRPGINFO_H
|
#ifndef _PLAYERBOT_NEWRPGINFO_H
|
||||||
#define _PLAYERBOT_NEWRPGINFO_H
|
#define _PLAYERBOT_NEWRPGINFO_H
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
#include "Define.h"
|
#include "Define.h"
|
||||||
#include "ObjectGuid.h"
|
#include "ObjectGuid.h"
|
||||||
#include "ObjectMgr.h"
|
#include "ObjectMgr.h"
|
||||||
@ -8,6 +10,7 @@
|
|||||||
#include "Strategy.h"
|
#include "Strategy.h"
|
||||||
#include "Timer.h"
|
#include "Timer.h"
|
||||||
#include "TravelMgr.h"
|
#include "TravelMgr.h"
|
||||||
|
#include "TravelNode.h"
|
||||||
|
|
||||||
using NewRpgStatusTransitionProb = std::vector<std::vector<int>>;
|
using NewRpgStatusTransitionProb = std::vector<std::vector<int>>;
|
||||||
|
|
||||||
@ -45,6 +48,11 @@ struct NewRpgInfo
|
|||||||
int32 objectiveIdx{0};
|
int32 objectiveIdx{0};
|
||||||
WorldPosition pos{};
|
WorldPosition pos{};
|
||||||
uint32 lastReachPOI{0};
|
uint32 lastReachPOI{0};
|
||||||
|
// committed target per objective type. stops zig-zagging in
|
||||||
|
// dense spawn clusters when "nearest" would flip each tick.
|
||||||
|
ObjectGuid pursuedLootGO{}; // GOs we loot (lilies, eggs)
|
||||||
|
ObjectGuid pursuedUseGO{}; // GOs we click or focus on
|
||||||
|
ObjectGuid pursuedUseTarget{}; // creature we apply an item to
|
||||||
};
|
};
|
||||||
// RPG_TRAVEL_FLIGHT
|
// RPG_TRAVEL_FLIGHT
|
||||||
struct TravelFlight
|
struct TravelFlight
|
||||||
@ -70,12 +78,10 @@ struct NewRpgInfo
|
|||||||
|
|
||||||
uint32 startT{0}; // start timestamp of the current status
|
uint32 startT{0}; // start timestamp of the current status
|
||||||
|
|
||||||
// MOVE_FAR
|
// Travel Node System
|
||||||
float nearestMoveFarDis{FLT_MAX};
|
TravelPlan travelPlan;
|
||||||
uint32 stuckTs{0};
|
bool HasActiveTravelPlan() const { return travelPlan.IsActive(); }
|
||||||
uint32 stuckAttempts{0};
|
void ClearTravel() { travelPlan.Reset(); }
|
||||||
WorldPosition moveFarPos;
|
|
||||||
// END MOVE_FAR
|
|
||||||
|
|
||||||
using RpgData = std::variant<
|
using RpgData = std::variant<
|
||||||
Idle,
|
Idle,
|
||||||
@ -103,7 +109,6 @@ struct NewRpgInfo
|
|||||||
void ChangeToIdle();
|
void ChangeToIdle();
|
||||||
bool CanChangeTo(NewRpgStatus status);
|
bool CanChangeTo(NewRpgStatus status);
|
||||||
void Reset();
|
void Reset();
|
||||||
void SetMoveFarTo(WorldPosition pos);
|
|
||||||
std::string ToString();
|
std::string ToString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -5,11 +5,66 @@
|
|||||||
|
|
||||||
#include "NewRpgStrategy.h"
|
#include "NewRpgStrategy.h"
|
||||||
|
|
||||||
|
#include "Action.h"
|
||||||
|
#include "NewRpgInfo.h"
|
||||||
|
#include "Player.h"
|
||||||
|
#include "PlayerbotAI.h"
|
||||||
|
|
||||||
|
static bool IsGatherObjectiveForDoQuest(NewRpgInfo::DoQuest const* data)
|
||||||
|
{
|
||||||
|
if (!data || !data->quest)
|
||||||
|
return false;
|
||||||
|
Quest const* q = data->quest;
|
||||||
|
int32 obj = data->objectiveIdx;
|
||||||
|
if (obj < QUEST_OBJECTIVES_COUNT)
|
||||||
|
{
|
||||||
|
int32 entry = q->RequiredNpcOrGo[obj];
|
||||||
|
if (entry < 0) // GO objective
|
||||||
|
return true;
|
||||||
|
if (entry == 0 && obj < QUEST_ITEM_OBJECTIVES_COUNT && q->RequiredItemId[obj])
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (obj < QUEST_OBJECTIVES_COUNT + QUEST_ITEM_OBJECTIVES_COUNT)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// source-item quest: need to find the right target to use it on
|
||||||
|
if (q->GetSrcItemId())
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float NewRpgDoQuestMultiplier::GetValue(Action* action)
|
||||||
|
{
|
||||||
|
if (!action || action->getName() != "attack anything")
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
NewRpgInfo& info = botAI->rpgInfo;
|
||||||
|
if (info.GetStatus() != RPG_DO_QUEST)
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
auto* data = std::get_if<NewRpgInfo::DoQuest>(&info.data);
|
||||||
|
if (!data)
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
// heading back to turn in, don't get sidetracked
|
||||||
|
if (data->questId && bot->GetQuestStatus(data->questId) == QUEST_STATUS_COMPLETE)
|
||||||
|
return 0.15f;
|
||||||
|
|
||||||
|
// at POI: gather stays low so mobs don't pull us off the cluster;
|
||||||
|
// kill runs full so attack-anything drives behavior
|
||||||
|
if (data->lastReachPOI)
|
||||||
|
return IsGatherObjectiveForDoQuest(data) ? 0.30f : 1.0f;
|
||||||
|
|
||||||
|
// traveling
|
||||||
|
return 0.20f;
|
||||||
|
}
|
||||||
|
|
||||||
NewRpgStrategy::NewRpgStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
|
NewRpgStrategy::NewRpgStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
|
||||||
|
|
||||||
std::vector<NextAction> NewRpgStrategy::getDefaultActions()
|
std::vector<NextAction> NewRpgStrategy::getDefaultActions()
|
||||||
{
|
{
|
||||||
// the releavance should be greater than grind
|
// must outrank grind
|
||||||
return {
|
return {
|
||||||
NextAction("new rpg status update", 11.0f)
|
NextAction("new rpg status update", 11.0f)
|
||||||
};
|
};
|
||||||
@ -53,7 +108,8 @@ void NewRpgStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
new TriggerNode(
|
new TriggerNode(
|
||||||
"do quest status",
|
"do quest status",
|
||||||
{
|
{
|
||||||
NextAction("new rpg do quest", 3.0f)
|
// 4.5: above attack-anything (4.0), below loot (5.0+)
|
||||||
|
NextAction("new rpg do quest", 4.5f)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -75,6 +131,7 @@ void NewRpgStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NewRpgStrategy::InitMultipliers(std::vector<Multiplier*>&)
|
void NewRpgStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
|
||||||
{
|
{
|
||||||
|
multipliers.push_back(new NewRpgDoQuestMultiplier(botAI));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,13 @@
|
|||||||
|
|
||||||
class PlayerbotAI;
|
class PlayerbotAI;
|
||||||
|
|
||||||
|
class NewRpgDoQuestMultiplier : public Multiplier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NewRpgDoQuestMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "new rpg do quest") {}
|
||||||
|
float GetValue(Action* action) override;
|
||||||
|
};
|
||||||
|
|
||||||
class NewRpgStrategy : public Strategy
|
class NewRpgStrategy : public Strategy
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@ -72,7 +72,6 @@ bool PlayerbotAIConfig::Initialize()
|
|||||||
globalCoolDown = sConfigMgr->GetOption<int32>("AiPlayerbot.GlobalCooldown", 500);
|
globalCoolDown = sConfigMgr->GetOption<int32>("AiPlayerbot.GlobalCooldown", 500);
|
||||||
maxWaitForMove = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxWaitForMove", 5000);
|
maxWaitForMove = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxWaitForMove", 5000);
|
||||||
disableMoveSplinePath = sConfigMgr->GetOption<int32>("AiPlayerbot.DisableMoveSplinePath", 0);
|
disableMoveSplinePath = sConfigMgr->GetOption<int32>("AiPlayerbot.DisableMoveSplinePath", 0);
|
||||||
maxMovementSearchTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxMovementSearchTime", 3);
|
|
||||||
expireActionTime = sConfigMgr->GetOption<int32>("AiPlayerbot.ExpireActionTime", 5000);
|
expireActionTime = sConfigMgr->GetOption<int32>("AiPlayerbot.ExpireActionTime", 5000);
|
||||||
dispelAuraDuration = sConfigMgr->GetOption<int32>("AiPlayerbot.DispelAuraDuration", 700);
|
dispelAuraDuration = sConfigMgr->GetOption<int32>("AiPlayerbot.DispelAuraDuration", 700);
|
||||||
reactDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.ReactDelay", 100);
|
reactDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.ReactDelay", 100);
|
||||||
@ -654,6 +653,7 @@ bool PlayerbotAIConfig::Initialize()
|
|||||||
autoTeleportForLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoTeleportForLevel", false);
|
autoTeleportForLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoTeleportForLevel", false);
|
||||||
autoDoQuests = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoDoQuests", true);
|
autoDoQuests = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoDoQuests", true);
|
||||||
enableNewRpgStrategy = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableNewRpgStrategy", true);
|
enableNewRpgStrategy = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableNewRpgStrategy", true);
|
||||||
|
enableTravelNodes = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableTravelNodes", false);
|
||||||
|
|
||||||
RpgStatusProbWeight[RPG_WANDER_RANDOM] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.WanderRandom", 15);
|
RpgStatusProbWeight[RPG_WANDER_RANDOM] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.WanderRandom", 15);
|
||||||
RpgStatusProbWeight[RPG_WANDER_NPC] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.WanderNpc", 20);
|
RpgStatusProbWeight[RPG_WANDER_NPC] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.WanderNpc", 20);
|
||||||
|
|||||||
@ -84,7 +84,7 @@ public:
|
|||||||
bool EnableICCBuffs;
|
bool EnableICCBuffs;
|
||||||
bool allowAccountBots, allowGuildBots, allowTrustedAccountBots;
|
bool allowAccountBots, allowGuildBots, allowTrustedAccountBots;
|
||||||
bool randomBotGuildNearby, randomBotInvitePlayer, inviteChat;
|
bool randomBotGuildNearby, randomBotInvitePlayer, inviteChat;
|
||||||
uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, maxMovementSearchTime, expireActionTime,
|
uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, expireActionTime,
|
||||||
dispelAuraDuration, passiveDelay, repeatDelay, errorDelay, rpgDelay, sitDelay, returnDelay, lootDelay;
|
dispelAuraDuration, passiveDelay, repeatDelay, errorDelay, rpgDelay, sitDelay, returnDelay, lootDelay;
|
||||||
bool dynamicReactDelay;
|
bool dynamicReactDelay;
|
||||||
float sightDistance, spellDistance, reactDistance, grindDistance, lootDistance, shootDistance, fleeDistance,
|
float sightDistance, spellDistance, reactDistance, grindDistance, lootDistance, shootDistance, fleeDistance,
|
||||||
@ -372,6 +372,7 @@ public:
|
|||||||
bool autoLearnTrainerSpells;
|
bool autoLearnTrainerSpells;
|
||||||
bool autoDoQuests;
|
bool autoDoQuests;
|
||||||
bool enableNewRpgStrategy;
|
bool enableNewRpgStrategy;
|
||||||
|
bool enableTravelNodes;
|
||||||
std::unordered_map<NewRpgStatus, uint32> RpgStatusProbWeight;
|
std::unordered_map<NewRpgStatus, uint32> RpgStatusProbWeight;
|
||||||
bool syncLevelWithPlayers;
|
bool syncLevelWithPlayers;
|
||||||
bool autoLearnQuestSpells;
|
bool autoLearnQuestSpells;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user