получение instance id для encounter journal
This commit is contained in:
@@ -9,8 +9,11 @@
|
||||
#include "../IndividualProgression.h"
|
||||
|
||||
#include "Config.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Field.h"
|
||||
#include "Log.h"
|
||||
#include "Player.h"
|
||||
#include "QueryResult.h"
|
||||
#include "QuestDef.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -202,6 +205,45 @@ namespace MoonWell::PlayerGuide
|
||||
// characters. We leave room for the framing header.
|
||||
if (_chunkSize < 64) _chunkSize = 64;
|
||||
if (_chunkSize > 240) _chunkSize = 240;
|
||||
|
||||
ReloadEncounterJournalCache();
|
||||
}
|
||||
|
||||
void Mgr::ReloadEncounterJournalCache()
|
||||
{
|
||||
_ejByMapId.clear();
|
||||
|
||||
// mod-encounter-journal may be disabled or its table absent.
|
||||
// The query then yields no result and we keep an empty cache;
|
||||
// recommendations that would have used EJ ids are skipped.
|
||||
QueryResult result = WorldDatabase.Query(
|
||||
"SELECT `id`, `map_id`, `name` "
|
||||
"FROM `custom_ej_instance`");
|
||||
if (!result)
|
||||
{
|
||||
LOG_INFO("server.loading",
|
||||
">> PlayerGuide: no custom_ej_instance rows found. "
|
||||
"Recommendations will be empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
Field* f = result->Fetch();
|
||||
EjInstanceRef ref;
|
||||
ref.ejId = f[0].Get<uint32>();
|
||||
uint32 mapId = f[1].Get<uint32>();
|
||||
ref.name = f[2].Get<std::string>();
|
||||
|
||||
// First entry wins. If the EJ has duplicates for the same
|
||||
// map (e.g. heroic variants) the lowest id is taken via the
|
||||
// INSERT order; we don't reorder.
|
||||
_ejByMapId.emplace(mapId, std::move(ref));
|
||||
} while (result->NextRow());
|
||||
|
||||
LOG_INFO("server.loading",
|
||||
">> PlayerGuide: cached {} EJ instance entries by mapId.",
|
||||
_ejByMapId.size());
|
||||
}
|
||||
|
||||
std::string Mgr::StageName(uint32 stage) const
|
||||
@@ -258,14 +300,19 @@ namespace MoonWell::PlayerGuide
|
||||
std::string const& title,
|
||||
uint32 order) const
|
||||
{
|
||||
auto it = _ejByMapId.find(mapId);
|
||||
if (it == _ejByMapId.end())
|
||||
return; // no EJ entry -> skip rather than emit a stub.
|
||||
|
||||
Objective o;
|
||||
o.id = order;
|
||||
o.type = "dungeon";
|
||||
o.title = title;
|
||||
o.required = 1;
|
||||
o.targetId = mapId;
|
||||
o.instanceId = mapId;
|
||||
o.order = order;
|
||||
o.id = order;
|
||||
o.type = "dungeon";
|
||||
o.title = it->second.name.empty() ? title : it->second.name;
|
||||
o.required = 1;
|
||||
o.targetId = it->second.ejId; // primary id for click
|
||||
o.ejInstanceId = it->second.ejId; // JOURNALINSTANCE key
|
||||
o.mapId = mapId; // WoW map id (auxiliary)
|
||||
o.order = order;
|
||||
out.objectives.push_back(std::move(o));
|
||||
}
|
||||
|
||||
@@ -273,14 +320,19 @@ namespace MoonWell::PlayerGuide
|
||||
std::string const& title,
|
||||
uint32 order) const
|
||||
{
|
||||
auto it = _ejByMapId.find(mapId);
|
||||
if (it == _ejByMapId.end())
|
||||
return;
|
||||
|
||||
Objective o;
|
||||
o.id = order;
|
||||
o.type = "raid";
|
||||
o.title = title;
|
||||
o.required = 1;
|
||||
o.targetId = mapId;
|
||||
o.instanceId = mapId;
|
||||
o.order = order;
|
||||
o.id = order;
|
||||
o.type = "raid";
|
||||
o.title = it->second.name.empty() ? title : it->second.name;
|
||||
o.required = 1;
|
||||
o.targetId = it->second.ejId;
|
||||
o.ejInstanceId = it->second.ejId;
|
||||
o.mapId = mapId;
|
||||
o.order = order;
|
||||
out.objectives.push_back(std::move(o));
|
||||
}
|
||||
|
||||
@@ -298,18 +350,29 @@ namespace MoonWell::PlayerGuide
|
||||
continue;
|
||||
if (level + 2 < d.minLevel || level > d.maxLevel + 1)
|
||||
continue;
|
||||
if (!seen.insert(d.mapId).second)
|
||||
|
||||
// Resolve EJ id from custom_ej_instance. If the EJ does
|
||||
// not know about this map, we cannot link to a card, so
|
||||
// we skip the recommendation entirely. This keeps the
|
||||
// contract that recommendations.instanceID is always a
|
||||
// valid EJ id.
|
||||
auto it = _ejByMapId.find(d.mapId);
|
||||
if (it == _ejByMapId.end())
|
||||
continue;
|
||||
if (!seen.insert(it->second.ejId).second)
|
||||
continue;
|
||||
|
||||
Recommendation r;
|
||||
r.id = recId++;
|
||||
r.type = "dungeon";
|
||||
r.title = d.title;
|
||||
r.instanceId = d.mapId;
|
||||
r.priority = 100 -
|
||||
r.id = recId++;
|
||||
r.type = "dungeon";
|
||||
r.title = it->second.name.empty() ? d.title
|
||||
: it->second.name;
|
||||
r.ejInstanceId = it->second.ejId; // JOURNALINSTANCE key
|
||||
r.mapId = d.mapId; // WoW map id (auxiliary)
|
||||
r.priority = 100 -
|
||||
static_cast<uint32>(std::abs(int32(level) -
|
||||
int32((d.minLevel + d.maxLevel) / 2)));
|
||||
r.description = "Подходит для вашего текущего этапа.";
|
||||
r.description = "Подходит для вашего текущего этапа.";
|
||||
out.recommendations.push_back(std::move(r));
|
||||
|
||||
if (out.recommendations.size() >= 6)
|
||||
@@ -496,15 +559,16 @@ namespace MoonWell::PlayerGuide
|
||||
WriteUInt(s, "targetID", o.targetId, first);
|
||||
WriteUInt(s, "order", o.order, first);
|
||||
|
||||
WriteOptUInt(s, "questID", o.questId, first);
|
||||
WriteOptUInt(s, "questChainID", o.questChainId, first);
|
||||
WriteOptUInt(s, "instanceID", o.instanceId, first);
|
||||
WriteOptUInt(s, "encounterID", o.encounterId, first);
|
||||
WriteOptUInt(s, "achievementID", o.achievementId, first);
|
||||
WriteOptUInt(s, "factionID", o.factionId, first);
|
||||
WriteOptUInt(s, "professionID", o.professionId, first);
|
||||
WriteOptUInt(s, "itemID", o.itemId, first);
|
||||
WriteOptUInt(s, "currencyID", o.currencyId, first);
|
||||
WriteOptUInt(s, "questID", o.questId, first);
|
||||
WriteOptUInt(s, "questChainID", o.questChainId, first);
|
||||
WriteOptUInt(s, "ej_instanceID", o.ejInstanceId, first);
|
||||
WriteOptUInt(s, "mapID", o.mapId, first);
|
||||
WriteOptUInt(s, "encounterID", o.encounterId, first);
|
||||
WriteOptUInt(s, "achievementID", o.achievementId, first);
|
||||
WriteOptUInt(s, "factionID", o.factionId, first);
|
||||
WriteOptUInt(s, "professionID", o.professionId, first);
|
||||
WriteOptUInt(s, "itemID", o.itemId, first);
|
||||
WriteOptUInt(s, "currencyID", o.currencyId, first);
|
||||
s << '}';
|
||||
return s.str();
|
||||
}
|
||||
@@ -551,10 +615,11 @@ namespace MoonWell::PlayerGuide
|
||||
WriteString(s, "title", r.title, f);
|
||||
if (!r.description.empty())
|
||||
WriteString(s, "description", r.description, f);
|
||||
WriteOptUInt(s, "instanceID", r.instanceId, f);
|
||||
WriteOptUInt(s, "encounterID", r.encounterId, f);
|
||||
WriteOptUInt(s, "questID", r.questId, f);
|
||||
WriteOptUInt(s, "itemID", r.itemId, f);
|
||||
WriteOptUInt(s, "ej_instanceID", r.ejInstanceId, f);
|
||||
WriteOptUInt(s, "mapID", r.mapId, f);
|
||||
WriteOptUInt(s, "encounterID", r.encounterId, f);
|
||||
WriteOptUInt(s, "questID", r.questId, f);
|
||||
WriteOptUInt(s, "itemID", r.itemId, f);
|
||||
WriteUInt(s, "priority", r.priority, f);
|
||||
s << '}';
|
||||
firstRec = false;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class Player;
|
||||
@@ -36,28 +37,34 @@ namespace MoonWell::PlayerGuide
|
||||
|
||||
// Optional type-specific helpers. Zero means "not set" and
|
||||
// serialiser omits it.
|
||||
uint32 questId = 0;
|
||||
uint32 questChainId = 0;
|
||||
uint32 instanceId = 0;
|
||||
uint32 encounterId = 0;
|
||||
uint32 achievementId = 0;
|
||||
uint32 factionId = 0;
|
||||
uint32 professionId = 0;
|
||||
uint32 itemId = 0;
|
||||
uint32 currencyId = 0;
|
||||
uint32 questId = 0;
|
||||
uint32 questChainId = 0;
|
||||
// ej_instanceID: key into client's JOURNALINSTANCE table.
|
||||
// Consumed by EncounterJournal_DisplayInstance / EJ_SelectInstance.
|
||||
uint32 ejInstanceId = 0;
|
||||
// WoW map id (for telemetry / teleport / fallback). NOT used by
|
||||
// the client to open EJ.
|
||||
uint32 mapId = 0;
|
||||
uint32 encounterId = 0;
|
||||
uint32 achievementId = 0;
|
||||
uint32 factionId = 0;
|
||||
uint32 professionId = 0;
|
||||
uint32 itemId = 0;
|
||||
uint32 currencyId = 0;
|
||||
};
|
||||
|
||||
struct Recommendation
|
||||
{
|
||||
uint32 id = 0;
|
||||
uint32 id = 0;
|
||||
std::string type;
|
||||
std::string title;
|
||||
std::string description;
|
||||
uint32 instanceId = 0;
|
||||
uint32 encounterId = 0;
|
||||
uint32 questId = 0;
|
||||
uint32 itemId = 0;
|
||||
uint32 priority = 0;
|
||||
uint32 ejInstanceId = 0; // JOURNALINSTANCE key
|
||||
uint32 mapId = 0; // WoW map id (auxiliary)
|
||||
uint32 encounterId = 0;
|
||||
uint32 questId = 0;
|
||||
uint32 itemId = 0;
|
||||
uint32 priority = 0;
|
||||
};
|
||||
|
||||
struct Reward
|
||||
@@ -105,6 +112,11 @@ namespace MoonWell::PlayerGuide
|
||||
bool LogPayloads() const { return _logPayloads; }
|
||||
uint32 ChunkSize() const { return _chunkSize; }
|
||||
|
||||
// Reloads (map_id -> ej_instance.id/name) cache from
|
||||
// custom_ej_instance. Called from LoadConfig() and from the
|
||||
// OnLoadCustomDatabaseTable world hook.
|
||||
void ReloadEncounterJournalCache();
|
||||
|
||||
private:
|
||||
Mgr() = default;
|
||||
|
||||
@@ -127,6 +139,15 @@ namespace MoonWell::PlayerGuide
|
||||
|
||||
void AppendRecommendations(Payload& out, Player* player) const;
|
||||
|
||||
struct EjInstanceRef
|
||||
{
|
||||
uint32 ejId = 0; // custom_ej_instance.id
|
||||
std::string name; // custom_ej_instance.name
|
||||
};
|
||||
|
||||
// mapId -> EJ instance ref. Filled at config load.
|
||||
std::unordered_map<uint32, EjInstanceRef> _ejByMapId;
|
||||
|
||||
bool _enabled = true;
|
||||
bool _logPayloads = false;
|
||||
uint32 _chunkSize = 230; // per-frame body capacity (addon channel)
|
||||
|
||||
@@ -164,7 +164,10 @@ namespace MoonWell::PlayerGuide
|
||||
public:
|
||||
PlayerGuide_WorldScript()
|
||||
: WorldScript("PlayerGuide_WorldScript",
|
||||
{ WORLDHOOK_ON_AFTER_CONFIG_LOAD }) { }
|
||||
{
|
||||
WORLDHOOK_ON_AFTER_CONFIG_LOAD,
|
||||
WORLDHOOK_ON_LOAD_CUSTOM_DATABASE_TABLE
|
||||
}) { }
|
||||
|
||||
void OnAfterConfigLoad(bool /*reload*/) override
|
||||
{
|
||||
@@ -173,6 +176,16 @@ namespace MoonWell::PlayerGuide
|
||||
">> PlayerGuide module: {}.",
|
||||
sPlayerGuideMgr->IsEnabled() ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
void OnLoadCustomDatabaseTable() override
|
||||
{
|
||||
// Re-read custom_ej_instance after the EJ loader has had
|
||||
// a chance to populate it. Order between modules on this
|
||||
// hook is undefined, but we are reading the table itself
|
||||
// (not EJ's in-memory cache) so we are fine as long as
|
||||
// the SQL has been applied.
|
||||
sPlayerGuideMgr->ReloadEncounterJournalCache();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user