[Quests] Support Multiple Quest, Plugin, and Lua Module Paths (#4906)

* [Quests] Add Support for Multiple Load Paths

* Adjust load paths

* plugin != m_lua_module_directories
This commit is contained in:
Chris Miles 2025-06-09 12:49:46 -05:00 committed by GitHub
parent 3d70063a68
commit befee1c729
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 471 additions and 374 deletions

View File

@ -177,6 +177,21 @@ void EQEmuConfig::parse_config()
SharedMemDir = _root["server"]["directories"].get("shared_memory", "shared/").asString();
LogDir = _root["server"]["directories"].get("logs", "logs/").asString();
auto load_paths = [&](const std::string& key, std::vector<std::string>& target) {
const auto& paths = _root["server"]["directories"][key];
if (paths.isArray()) {
for (const auto& dir : paths) {
if (dir.isString()) {
target.push_back(dir.asString());
}
}
}
};
load_paths("quest_paths", m_quest_directories);
load_paths("plugin_paths", m_plugin_directories);
load_paths("lua_module_paths", m_lua_module_directories);
/**
* Logs
*/

View File

@ -120,6 +120,22 @@ class EQEmuConfig
const std::string &GetUCSHost() const;
uint16 GetUCSPort() const;
std::vector<std::string> GetQuestDirectories() const
{
return m_quest_directories;
}
std::vector<std::string> GetPluginsDirectories() const
{
return m_plugin_directories;
}
std::vector<std::string> GetLuaModuleDirectories() const
{
return m_lua_module_directories;
}
// uint16 DynamicCount;
// map<string,uint16> StaticZones;
@ -133,6 +149,11 @@ class EQEmuConfig
Json::Value _root;
static std::string ConfigFile;
std::vector<std::string> m_quest_directories = {};
std::vector<std::string> m_plugin_directories = {};
std::vector<std::string> m_lua_module_directories = {};
protected:
void parse_config();
EQEmuConfig()

View File

@ -48,10 +48,23 @@ void PathManager::LoadPaths()
return dir;
};
auto load_many_paths_fallback = [&](const std::vector<std::string>& dirs, const std::string& fallback, std::vector<std::string>& target) {
target.clear();
if (!dirs.empty()) {
for (const auto& path : dirs) {
target.push_back(resolve_path(path));
}
} else {
target.push_back(resolve_path(fallback));
}
};
load_many_paths_fallback(c->GetQuestDirectories(), c->QuestDir, m_quests_paths);
load_many_paths_fallback(c->GetPluginsDirectories(), c->PluginDir, m_plugin_paths);
load_many_paths_fallback(c->GetLuaModuleDirectories(), c->LuaModuleDir, m_lua_module_paths);
// resolve all paths
m_maps_path = resolve_path(c->MapDir, {"maps", "Maps"});
m_quests_path = resolve_path(c->QuestDir);
m_plugins_path = resolve_path(c->PluginDir);
m_lua_modules_path = resolve_path(c->LuaModuleDir);
m_lua_mods_path = resolve_path("mods");
m_patch_path = resolve_path(c->PatchDir);
m_opcode_path = resolve_path(c->OpcodeDir);
@ -62,13 +75,10 @@ void PathManager::LoadPaths()
std::vector<std::pair<std::string, std::string>> paths = {
{"server", m_server_path},
{"logs", m_log_path},
{"lua mods", m_lua_mods_path},
{"lua_modules", m_lua_modules_path},
{"maps", m_maps_path},
{"lua mods", m_lua_mods_path},
{"patches", m_patch_path},
{"opcode", m_opcode_path},
{"plugins", m_plugins_path},
{"quests", m_quests_path},
{"shared_memory", m_shared_memory_path}
};
@ -83,6 +93,17 @@ void PathManager::LoadPaths()
LogInfo("{:>{}} > [{:<{}}]", name, name_width, in_path, path_width);
}
}
auto log_paths = [&](const std::string& label, const std::vector<std::string>& paths) {
if (!paths.empty()) {
LogInfo("{:>{}} > [{:<{}}]", label, name_width - 1, Strings::Join(paths, ";"), path_width);
}
};
log_paths("quests", m_quests_paths);
log_paths("plugins", m_plugin_paths);
log_paths("lua_modules", m_lua_module_paths);
LogInfo("{}", Strings::Repeat("-", break_length));
}
@ -96,21 +117,26 @@ const std::string &PathManager::GetMapsPath() const
return m_maps_path;
}
const std::string &PathManager::GetQuestsPath() const
{
return m_quests_path;
}
const std::string &PathManager::GetPluginsPath() const
{
return m_plugins_path;
}
const std::string &PathManager::GetSharedMemoryPath() const
{
return m_shared_memory_path;
}
std::vector<std::string> PathManager::GetQuestPaths() const
{
return m_quests_paths;
}
std::vector<std::string> PathManager::GetPluginPaths() const
{
return m_plugin_paths;
}
std::vector<std::string> PathManager::GetLuaModulePaths() const
{
return m_lua_module_paths;
}
const std::string &PathManager::GetLogPath() const
{
return m_log_path;
@ -126,11 +152,6 @@ const std::string &PathManager::GetOpcodePath() const
return m_opcode_path;
}
const std::string &PathManager::GetLuaModulesPath() const
{
return m_lua_modules_path;
}
const std::string &PathManager::GetLuaModsPath() const
{
return m_lua_mods_path;

View File

@ -3,6 +3,7 @@
#include <string>
#include <vector>
class PathManager {
public:
@ -14,20 +15,25 @@ public:
[[nodiscard]] const std::string &GetMapsPath() const;
[[nodiscard]] const std::string &GetPatchPath() const;
[[nodiscard]] const std::string &GetOpcodePath() const;
[[nodiscard]] const std::string &GetPluginsPath() const;
[[nodiscard]] const std::string &GetQuestsPath() const;
[[nodiscard]] const std::string &GetServerPath() const;
[[nodiscard]] const std::string &GetSharedMemoryPath() const;
[[nodiscard]] std::vector<std::string> GetQuestPaths() const;
[[nodiscard]] std::vector<std::string> GetPluginPaths() const;
[[nodiscard]] std::vector<std::string> GetLuaModulePaths() const;
private:
std::string m_log_path;
std::string m_lua_mods_path;
std::string m_lua_modules_path;
std::string m_maps_path;
std::string m_patch_path;
std::string m_opcode_path;
std::string m_plugins_path;
std::string m_quests_path;
std::vector<std::string> m_quests_paths;
std::vector<std::string> m_plugin_paths;
std::vector<std::string> m_lua_module_paths;
private:
std::string m_server_path;
std::string m_shared_memory_path;
};

View File

@ -137,18 +137,20 @@ void Embperl::DoInit()
catch (std::string& e) {
LogQuests("Warning [{}]: [{}]", Config->PluginPlFile, e);
}
for (auto & dir : path.GetPluginPaths()) {
try {
//should probably read the directory in c, instead, so that
//I can echo filenames as I do it, but c'mon... I'm lazy and this 1 line reads in all the plugins
const std::string& perl_command = (
"if(opendir(D,'" +
path.GetPluginsPath() +
dir +
"')) { "
" my @d = readdir(D);"
" closedir(D);"
" foreach(@d){ "
" main::eval_file('plugin','" +
path.GetPluginsPath() +
dir +
"/'.$_)if/\\.pl$/;"
" }"
"}");
@ -157,6 +159,7 @@ void Embperl::DoInit()
catch (std::string& e) {
LogQuests("Warning [{}]", e);
}
}
#endif //EMBPERL_PLUGIN
}

View File

@ -1068,12 +1068,15 @@ void LuaParser::ReloadQuests() {
lua_getglobal(L, "package");
lua_getfield(L, -1, "path");
std::string module_path = lua_tostring(L, -1);
module_path += ";" + path.GetLuaModulesPath() + "/?.lua;" + path.GetLuaModulesPath() + "/?/init.lua";
// luarock paths using lua_modules as tree
// to path it adds foo/share/lua/5.1/?.lua and foo/share/lua/5.1/?/init.lua
module_path += ";" + path.GetLuaModulesPath() + "/share/lua/" + lua_version + "/?.lua";
module_path += ";" + path.GetLuaModulesPath() + "/share/lua/" + lua_version + "/?/init.lua";
lua_pop(L, 1);
for (const auto& dir : path.GetLuaModulePaths()) {
module_path += fmt::format(";{}/?.lua", dir);
module_path += fmt::format(";{}/?/init.lua", dir);
module_path += fmt::format(";{}/share/lua/{}/?.lua", dir, lua_version);
module_path += fmt::format(";{}/share/lua/{}/?/init.lua", dir, lua_version);
}
lua_pushstring(L, module_path.c_str());
lua_setfield(L, -2, "path");
lua_pop(L, 1);
@ -1081,11 +1084,13 @@ void LuaParser::ReloadQuests() {
lua_getglobal(L, "package");
lua_getfield(L, -1, "cpath");
module_path = lua_tostring(L, -1);
module_path += ";" + path.GetLuaModulesPath() + "/?" + libext;
// luarock paths using lua_modules as tree
// luarocks adds foo/lib/lua/5.1/?.so for cpath
module_path += ";" + path.GetLuaModulesPath() + "/lib/lua/" + lua_version + "/?" + libext;
lua_pop(L, 1);
for (const auto& dir : path.GetLuaModulePaths()) {
module_path += fmt::format(";{}/?{}", dir, libext);
module_path += fmt::format(";{}/lib/lua/{}/?{}", dir, lua_version, libext);
}
lua_pushstring(L, module_path.c_str());
lua_setfield(L, -2, "cpath");
lua_pop(L, 1);
@ -1093,7 +1098,8 @@ void LuaParser::ReloadQuests() {
MapFunctions(L);
// load init
std::string filename = fmt::format("{}/{}/script_init.lua", path.GetQuestsPath(), QUEST_GLOBAL_DIRECTORY);
for (auto& dir : path.GetQuestPaths()) {
std::string filename = fmt::format("{}/{}/script_init.lua", dir, QUEST_GLOBAL_DIRECTORY);
FILE* f = fopen(filename.c_str(), "r");
if (f) {
@ -1104,17 +1110,19 @@ void LuaParser::ReloadQuests() {
AddError(error);
}
}
}
//zone init - always loads after global
if (zone) {
for (auto& dir : path.GetQuestPaths()) {
std::string zone_script = fmt::format(
"{}/{}/script_init_v{}.lua",
path.GetQuestsPath(),
dir,
zone->GetShortName(),
zone->GetInstanceVersion()
);
f = fopen(zone_script.c_str(), "r");
FILE* f = fopen(zone_script.c_str(), "r");
if (f) {
fclose(f);
@ -1124,7 +1132,7 @@ void LuaParser::ReloadQuests() {
}
}
else {
zone_script = fmt::format("{}/{}/script_init.lua", path.GetQuestsPath(), zone->GetShortName());
zone_script = fmt::format("{}/{}/script_init.lua", dir, zone->GetShortName());
f = fopen(zone_script.c_str(), "r");
if (f) {
@ -1137,6 +1145,7 @@ void LuaParser::ReloadQuests() {
}
}
}
}
FILE *load_order = fopen(fmt::format("{}/load_order.txt", path.GetLuaModsPath()).c_str(), "r");
if (load_order) {

View File

@ -939,6 +939,7 @@ QuestInterface* QuestParserCollection::GetQIByNPCQuest(uint32 npc_id, std::strin
Strings::FindReplace(npc_name, "`", "-");
for (auto & dir : path.GetQuestPaths()) {
const std::string& npc_id_and_name = fmt::format(
"{}_{}",
npc_name,
@ -947,19 +948,19 @@ QuestInterface* QuestParserCollection::GetQIByNPCQuest(uint32 npc_id, std::strin
const std::string& global_path = fmt::format(
"{}/{}",
path.GetQuestsPath(),
dir,
QUEST_GLOBAL_DIRECTORY
);
const std::string& zone_path = fmt::format(
"{}/{}",
path.GetQuestsPath(),
dir,
zone->GetShortName()
);
const std::string& zone_versioned_path = fmt::format(
"{}/{}/v{}",
path.GetQuestsPath(),
dir,
zone->GetShortName(),
zone->GetInstanceVersion()
);
@ -995,6 +996,7 @@ QuestInterface* QuestParserCollection::GetQIByNPCQuest(uint32 npc_id, std::strin
}
}
}
}
return nullptr;
}
@ -1005,21 +1007,22 @@ QuestInterface* QuestParserCollection::GetQIByPlayerQuest(std::string& filename)
return nullptr;
}
for (auto & dir : path.GetQuestPaths()) {
const std::string& global_path = fmt::format(
"{}/{}",
path.GetQuestsPath(),
dir,
QUEST_GLOBAL_DIRECTORY
);
const std::string& zone_path = fmt::format(
"{}/{}",
path.GetQuestsPath(),
dir,
zone->GetShortName()
);
const std::string& zone_versioned_path = fmt::format(
"{}/{}/v{}",
path.GetQuestsPath(),
dir,
zone->GetShortName(),
zone->GetInstanceVersion()
);
@ -1046,6 +1049,7 @@ QuestInterface* QuestParserCollection::GetQIByPlayerQuest(std::string& filename)
}
}
}
}
return nullptr;
}
@ -1058,10 +1062,11 @@ QuestInterface* QuestParserCollection::GetQIByGlobalNPCQuest(std::string& filena
std::string file_name;
for (auto & dir : path.GetQuestPaths()) {
for (auto* e: _load_precedence) {
file_name = fmt::format(
"{}/{}/global_npc.{}",
path.GetQuestsPath(),
dir,
QUEST_GLOBAL_DIRECTORY,
_extensions.find(e->GetIdentifier())->second
);
@ -1071,6 +1076,7 @@ QuestInterface* QuestParserCollection::GetQIByGlobalNPCQuest(std::string& filena
return e;
}
}
}
return nullptr;
}
@ -1082,10 +1088,11 @@ QuestInterface* QuestParserCollection::GetQIByGlobalPlayerQuest(std::string& fil
}
std::string file_name;
for (auto & dir : path.GetQuestPaths()) {
for (auto* e: _load_precedence) {
file_name = fmt::format(
"{}/{}/global_player.{}",
path.GetQuestsPath(),
dir,
QUEST_GLOBAL_DIRECTORY,
_extensions.find(e->GetIdentifier())->second
);
@ -1095,6 +1102,7 @@ QuestInterface* QuestParserCollection::GetQIByGlobalPlayerQuest(std::string& fil
return e;
}
}
}
return nullptr;
}
@ -1105,21 +1113,22 @@ QuestInterface* QuestParserCollection::GetQIBySpellQuest(uint32 spell_id, std::s
return nullptr;
}
for (auto & dir : path.GetQuestPaths()) {
const std::string& global_path = fmt::format(
"{}/{}/spells",
path.GetQuestsPath(),
dir,
QUEST_GLOBAL_DIRECTORY
);
const std::string& zone_path = fmt::format(
"{}/{}/spells",
path.GetQuestsPath(),
dir,
zone->GetShortName()
);
const std::string& zone_versioned_path = fmt::format(
"{}/{}/v{}/spells",
path.GetQuestsPath(),
dir,
zone->GetShortName(),
zone->GetInstanceVersion()
);
@ -1147,6 +1156,7 @@ QuestInterface* QuestParserCollection::GetQIBySpellQuest(uint32 spell_id, std::s
}
}
}
}
return nullptr;
}
@ -1157,21 +1167,22 @@ QuestInterface* QuestParserCollection::GetQIByItemQuest(std::string item_script,
return nullptr;
}
for (auto & dir : path.GetQuestPaths()) {
const std::string& global_path = fmt::format(
"{}/{}/items",
path.GetQuestsPath(),
dir,
QUEST_GLOBAL_DIRECTORY
);
const std::string& zone_path = fmt::format(
"{}/{}/items",
path.GetQuestsPath(),
dir,
zone->GetShortName()
);
const std::string& zone_versioned_path = fmt::format(
"{}/{}/v{}/items",
path.GetQuestsPath(),
dir,
zone->GetShortName(),
zone->GetInstanceVersion()
);
@ -1199,6 +1210,7 @@ QuestInterface* QuestParserCollection::GetQIByItemQuest(std::string item_script,
}
}
}
}
return nullptr;
}
@ -1209,21 +1221,22 @@ QuestInterface* QuestParserCollection::GetQIByEncounterQuest(std::string encount
return nullptr;
}
for (auto & dir : path.GetQuestPaths()) {
const std::string& global_path = fmt::format(
"{}/{}/encounters",
path.GetQuestsPath(),
dir,
QUEST_GLOBAL_DIRECTORY
);
const std::string& zone_path = fmt::format(
"{}/{}/encounters",
path.GetQuestsPath(),
dir,
zone->GetShortName()
);
const std::string& zone_versioned_path = fmt::format(
"{}/{}/v{}/encounters",
path.GetQuestsPath(),
dir,
zone->GetShortName(),
zone->GetInstanceVersion()
);
@ -1249,6 +1262,7 @@ QuestInterface* QuestParserCollection::GetQIByEncounterQuest(std::string encount
}
}
}
}
return nullptr;
}
@ -1259,21 +1273,22 @@ QuestInterface* QuestParserCollection::GetQIByBotQuest(std::string& filename)
return nullptr;
}
for (auto & dir : path.GetQuestPaths()) {
const std::string& global_path = fmt::format(
"{}/{}",
path.GetQuestsPath(),
dir,
QUEST_GLOBAL_DIRECTORY
);
const std::string& zone_path = fmt::format(
"{}/{}",
path.GetQuestsPath(),
dir,
zone->GetShortName()
);
const std::string& zone_versioned_path = fmt::format(
"{}/{}/v{}",
path.GetQuestsPath(),
dir,
zone->GetShortName(),
zone->GetInstanceVersion()
);
@ -1300,6 +1315,7 @@ QuestInterface* QuestParserCollection::GetQIByBotQuest(std::string& filename)
}
}
}
}
return nullptr;
}
@ -1311,10 +1327,11 @@ QuestInterface* QuestParserCollection::GetQIByGlobalBotQuest(std::string& filena
}
std::string file_name;
for (auto & dir : path.GetQuestPaths()) {
for (auto* e: _load_precedence) {
file_name = fmt::format(
"{}/{}/global_bot.{}",
path.GetQuestsPath(),
dir,
QUEST_GLOBAL_DIRECTORY,
_extensions.find(e->GetIdentifier())->second
);
@ -1324,6 +1341,7 @@ QuestInterface* QuestParserCollection::GetQIByGlobalBotQuest(std::string& filena
return e;
}
}
}
return nullptr;
}
@ -1334,21 +1352,22 @@ QuestInterface* QuestParserCollection::GetQIByMercQuest(std::string& filename)
return nullptr;
}
for (auto & dir : path.GetQuestPaths()) {
const std::string& global_path = fmt::format(
"{}/{}",
path.GetQuestsPath(),
dir,
QUEST_GLOBAL_DIRECTORY
);
const std::string& zone_path = fmt::format(
"{}/{}",
path.GetQuestsPath(),
dir,
zone->GetShortName()
);
const std::string& zone_versioned_path = fmt::format(
"{}/{}/v{}",
path.GetQuestsPath(),
dir,
zone->GetShortName(),
zone->GetInstanceVersion()
);
@ -1375,6 +1394,7 @@ QuestInterface* QuestParserCollection::GetQIByMercQuest(std::string& filename)
}
}
}
}
return nullptr;
}
@ -1386,10 +1406,11 @@ QuestInterface* QuestParserCollection::GetQIByGlobalMercQuest(std::string& filen
}
std::string file_name;
for (auto & dir : path.GetQuestPaths()) {
for (auto* e: _load_precedence) {
file_name = fmt::format(
"{}/{}/global_merc.{}",
path.GetQuestsPath(),
dir,
QUEST_GLOBAL_DIRECTORY,
_extensions.find(e->GetIdentifier())->second
);
@ -1399,6 +1420,7 @@ QuestInterface* QuestParserCollection::GetQIByGlobalMercQuest(std::string& filen
return e;
}
}
}
return nullptr;
}