Refactor newrpginfo data union to std::variant (#2079)

# Pull Request

As I began modifying the newrpginfo to change the types of data it
stored, or add new data I found myself with the issue of ending up
either with garbage memory if the information wasnt properly stored on
status change, or needing complicated destructor patterns for non
trivial data sets.

---

## Design Philosophy

Make rpginfo able to handle more complicated information in a strongly 

---

## Feature Evaluation

No Feature changes

---

## How to Test the Changes

-  Server should be stable for an extended period of time. 
- Bots should be able to complete quests, fly, etc as they did before.

## Complexity & Impact

- Does this change add new decision branches?
    - [X ] No
    - [ ] Yes (**explain below**)

- Does this change increase per-bot or per-tick processing?
    - [ ] No
    - [ X] Yes (**describe and justify impact**)
Potentially as there can be more memory involved in the object.

- Could this logic scale poorly under load?
    - [X ] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [ X] No
    - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:

- [ ] Lightweight mode remains the default
- [ ] More complex behavior is optional and thereby configurable

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [ ] No
    - [ X] Yes (**explain below**)

If yes, please specify:

- Gemini suggested the use of std::variant as an alternative data
structure. I found additinal external references that correlated with
the same suggestion of moving away from a union.
- Implementation was performed manually with Co-pilot auto-complete

---

## Final Checklist 
In progress.
- [ ] Stability is not compromised
- [ ] Performance impact is understood, tested, and acceptable
- [ ] Added logic complexity is justified and explained
- [ ] Documentation updated if needed

---

## Notes for Reviewers

Im not 100% sure if this is a good design choice. There are some things
I didnt quite like by the end of this, specifically having to double
check whenever accessing data whether exists or not even though an
action has already been triggered. But I have a PR in the works where I
want to store a full flight path vector, and the union was giving me
issues. (It appears that state changes may be occuring in the same tick
between RPG status update and the stated action, leading to incorrect
data gathering.

I ended up solving it by first checking a pointer to the object, and
then getting the reference.
```c++
    auto* dataPtr = std::get_if<NewRpgInfo::DoQuest>(&info.data);
    if (!dataPtr)
        return false;
    auto& data = *dataPtr;
```

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
This commit is contained in:
Keleborn
2026-02-13 09:19:54 -08:00
committed by GitHub
parent 610fdc16d7
commit ee2a399ac8
8 changed files with 196 additions and 161 deletions

View File

@@ -107,7 +107,7 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
continue;
}
bool inactiveGrindStatus = botAI->rpgInfo.status != RPG_WANDER_RANDOM && botAI->rpgInfo.status != RPG_IDLE;
bool inactiveGrindStatus = botAI->rpgInfo.GetStatus() != RPG_WANDER_RANDOM && botAI->rpgInfo.GetStatus() != RPG_IDLE;
float aggroRange = 30.0f;
if (unit->ToCreature())

View File

@@ -64,17 +64,18 @@ bool StartRpgDoQuestAction::Execute(Event event)
bool NewRpgStatusUpdateAction::Execute(Event event)
{
NewRpgInfo& info = botAI->rpgInfo;
switch (info.status)
NewRpgStatus status = info.GetStatus();
switch (status)
{
case RPG_IDLE:
{
return RandomChangeStatus({RPG_GO_CAMP, RPG_GO_GRIND, RPG_WANDER_RANDOM, RPG_WANDER_NPC, RPG_DO_QUEST,
RPG_TRAVEL_FLIGHT, RPG_REST});
}
case RPG_GO_GRIND:
{
WorldPosition& originalPos = info.go_grind.pos;
assert(info.go_grind.pos != WorldPosition());
auto& data = std::get<NewRpgInfo::GoGrind>(info.data);
WorldPosition& originalPos = data.pos;
assert(data.pos != WorldPosition());
// GO_GRIND -> WANDER_RANDOM
if (bot->GetExactDist(originalPos) < 10.0f)
{
@@ -85,8 +86,9 @@ bool NewRpgStatusUpdateAction::Execute(Event event)
}
case RPG_GO_CAMP:
{
WorldPosition& originalPos = info.go_camp.pos;
assert(info.go_camp.pos != WorldPosition());
auto& data = std::get<NewRpgInfo::GoCamp>(info.data);
WorldPosition& originalPos = data.pos;
assert(data.pos != WorldPosition());
// GO_CAMP -> WANDER_NPC
if (bot->GetExactDist(originalPos) < 10.0f)
{
@@ -126,7 +128,8 @@ bool NewRpgStatusUpdateAction::Execute(Event event)
}
case RPG_TRAVEL_FLIGHT:
{
if (info.flight.inFlight && !bot->IsInFlight())
auto& data = std::get<NewRpgInfo::TravelFlight>(info.data);
if (data.inFlight && !bot->IsInFlight())
{
// flight arrival
info.ChangeToIdle();
@@ -154,8 +157,10 @@ bool NewRpgGoGrindAction::Execute(Event event)
{
if (SearchQuestGiverAndAcceptOrReward())
return true;
if (auto* data = std::get_if<NewRpgInfo::GoGrind>(&botAI->rpgInfo.data))
return MoveFarTo(data->pos);
return MoveFarTo(botAI->rpgInfo.go_grind.pos);
return false;
}
bool NewRpgGoCampAction::Execute(Event event)
@@ -163,7 +168,10 @@ bool NewRpgGoCampAction::Execute(Event event)
if (SearchQuestGiverAndAcceptOrReward())
return true;
return MoveFarTo(botAI->rpgInfo.go_camp.pos);
if (auto* data = std::get_if<NewRpgInfo::GoCamp>(&botAI->rpgInfo.data))
return MoveFarTo(data->pos);
return false;
}
bool NewRpgWanderRandomAction::Execute(Event event)
@@ -177,7 +185,11 @@ bool NewRpgWanderRandomAction::Execute(Event event)
bool NewRpgWanderNpcAction::Execute(Event event)
{
NewRpgInfo& info = botAI->rpgInfo;
if (!info.wander_npc.npcOrGo)
auto* dataPtr = std::get_if<NewRpgInfo::WanderNpc>(&info.data);
if (!dataPtr)
return false;
auto& data = *dataPtr;
if (!data.npcOrGo)
{
// No npc can be found, switch to IDLE
ObjectGuid npcOrGo = ChooseNpcOrGameObjectToInteract();
@@ -186,33 +198,32 @@ bool NewRpgWanderNpcAction::Execute(Event event)
info.ChangeToIdle();
return true;
}
info.wander_npc.npcOrGo = npcOrGo;
info.wander_npc.lastReach = 0;
data.npcOrGo = npcOrGo;
data.lastReach = 0;
return true;
}
WorldObject* object = ObjectAccessor::GetWorldObject(*bot, info.wander_npc.npcOrGo);
WorldObject* object = ObjectAccessor::GetWorldObject(*bot, data.npcOrGo);
if (object && IsWithinInteractionDist(object))
{
if (!info.wander_npc.lastReach)
if (!data.lastReach)
{
info.wander_npc.lastReach = getMSTime();
data.lastReach = getMSTime();
if (bot->CanInteractWithQuestGiver(object))
InteractWithNpcOrGameObjectForQuest(info.wander_npc.npcOrGo);
InteractWithNpcOrGameObjectForQuest(data.npcOrGo);
return true;
}
if (info.wander_npc.lastReach && GetMSTimeDiffToNow(info.wander_npc.lastReach) < npcStayTime)
if (data.lastReach && GetMSTimeDiffToNow(data.lastReach) < npcStayTime)
return false;
// has reached the npc for more than `npcStayTime`, select the next target
info.wander_npc.npcOrGo = ObjectGuid();
info.wander_npc.lastReach = 0;
data.npcOrGo = ObjectGuid();
data.lastReach = 0;
}
else
{
return MoveWorldObjectTo(info.wander_npc.npcOrGo);
}
return MoveWorldObjectTo(data.npcOrGo);
return true;
}
@@ -222,29 +233,33 @@ bool NewRpgDoQuestAction::Execute(Event event)
return true;
NewRpgInfo& info = botAI->rpgInfo;
uint32 questId = RPG_INFO(quest, questId);
const Quest* quest = RPG_INFO(quest, quest);
auto* dataPtr = std::get_if<NewRpgInfo::DoQuest>(&info.data);
if (!dataPtr)
return false;
auto& data = *dataPtr;
uint32 questId = data.questId;
const Quest* quest = data.quest;
uint8 questStatus = bot->GetQuestStatus(questId);
switch (questStatus)
{
case QUEST_STATUS_INCOMPLETE:
return DoIncompleteQuest();
return DoIncompleteQuest(data);
case QUEST_STATUS_COMPLETE:
return DoCompletedQuest();
return DoCompletedQuest(data);
default:
break;
}
botAI->rpgInfo.ChangeToIdle();
info.ChangeToIdle();
return true;
}
bool NewRpgDoQuestAction::DoIncompleteQuest()
bool NewRpgDoQuestAction::DoIncompleteQuest(NewRpgInfo::DoQuest& data)
{
uint32 questId = RPG_INFO(do_quest, questId);
if (botAI->rpgInfo.do_quest.pos != WorldPosition())
uint32 questId = data.questId;
if (data.pos != WorldPosition())
{
/// @TODO: extract to a new function
int32 currentObjective = botAI->rpgInfo.do_quest.objectiveIdx;
int32 currentObjective = data.objectiveIdx;
// check if the objective has completed
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
const QuestStatusData& q_status = bot->getQuestStatusMap().at(questId);
@@ -263,12 +278,12 @@ bool NewRpgDoQuestAction::DoIncompleteQuest()
// 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;
data.lastReachPOI = 0;
data.pos = WorldPosition();
data.objectiveIdx = 0;
}
}
if (botAI->rpgInfo.do_quest.pos == WorldPosition())
if (data.pos == WorldPosition())
{
std::vector<POIInfo> poiInfo;
if (!GetQuestPOIPosAndObjectiveIdx(questId, poiInfo))
@@ -291,28 +306,28 @@ bool NewRpgDoQuestAction::DoIncompleteQuest()
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;
data.lastReachPOI = 0;
data.pos = pos;
data.objectiveIdx = objectiveIdx;
}
if (bot->GetDistance(botAI->rpgInfo.do_quest.pos) > 10.0f && !botAI->rpgInfo.do_quest.lastReachPOI)
if (bot->GetDistance(data.pos) > 10.0f && !data.lastReachPOI)
{
return MoveFarTo(botAI->rpgInfo.do_quest.pos);
return MoveFarTo(data.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)
if (!data.lastReachPOI)
{
botAI->rpgInfo.do_quest.lastReachPOI = getMSTime();
data.lastReachPOI = getMSTime();
return true;
}
// stayed at this POI for more than 5 minutes
if (GetMSTimeDiffToNow(botAI->rpgInfo.do_quest.lastReachPOI) >= poiStayTime)
if (GetMSTimeDiffToNow(data.lastReachPOI) >= poiStayTime)
{
bool hasProgression = false;
int32 currentObjective = botAI->rpgInfo.do_quest.objectiveIdx;
int32 currentObjective = data.objectiveIdx;
// check if the objective has progression
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
const QuestStatusData& q_status = bot->getQuestStatusMap().at(questId);
@@ -339,21 +354,21 @@ bool NewRpgDoQuestAction::DoIncompleteQuest()
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;
data.lastReachPOI = 0;
data.pos = WorldPosition();
data.objectiveIdx = 0;
return true;
}
return MoveRandomNear(20.0f);
}
bool NewRpgDoQuestAction::DoCompletedQuest()
bool NewRpgDoQuestAction::DoCompletedQuest(NewRpgInfo::DoQuest& data)
{
uint32 questId = RPG_INFO(quest, questId);
const Quest* quest = RPG_INFO(quest, quest);
uint32 questId = data.questId;
const Quest* quest = data.quest;
if (RPG_INFO(quest, objectiveIdx) != -1)
if (data.objectiveIdx != -1)
{
// if quest is completed, back to poi with -1 idx to reward
BroadcastHelper::BroadcastQuestUpdateComplete(botAI, bot, quest);
@@ -376,26 +391,26 @@ bool NewRpgDoQuestAction::DoCompletedQuest()
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;
data.lastReachPOI = 0;
data.pos = pos;
data.objectiveIdx = -1;
}
if (botAI->rpgInfo.do_quest.pos == WorldPosition())
if (data.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);
if (bot->GetDistance(data.pos) > 10.0f && !data.lastReachPOI)
return MoveFarTo(data.pos);
// Now we are near the qoi of reward
// the quest should be rewarded by SearchQuestGiverAndAcceptOrReward
if (!botAI->rpgInfo.do_quest.lastReachPOI)
if (!data.lastReachPOI)
{
botAI->rpgInfo.do_quest.lastReachPOI = getMSTime();
data.lastReachPOI = getMSTime();
return true;
}
// stayed at this POI for more than 5 minutes
if (GetMSTimeDiffToNow(botAI->rpgInfo.do_quest.lastReachPOI) >= poiStayTime)
if (GetMSTimeDiffToNow(data.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)
@@ -410,29 +425,33 @@ bool NewRpgDoQuestAction::DoCompletedQuest()
bool NewRpgTravelFlightAction::Execute(Event event)
{
NewRpgInfo& info = botAI->rpgInfo;
auto* dataPtr = std::get_if<NewRpgInfo::TravelFlight>(&info.data);
if (!dataPtr)
return false;
auto& data = *dataPtr;
if (bot->IsInFlight())
{
botAI->rpgInfo.flight.inFlight = true;
data.inFlight = true;
return false;
}
Creature* flightMaster = ObjectAccessor::GetCreature(*bot, botAI->rpgInfo.flight.fromFlightMaster);
Creature* flightMaster = ObjectAccessor::GetCreature(*bot, data.fromFlightMaster);
if (!flightMaster || !flightMaster->IsAlive())
{
botAI->rpgInfo.ChangeToIdle();
return true;
}
const TaxiNodesEntry* entry = sTaxiNodesStore.LookupEntry(botAI->rpgInfo.flight.toNode);
const TaxiNodesEntry* entry = sTaxiNodesStore.LookupEntry(data.toNode);
if (bot->GetDistance(flightMaster) > INTERACTION_DISTANCE)
{
return MoveFarTo(flightMaster);
}
std::vector<uint32> nodes = {botAI->rpgInfo.flight.fromNode, botAI->rpgInfo.flight.toNode};
std::vector<uint32> nodes = {data.fromNode, data.toNode};
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(),

View File

@@ -90,8 +90,8 @@ public:
bool Execute(Event event) override;
protected:
bool DoIncompleteQuest();
bool DoCompletedQuest();
bool DoIncompleteQuest(NewRpgInfo::DoQuest& data);
bool DoCompletedQuest(NewRpgInfo::DoQuest& data);
const uint32 poiStayTime = 5 * 60 * 1000;
};

View File

@@ -6,71 +6,65 @@
void NewRpgInfo::ChangeToGoGrind(WorldPosition pos)
{
Reset();
status = RPG_GO_GRIND;
go_grind = GoGrind();
go_grind.pos = pos;
startT = getMSTime();
data = GoGrind{pos};
}
void NewRpgInfo::ChangeToGoCamp(WorldPosition pos)
{
Reset();
status = RPG_GO_CAMP;
go_camp = GoCamp();
go_camp.pos = pos;
startT = getMSTime();
data = GoCamp{pos};
}
void NewRpgInfo::ChangeToWanderNpc()
{
Reset();
status = RPG_WANDER_NPC;
wander_npc = WanderNpc();
startT = getMSTime();
data = WanderNpc{};
}
void NewRpgInfo::ChangeToWanderRandom()
{
Reset();
status = RPG_WANDER_RANDOM;
WANDER_RANDOM = WanderRandom();
startT = getMSTime();
data = WanderRandom{};
}
void NewRpgInfo::ChangeToDoQuest(uint32 questId, const Quest* quest)
{
Reset();
status = RPG_DO_QUEST;
do_quest = DoQuest();
startT = getMSTime();
DoQuest do_quest;
do_quest.questId = questId;
do_quest.quest = quest;
data = do_quest;
}
void NewRpgInfo::ChangeToTravelFlight(ObjectGuid fromFlightMaster, uint32 fromNode, uint32 toNode)
{
Reset();
status = RPG_TRAVEL_FLIGHT;
flight = TravelFlight();
startT = getMSTime();
TravelFlight flight;
flight.fromFlightMaster = fromFlightMaster;
flight.fromNode = fromNode;
flight.toNode = toNode;
flight.inFlight = false;
data = flight;
}
void NewRpgInfo::ChangeToRest()
{
Reset();
status = RPG_REST;
rest = Rest();
startT = getMSTime();
data = Rest{};
}
void NewRpgInfo::ChangeToIdle()
{
Reset();
status = RPG_IDLE;
startT = getMSTime();
data = Idle{};
}
bool NewRpgInfo::CanChangeTo(NewRpgStatus status) { return true; }
void NewRpgInfo::Reset()
{
*this = NewRpgInfo();
data = Idle{};
startT = getMSTime();
}
@@ -82,58 +76,83 @@ void NewRpgInfo::SetMoveFarTo(WorldPosition pos)
moveFarPos = pos;
}
NewRpgStatus NewRpgInfo::GetStatus()
{
return std::visit([](auto&& arg) -> NewRpgStatus {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Idle>) return RPG_IDLE;
if constexpr (std::is_same_v<T, GoGrind>) return RPG_GO_GRIND;
if constexpr (std::is_same_v<T, GoCamp>) return RPG_GO_CAMP;
if constexpr (std::is_same_v<T, WanderNpc>) return RPG_WANDER_NPC;
if constexpr (std::is_same_v<T, WanderRandom>) return RPG_WANDER_RANDOM;
if constexpr (std::is_same_v<T, Rest>) return RPG_REST;
if constexpr (std::is_same_v<T, DoQuest>) return RPG_DO_QUEST;
if constexpr (std::is_same_v<T, TravelFlight>) return RPG_TRAVEL_FLIGHT;
return RPG_IDLE;
}, data);
}
std::string NewRpgInfo::ToString()
{
std::stringstream out;
out << "Status: ";
switch (status)
std::visit([&out, this](auto&& arg)
{
case RPG_GO_GRIND:
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, GoGrind>)
{
out << "GO_GRIND";
out << "\nGrindPos: " << go_grind.pos.GetMapId() << " " << go_grind.pos.GetPositionX() << " "
<< go_grind.pos.GetPositionY() << " " << go_grind.pos.GetPositionZ();
out << "\nGrindPos: " << arg.pos.GetMapId() << " " << arg.pos.GetPositionX() << " "
<< arg.pos.GetPositionY() << " " << arg.pos.GetPositionZ();
out << "\nlastGoGrind: " << startT;
break;
case RPG_GO_CAMP:
}
else if constexpr (std::is_same_v<T, GoCamp>)
{
out << "GO_CAMP";
out << "\nCampPos: " << go_camp.pos.GetMapId() << " " << go_camp.pos.GetPositionX() << " "
<< go_camp.pos.GetPositionY() << " " << go_camp.pos.GetPositionZ();
out << "\nCampPos: " << arg.pos.GetMapId() << " " << arg.pos.GetPositionX() << " "
<< arg.pos.GetPositionY() << " " << arg.pos.GetPositionZ();
out << "\nlastGoCamp: " << startT;
break;
case RPG_WANDER_NPC:
}
else if constexpr (std::is_same_v<T, WanderNpc>)
{
out << "WANDER_NPC";
out << "\nnpcOrGoEntry: " << wander_npc.npcOrGo.GetCounter();
out << "\nnpcOrGoEntry: " << arg.npcOrGo.GetCounter();
out << "\nlastWanderNpc: " << startT;
out << "\nlastReachNpcOrGo: " << wander_npc.lastReach;
break;
case RPG_WANDER_RANDOM:
out << "\nlastReachNpcOrGo: " << arg.lastReach;
}
else if constexpr (std::is_same_v<T, WanderRandom>)
{
out << "WANDER_RANDOM";
out << "\nlastWanderRandom: " << startT;
break;
case RPG_IDLE:
}
else if constexpr (std::is_same_v<T, Idle>)
{
out << "IDLE";
break;
case RPG_REST:
}
else if constexpr (std::is_same_v<T, Rest>)
{
out << "REST";
out << "\nlastRest: " << startT;
break;
case RPG_DO_QUEST:
}
else if constexpr (std::is_same_v<T, DoQuest>)
{
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 ? GetMSTimeDiffToNow(do_quest.lastReachPOI) : 0;
break;
case RPG_TRAVEL_FLIGHT:
out << "\nquestId: " << arg.questId;
out << "\nobjectiveIdx: " << arg.objectiveIdx;
out << "\npoiPos: " << arg.pos.GetMapId() << " " << arg.pos.GetPositionX() << " "
<< arg.pos.GetPositionY() << " " << arg.pos.GetPositionZ();
out << "\nlastReachPOI: " << (arg.lastReachPOI ? GetMSTimeDiffToNow(arg.lastReachPOI) : 0);
}
else if constexpr (std::is_same_v<T, TravelFlight>)
{
out << "TRAVEL_FLIGHT";
out << "\nfromFlightMaster: " << flight.fromFlightMaster.GetEntry();
out << "\nfromNode: " << flight.fromNode;
out << "\ntoNode: " << flight.toNode;
out << "\ninFlight: " << flight.inFlight;
break;
default:
out << "\nfromFlightMaster: " << arg.fromFlightMaster.GetEntry();
out << "\nfromNode: " << arg.fromNode;
out << "\ntoNode: " << arg.toNode;
out << "\ninFlight: " << arg.inFlight;
}
else
out << "UNKNOWN";
}
}, data);
return out.str();
}

View File

@@ -13,7 +13,8 @@ using NewRpgStatusTransitionProb = std::vector<std::vector<int>>;
struct NewRpgInfo
{
NewRpgInfo() {}
NewRpgInfo() : data(Idle{}) {}
~NewRpgInfo() = default;
// RPG_GO_GRIND
struct GoGrind
@@ -61,7 +62,6 @@ struct NewRpgInfo
struct Idle
{
};
NewRpgStatus status{RPG_IDLE};
uint32 startT{0}; // start timestamp of the current status
@@ -72,18 +72,19 @@ struct NewRpgInfo
WorldPosition moveFarPos;
// END MOVE_FAR
union
{
GoGrind go_grind;
GoCamp go_camp;
WanderNpc wander_npc;
WanderRandom WANDER_RANDOM;
DoQuest do_quest;
Rest rest;
DoQuest quest;
TravelFlight flight;
};
using RpgData = std::variant<
Idle,
GoGrind,
GoCamp,
WanderNpc,
WanderRandom,
DoQuest,
Rest,
TravelFlight
>;
RpgData data;
NewRpgStatus GetStatus();
bool HasStatusPersisted(uint32 maxDuration) { return GetMSTimeDiffToNow(startT) > maxDuration; }
void ChangeToGoGrind(WorldPosition pos);
void ChangeToGoCamp(WorldPosition pos);
@@ -127,7 +128,4 @@ struct NewRpgStatistic
}
};
// not sure is it necessary but keep it for now
#define RPG_INFO(x, y) botAI->rpgInfo.x.y
#endif

View File

@@ -1,4 +1,4 @@
#include "NewRpgTriggers.h"
#include "PlayerbotAI.h"
bool NewRpgStatusTrigger::IsActive() { return status == botAI->rpgInfo.status; }
bool NewRpgStatusTrigger::IsActive() { return status == botAI->rpgInfo.GetStatus(); }

View File

@@ -3252,7 +3252,7 @@ void RandomPlayerbotMgr::PrintStats()
if (sPlayerbotAIConfig.enableNewRpgStrategy)
{
rpgStatusCount[botAI->rpgInfo.status]++;
rpgStatusCount[botAI->rpgInfo.GetStatus()]++;
rpgStasticTotal += botAI->rpgStatistic;
botAI->rpgStatistic = NewRpgStatistic();
}

View File

@@ -42,21 +42,20 @@ enum class HealingManaEfficiency : uint8
enum NewRpgStatus : int
{
RPG_STATUS_START = 0,
// Going to far away place
RPG_GO_GRIND = 0,
RPG_GO_CAMP = 1,
//Initial Status
RPG_IDLE = 0,
RPG_GO_GRIND = 1,
RPG_GO_CAMP = 2,
// Exploring nearby
RPG_WANDER_RANDOM = 2,
RPG_WANDER_NPC = 3,
RPG_WANDER_RANDOM = 3,
RPG_WANDER_NPC = 4,
// Do Quest (based on quest status)
RPG_DO_QUEST = 4,
RPG_DO_QUEST = 5,
// Travel
RPG_TRAVEL_FLIGHT = 5,
RPG_TRAVEL_FLIGHT = 6,
// Taking a break
RPG_REST = 6,
// Initial status
RPG_IDLE = 7,
RPG_REST = 7,
RPG_STATUS_END = 8
};