Merge branch 'encounter-journal' into release
This commit is contained in:
@@ -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.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,8 +9,11 @@
|
|||||||
#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>
|
||||||
@@ -202,6 +205,45 @@ 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
|
||||||
@@ -258,14 +300,19 @@ 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 = title;
|
o.title = it->second.name.empty() ? title : it->second.name;
|
||||||
o.required = 1;
|
o.required = 1;
|
||||||
o.targetId = mapId;
|
o.targetId = it->second.ejId; // primary id for click
|
||||||
o.instanceId = mapId;
|
o.ejInstanceId = it->second.ejId; // JOURNALINSTANCE key
|
||||||
o.order = order;
|
o.mapId = mapId; // WoW map id (auxiliary)
|
||||||
|
o.order = order;
|
||||||
out.objectives.push_back(std::move(o));
|
out.objectives.push_back(std::move(o));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,14 +320,19 @@ 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 = title;
|
o.title = it->second.name.empty() ? title : it->second.name;
|
||||||
o.required = 1;
|
o.required = 1;
|
||||||
o.targetId = mapId;
|
o.targetId = it->second.ejId;
|
||||||
o.instanceId = mapId;
|
o.ejInstanceId = it->second.ejId;
|
||||||
o.order = order;
|
o.mapId = mapId;
|
||||||
|
o.order = order;
|
||||||
out.objectives.push_back(std::move(o));
|
out.objectives.push_back(std::move(o));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,18 +350,29 @@ 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 = d.title;
|
r.title = it->second.name.empty() ? d.title
|
||||||
r.instanceId = d.mapId;
|
: it->second.name;
|
||||||
r.priority = 100 -
|
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) -
|
static_cast<uint32>(std::abs(int32(level) -
|
||||||
int32((d.minLevel + d.maxLevel) / 2)));
|
int32((d.minLevel + d.maxLevel) / 2)));
|
||||||
r.description = "Подходит для вашего текущего этапа.";
|
r.description = "Подходит для вашего текущего этапа.";
|
||||||
out.recommendations.push_back(std::move(r));
|
out.recommendations.push_back(std::move(r));
|
||||||
|
|
||||||
if (out.recommendations.size() >= 6)
|
if (out.recommendations.size() >= 6)
|
||||||
@@ -496,15 +559,16 @@ namespace MoonWell::PlayerGuide
|
|||||||
WriteUInt(s, "targetID", o.targetId, first);
|
WriteUInt(s, "targetID", o.targetId, first);
|
||||||
WriteUInt(s, "order", o.order, first);
|
WriteUInt(s, "order", o.order, first);
|
||||||
|
|
||||||
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, "instanceID", o.instanceId, first);
|
WriteOptUInt(s, "ej_instanceID", o.ejInstanceId, first);
|
||||||
WriteOptUInt(s, "encounterID", o.encounterId, first);
|
WriteOptUInt(s, "mapID", o.mapId, first);
|
||||||
WriteOptUInt(s, "achievementID", o.achievementId, first);
|
WriteOptUInt(s, "encounterID", o.encounterId, first);
|
||||||
WriteOptUInt(s, "factionID", o.factionId, first);
|
WriteOptUInt(s, "achievementID", o.achievementId, first);
|
||||||
WriteOptUInt(s, "professionID", o.professionId, first);
|
WriteOptUInt(s, "factionID", o.factionId, first);
|
||||||
WriteOptUInt(s, "itemID", o.itemId, first);
|
WriteOptUInt(s, "professionID", o.professionId, first);
|
||||||
WriteOptUInt(s, "currencyID", o.currencyId, first);
|
WriteOptUInt(s, "itemID", o.itemId, first);
|
||||||
|
WriteOptUInt(s, "currencyID", o.currencyId, first);
|
||||||
s << '}';
|
s << '}';
|
||||||
return s.str();
|
return s.str();
|
||||||
}
|
}
|
||||||
@@ -551,10 +615,11 @@ 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, "instanceID", r.instanceId, f);
|
WriteOptUInt(s, "ej_instanceID", r.ejInstanceId, f);
|
||||||
WriteOptUInt(s, "encounterID", r.encounterId, f);
|
WriteOptUInt(s, "mapID", r.mapId, f);
|
||||||
WriteOptUInt(s, "questID", r.questId, f);
|
WriteOptUInt(s, "encounterID", r.encounterId, f);
|
||||||
WriteOptUInt(s, "itemID", r.itemId, f);
|
WriteOptUInt(s, "questID", r.questId, f);
|
||||||
|
WriteOptUInt(s, "itemID", r.itemId, f);
|
||||||
WriteUInt(s, "priority", r.priority, f);
|
WriteUInt(s, "priority", r.priority, f);
|
||||||
s << '}';
|
s << '}';
|
||||||
firstRec = false;
|
firstRec = false;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class Player;
|
class Player;
|
||||||
@@ -36,28 +37,34 @@ namespace MoonWell::PlayerGuide
|
|||||||
|
|
||||||
// Optional type-specific helpers. Zero means "not set" and
|
// Optional type-specific helpers. Zero means "not set" and
|
||||||
// serialiser omits it.
|
// serialiser omits it.
|
||||||
uint32 questId = 0;
|
uint32 questId = 0;
|
||||||
uint32 questChainId = 0;
|
uint32 questChainId = 0;
|
||||||
uint32 instanceId = 0;
|
// ej_instanceID: key into client's JOURNALINSTANCE table.
|
||||||
uint32 encounterId = 0;
|
// Consumed by EncounterJournal_DisplayInstance / EJ_SelectInstance.
|
||||||
uint32 achievementId = 0;
|
uint32 ejInstanceId = 0;
|
||||||
uint32 factionId = 0;
|
// WoW map id (for telemetry / teleport / fallback). NOT used by
|
||||||
uint32 professionId = 0;
|
// the client to open EJ.
|
||||||
uint32 itemId = 0;
|
uint32 mapId = 0;
|
||||||
uint32 currencyId = 0;
|
uint32 encounterId = 0;
|
||||||
|
uint32 achievementId = 0;
|
||||||
|
uint32 factionId = 0;
|
||||||
|
uint32 professionId = 0;
|
||||||
|
uint32 itemId = 0;
|
||||||
|
uint32 currencyId = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Recommendation
|
struct Recommendation
|
||||||
{
|
{
|
||||||
uint32 id = 0;
|
uint32 id = 0;
|
||||||
std::string type;
|
std::string type;
|
||||||
std::string title;
|
std::string title;
|
||||||
std::string description;
|
std::string description;
|
||||||
uint32 instanceId = 0;
|
uint32 ejInstanceId = 0; // JOURNALINSTANCE key
|
||||||
uint32 encounterId = 0;
|
uint32 mapId = 0; // WoW map id (auxiliary)
|
||||||
uint32 questId = 0;
|
uint32 encounterId = 0;
|
||||||
uint32 itemId = 0;
|
uint32 questId = 0;
|
||||||
uint32 priority = 0;
|
uint32 itemId = 0;
|
||||||
|
uint32 priority = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Reward
|
struct Reward
|
||||||
@@ -105,6 +112,11 @@ 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;
|
||||||
|
|
||||||
@@ -127,6 +139,15 @@ 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,7 +164,10 @@ 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
|
||||||
{
|
{
|
||||||
@@ -173,6 +176,16 @@ 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();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user