From ee2a399ac8544bbb6cc9d56e88252afce8405fc1 Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Fri, 13 Feb 2026 09:19:54 -0800 Subject: [PATCH] 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(&info.data); if (!dataPtr) return false; auto& data = *dataPtr; ``` --------- Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com> --- src/Ai/Base/Value/GrindTargetValue.cpp | 2 +- src/Ai/World/Rpg/Action/NewRpgAction.cpp | 157 ++++++++++++--------- src/Ai/World/Rpg/Action/NewRpgAction.h | 4 +- src/Ai/World/Rpg/NewRpgInfo.cpp | 139 ++++++++++-------- src/Ai/World/Rpg/NewRpgInfo.h | 30 ++-- src/Ai/World/Rpg/Trigger/NewRpgTrigger.cpp | 2 +- src/Bot/RandomPlayerbotMgr.cpp | 2 +- src/PlayerbotAIConfig.h | 21 ++- 8 files changed, 196 insertions(+), 161 deletions(-) diff --git a/src/Ai/Base/Value/GrindTargetValue.cpp b/src/Ai/Base/Value/GrindTargetValue.cpp index ce021a2d9..573de8fb0 100644 --- a/src/Ai/Base/Value/GrindTargetValue.cpp +++ b/src/Ai/Base/Value/GrindTargetValue.cpp @@ -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()) diff --git a/src/Ai/World/Rpg/Action/NewRpgAction.cpp b/src/Ai/World/Rpg/Action/NewRpgAction.cpp index 41fec2291..2c42741c8 100644 --- a/src/Ai/World/Rpg/Action/NewRpgAction.cpp +++ b/src/Ai/World/Rpg/Action/NewRpgAction.cpp @@ -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(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(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(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(&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(&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(&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(&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; 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(&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 nodes = {botAI->rpgInfo.flight.fromNode, botAI->rpgInfo.flight.toNode}; + + std::vector 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(), diff --git a/src/Ai/World/Rpg/Action/NewRpgAction.h b/src/Ai/World/Rpg/Action/NewRpgAction.h index 0e621fc40..a8cb7a2bc 100644 --- a/src/Ai/World/Rpg/Action/NewRpgAction.h +++ b/src/Ai/World/Rpg/Action/NewRpgAction.h @@ -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; }; diff --git a/src/Ai/World/Rpg/NewRpgInfo.cpp b/src/Ai/World/Rpg/NewRpgInfo.cpp index 889fb1ff9..def0d472d 100644 --- a/src/Ai/World/Rpg/NewRpgInfo.cpp +++ b/src/Ai/World/Rpg/NewRpgInfo.cpp @@ -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; + if constexpr (std::is_same_v) return RPG_IDLE; + if constexpr (std::is_same_v) return RPG_GO_GRIND; + if constexpr (std::is_same_v) return RPG_GO_CAMP; + if constexpr (std::is_same_v) return RPG_WANDER_NPC; + if constexpr (std::is_same_v) return RPG_WANDER_RANDOM; + if constexpr (std::is_same_v) return RPG_REST; + if constexpr (std::is_same_v) return RPG_DO_QUEST; + if constexpr (std::is_same_v) 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; + if constexpr (std::is_same_v) + { 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) + { 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) + { 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) + { out << "WANDER_RANDOM"; out << "\nlastWanderRandom: " << startT; - break; - case RPG_IDLE: + } + else if constexpr (std::is_same_v) + { out << "IDLE"; - break; - case RPG_REST: + } + else if constexpr (std::is_same_v) + { out << "REST"; out << "\nlastRest: " << startT; - break; - case RPG_DO_QUEST: + } + else if constexpr (std::is_same_v) + { 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) + { 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(); } diff --git a/src/Ai/World/Rpg/NewRpgInfo.h b/src/Ai/World/Rpg/NewRpgInfo.h index 20f801bf7..5b6ae3cb9 100644 --- a/src/Ai/World/Rpg/NewRpgInfo.h +++ b/src/Ai/World/Rpg/NewRpgInfo.h @@ -13,7 +13,8 @@ using NewRpgStatusTransitionProb = std::vector>; 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 diff --git a/src/Ai/World/Rpg/Trigger/NewRpgTrigger.cpp b/src/Ai/World/Rpg/Trigger/NewRpgTrigger.cpp index 0804320ad..65e78eb4c 100644 --- a/src/Ai/World/Rpg/Trigger/NewRpgTrigger.cpp +++ b/src/Ai/World/Rpg/Trigger/NewRpgTrigger.cpp @@ -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(); } diff --git a/src/Bot/RandomPlayerbotMgr.cpp b/src/Bot/RandomPlayerbotMgr.cpp index 892368c66..c2b3fae3c 100644 --- a/src/Bot/RandomPlayerbotMgr.cpp +++ b/src/Bot/RandomPlayerbotMgr.cpp @@ -3252,7 +3252,7 @@ void RandomPlayerbotMgr::PrintStats() if (sPlayerbotAIConfig.enableNewRpgStrategy) { - rpgStatusCount[botAI->rpgInfo.status]++; + rpgStatusCount[botAI->rpgInfo.GetStatus()]++; rpgStasticTotal += botAI->rpgStatistic; botAI->rpgStatistic = NewRpgStatistic(); } diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 729fc5be1..27177565c 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -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 };