1 Commits

Author SHA1 Message Date
sindoring df180281e6 Merge branch 'encounter-journal' into release 2026-06-12 18:40:46 +04:00
4 changed files with 51 additions and 151 deletions
+1 -2
View File
@@ -20,8 +20,7 @@
"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.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(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(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,11 +9,8 @@
#include "../IndividualProgression.h" #include "../IndividualProgression.h"
#include "Config.h" #include "Config.h"
#include "DatabaseEnv.h"
#include "Field.h"
#include "Log.h" #include "Log.h"
#include "Player.h" #include "Player.h"
#include "QueryResult.h"
#include "QuestDef.h" #include "QuestDef.h"
#include <algorithm> #include <algorithm>
@@ -205,45 +202,6 @@ namespace MoonWell::PlayerGuide
// characters. We leave room for the framing header. // characters. We leave room for the framing header.
if (_chunkSize < 64) _chunkSize = 64; if (_chunkSize < 64) _chunkSize = 64;
if (_chunkSize > 240) _chunkSize = 240; 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 std::string Mgr::StageName(uint32 stage) const
@@ -300,18 +258,13 @@ namespace MoonWell::PlayerGuide
std::string const& title, std::string const& title,
uint32 order) const uint32 order) const
{ {
auto it = _ejByMapId.find(mapId);
if (it == _ejByMapId.end())
return; // no EJ entry -> skip rather than emit a stub.
Objective o; Objective o;
o.id = order; o.id = order;
o.type = "dungeon"; o.type = "dungeon";
o.title = it->second.name.empty() ? title : it->second.name; o.title = title;
o.required = 1; o.required = 1;
o.targetId = it->second.ejId; // primary id for click o.targetId = mapId;
o.ejInstanceId = it->second.ejId; // JOURNALINSTANCE key o.instanceId = mapId;
o.mapId = mapId; // WoW map id (auxiliary)
o.order = order; o.order = order;
out.objectives.push_back(std::move(o)); out.objectives.push_back(std::move(o));
} }
@@ -320,18 +273,13 @@ namespace MoonWell::PlayerGuide
std::string const& title, std::string const& title,
uint32 order) const uint32 order) const
{ {
auto it = _ejByMapId.find(mapId);
if (it == _ejByMapId.end())
return;
Objective o; Objective o;
o.id = order; o.id = order;
o.type = "raid"; o.type = "raid";
o.title = it->second.name.empty() ? title : it->second.name; o.title = title;
o.required = 1; o.required = 1;
o.targetId = it->second.ejId; o.targetId = mapId;
o.ejInstanceId = it->second.ejId; o.instanceId = mapId;
o.mapId = mapId;
o.order = order; o.order = order;
out.objectives.push_back(std::move(o)); out.objectives.push_back(std::move(o));
} }
@@ -350,25 +298,14 @@ namespace MoonWell::PlayerGuide
continue; continue;
if (level + 2 < d.minLevel || level > d.maxLevel + 1) if (level + 2 < d.minLevel || level > d.maxLevel + 1)
continue; 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; continue;
Recommendation r; Recommendation r;
r.id = recId++; r.id = recId++;
r.type = "dungeon"; r.type = "dungeon";
r.title = it->second.name.empty() ? d.title r.title = d.title;
: it->second.name; r.instanceId = d.mapId;
r.ejInstanceId = it->second.ejId; // JOURNALINSTANCE key
r.mapId = d.mapId; // WoW map id (auxiliary)
r.priority = 100 - r.priority = 100 -
static_cast<uint32>(std::abs(int32(level) - static_cast<uint32>(std::abs(int32(level) -
int32((d.minLevel + d.maxLevel) / 2))); int32((d.minLevel + d.maxLevel) / 2)));
@@ -561,8 +498,7 @@ namespace MoonWell::PlayerGuide
WriteOptUInt(s, "questID", o.questId, first); WriteOptUInt(s, "questID", o.questId, first);
WriteOptUInt(s, "questChainID", o.questChainId, first); WriteOptUInt(s, "questChainID", o.questChainId, first);
WriteOptUInt(s, "ej_instanceID", o.ejInstanceId, first); WriteOptUInt(s, "instanceID", o.instanceId, first);
WriteOptUInt(s, "mapID", o.mapId, first);
WriteOptUInt(s, "encounterID", o.encounterId, first); WriteOptUInt(s, "encounterID", o.encounterId, first);
WriteOptUInt(s, "achievementID", o.achievementId, first); WriteOptUInt(s, "achievementID", o.achievementId, first);
WriteOptUInt(s, "factionID", o.factionId, first); WriteOptUInt(s, "factionID", o.factionId, first);
@@ -615,8 +551,7 @@ namespace MoonWell::PlayerGuide
WriteString(s, "title", r.title, f); WriteString(s, "title", r.title, f);
if (!r.description.empty()) if (!r.description.empty())
WriteString(s, "description", r.description, f); WriteString(s, "description", r.description, f);
WriteOptUInt(s, "ej_instanceID", r.ejInstanceId, f); WriteOptUInt(s, "instanceID", r.instanceId, f);
WriteOptUInt(s, "mapID", r.mapId, f);
WriteOptUInt(s, "encounterID", r.encounterId, f); WriteOptUInt(s, "encounterID", r.encounterId, f);
WriteOptUInt(s, "questID", r.questId, f); WriteOptUInt(s, "questID", r.questId, f);
WriteOptUInt(s, "itemID", r.itemId, f); WriteOptUInt(s, "itemID", r.itemId, f);
@@ -11,7 +11,6 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <unordered_map>
#include <vector> #include <vector>
class Player; class Player;
@@ -39,12 +38,7 @@ namespace MoonWell::PlayerGuide
// serialiser omits it. // serialiser omits it.
uint32 questId = 0; uint32 questId = 0;
uint32 questChainId = 0; uint32 questChainId = 0;
// ej_instanceID: key into client's JOURNALINSTANCE table. uint32 instanceId = 0;
// 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 encounterId = 0;
uint32 achievementId = 0; uint32 achievementId = 0;
uint32 factionId = 0; uint32 factionId = 0;
@@ -59,8 +53,7 @@ namespace MoonWell::PlayerGuide
std::string type; std::string type;
std::string title; std::string title;
std::string description; std::string description;
uint32 ejInstanceId = 0; // JOURNALINSTANCE key uint32 instanceId = 0;
uint32 mapId = 0; // WoW map id (auxiliary)
uint32 encounterId = 0; uint32 encounterId = 0;
uint32 questId = 0; uint32 questId = 0;
uint32 itemId = 0; uint32 itemId = 0;
@@ -112,11 +105,6 @@ namespace MoonWell::PlayerGuide
bool LogPayloads() const { return _logPayloads; } bool LogPayloads() const { return _logPayloads; }
uint32 ChunkSize() const { return _chunkSize; } 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: private:
Mgr() = default; Mgr() = default;
@@ -139,15 +127,6 @@ namespace MoonWell::PlayerGuide
void AppendRecommendations(Payload& out, Player* player) const; 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 _enabled = true;
bool _logPayloads = false; bool _logPayloads = false;
uint32 _chunkSize = 230; // per-frame body capacity (addon channel) uint32 _chunkSize = 230; // per-frame body capacity (addon channel)
@@ -164,10 +164,7 @@ namespace MoonWell::PlayerGuide
public: public:
PlayerGuide_WorldScript() PlayerGuide_WorldScript()
: WorldScript("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 void OnAfterConfigLoad(bool /*reload*/) override
{ {
@@ -176,16 +173,6 @@ namespace MoonWell::PlayerGuide
">> PlayerGuide module: {}.", ">> PlayerGuide module: {}.",
sPlayerGuideMgr->IsEnabled() ? "enabled" : "disabled"); 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();
}
}; };
} }