получение instance id для encounter journal

This commit is contained in:
2026-06-13 15:09:04 +04:00
parent 60bf86a9f7
commit ddee7e14ac
4 changed files with 151 additions and 51 deletions
+2 -1
View File
@@ -20,7 +20,8 @@
"Bash(python3 /home/sindo/moonwell-core/modules/mod-encounter-journal/tools/import_donor_journal.py -i /home/sindo/moonwell-core/Generated_EncounterJournal.lua -o /tmp/donor_import.sql)",
"Bash(python3 /home/sindo/moonwell-core/modules/mod-encounter-journal/tools/import_donor_journal.py -i /home/sindo/moonwell-core/Generated_EncounterJournal.lua -o /tmp/donor_import_filtered.sql --known-maps /tmp/known_maps.txt --known-creatures /tmp/known_creatures.txt --known-items /tmp/known_items.txt)",
"Bash(sudo grep -c \"Encounter Journal loaded\\\\|mod-encounter-journal\" /var/lib/docker/containers/8a3d7a23f170597b5a8eae754ef3259ac6f2af352d78958707ef5cf3010133e3/8a3d7a23f170597b5a8eae754ef3259ac6f2af352d78958707ef5cf3010133e3-json.log)",
"Bash(xargs -I {} stat -c '%y %n' {})"
"Bash(xargs -I {} stat -c '%y %n' {})",
"Bash(grep -n \"INSERT INTO \\\\`custom_ej_instance\\\\`\" /home/sindo/moonwell-core/modules/mod-encounter-journal/data/sql/db-world/base/02_encounter_journal_sample.sql)"
]
}
}
@@ -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,13 +300,18 @@ 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.title = it->second.name.empty() ? title : it->second.name;
o.required = 1;
o.targetId = mapId;
o.instanceId = mapId;
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,13 +320,18 @@ 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.title = it->second.name.empty() ? title : it->second.name;
o.required = 1;
o.targetId = mapId;
o.instanceId = mapId;
o.targetId = it->second.ejId;
o.ejInstanceId = it->second.ejId;
o.mapId = mapId;
o.order = order;
out.objectives.push_back(std::move(o));
}
@@ -298,14 +350,25 @@ 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.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)));
@@ -498,7 +561,8 @@ namespace MoonWell::PlayerGuide
WriteOptUInt(s, "questID", o.questId, first);
WriteOptUInt(s, "questChainID", o.questChainId, first);
WriteOptUInt(s, "instanceID", o.instanceId, 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);
@@ -551,7 +615,8 @@ 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, "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);
@@ -11,6 +11,7 @@
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
class Player;
@@ -38,7 +39,12 @@ namespace MoonWell::PlayerGuide
// serialiser omits it.
uint32 questId = 0;
uint32 questChainId = 0;
uint32 instanceId = 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;
@@ -53,7 +59,8 @@ namespace MoonWell::PlayerGuide
std::string type;
std::string title;
std::string description;
uint32 instanceId = 0;
uint32 ejInstanceId = 0; // JOURNALINSTANCE key
uint32 mapId = 0; // WoW map id (auxiliary)
uint32 encounterId = 0;
uint32 questId = 0;
uint32 itemId = 0;
@@ -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();
}
};
}