mod-playerbots/src/Ai/World/Rpg/NewRpgInfo.h
bash 553c7739e8 feat(Core/RPG): MoveFarTo loop detection with strategy flip + grinding throttle
Per-bot ring buffer of last 3 path attempts on RpgInfo. When 3 mmap or 3 nodetravel attempts to the same dest fail, force the alternative routing strategy on the next tick. When both strategies have failed 3 times each (bothExhausted), fall through to MoveFar:spline rather than flip-flopping forever. Also drops the 10%-per-tick opportunistic combat engage during do-quest travel — the multiplier (0.20x) is the right knob; the random yield was overriding it and producing the 'still grinding too much while traveling' symptom.
2026-06-05 10:05:55 +02:00

161 lines
4.7 KiB
C++

#ifndef _PLAYERBOT_NEWRPGINFO_H
#define _PLAYERBOT_NEWRPGINFO_H
#include <deque>
#include "Define.h"
#include "ObjectGuid.h"
#include "ObjectMgr.h"
#include "QuestDef.h"
#include "Strategy.h"
#include "Timer.h"
#include "TravelMgr.h"
#include "TravelNode.h"
using NewRpgStatusTransitionProb = std::vector<std::vector<int>>;
struct NewRpgInfo
{
NewRpgInfo() : data(Idle{}) {}
~NewRpgInfo() = default;
// RPG_GO_GRIND
struct GoGrind
{
WorldPosition pos{};
};
// RPG_GO_CAMP
struct GoCamp
{
WorldPosition pos{};
};
// RPG_WANDER_NPC
struct WanderNpc
{
ObjectGuid npcOrGo{};
uint32 lastReach{0};
};
// RPG_WANDER_RANDOM
struct WanderRandom
{
WanderRandom() = default;
};
// RPG_DO_QUEST
struct DoQuest
{
const Quest* quest{nullptr};
uint32 questId{0};
int32 objectiveIdx{0};
WorldPosition pos{};
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
struct TravelFlight
{
uint32 flightMasterEntry{0};
WorldPosition flightMasterPos{};
std::vector<uint32> path;
bool inFlight{false};
};
// RPG_REST
struct Rest
{
Rest() = default;
};
// RPG_OUTDOOR_PVP
struct OutdoorPvP
{
ObjectGuid::LowType capturePointSpawnId{0};
};
struct Idle
{
};
uint32 startT{0}; // start timestamp of the current status
// Travel Node System
TravelPlan travelPlan;
bool HasActiveTravelPlan() const { return travelPlan.IsActive(); }
void ClearTravel() { travelPlan.Reset(); }
// MoveFar attempt history. Records the last 3 path commits (node
// plan or mmap) so MoveFarTo can detect when the same dest +
// strategy has failed repeatedly and force the alternative
// routing this tick. Breaks deterministic-loop scenarios where
// the chained probe (or node graph) keeps returning the same
// dead-end path. Cmangos doesn't do this — they wait 5+ minutes
// for UnstuckAction. We're more aggressive here for UX.
struct MoveFarAttempt
{
WorldPosition dest; // requested destination
bool wasNodeTravel{false}; // true=node plan, false=mmap/spline
uint32 timestamp{0};
};
std::deque<MoveFarAttempt> recentMoveFarAttempts;
void RecordMoveFarAttempt(WorldPosition const& dest, bool wasNodeTravel);
int CountRecentAttempts(WorldPosition const& dest, bool wasNodeTravel) const;
using RpgData = std::variant<
Idle,
GoGrind,
GoCamp,
WanderNpc,
WanderRandom,
DoQuest,
Rest,
TravelFlight,
OutdoorPvP
>;
RpgData data;
NewRpgStatus GetStatus();
bool HasStatusPersisted(uint32 maxDuration) { return GetMSTimeDiffToNow(startT) > maxDuration; }
void ChangeToGoGrind(WorldPosition pos);
void ChangeToGoCamp(WorldPosition pos);
void ChangeToWanderNpc();
void ChangeToWanderRandom();
void ChangeToDoQuest(uint32 questId, const Quest* quest);
void ChangeToTravelFlight(uint32 flightMasterEntry, WorldPosition flightMasterPos, std::vector<uint32> path);
void ChangeToOutdoorPvp(ObjectGuid::LowType capturePointSpawnId = 0);
void ChangeToRest();
void ChangeToIdle();
bool CanChangeTo(NewRpgStatus status);
void Reset();
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;
}
};
#endif