Парсер файлов модов

This commit is contained in:
2025-08-02 18:28:09 +06:00
parent a8b6647fac
commit 24c133b2f7

View File

@@ -14,6 +14,7 @@
#include <iterator>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>
#include <unordered_map>
@@ -22,8 +23,10 @@
#include "Server/SaveBackend.hpp"
#include "Server/World.hpp"
#include "TOSLib.hpp"
#include "boost/json/array.hpp"
#include "boost/json/object.hpp"
#include "boost/json/parse_into.hpp"
#include "boost/json/serialize.hpp"
#include "glm/gtc/noise.hpp"
#include <fstream>
@@ -49,17 +52,58 @@ int luaAtException(lua_State* L, sol::optional<const std::exception&> exc, std::
namespace LV::Server {
struct ModeDepend {
struct ModDepend {
std::string Id;
uint32_t VersionMin[4], VersionMax[4];
};
struct ModInfo {
std::string Id, Name, Description;
std::string Id, Name, Description, Author;
uint32_t Version[4];
std::vector<ModeDepend> Dependency, Optional;
std::vector<ModDepend> Dependencies, Optional;
float LoadPriority;
fs::path Path;
std::string dump() const {
js::object obj;
obj["id"] = Id;
obj["name"] = Name;
obj["description"] = Description;
obj["author"] = Author;
obj["version"] = {Version[0], Version[1], Version[2], Version[3]};
{
js::array arr;
for(const auto& depend : Dependencies) {
js::object obj;
obj["id"] = depend.Id;
obj["version_min"] = {depend.VersionMin[0], depend.VersionMin[1], depend.VersionMin[2], depend.VersionMin[3]};
obj["version_max"] = {depend.VersionMax[0], depend.VersionMax[1], depend.VersionMax[2], depend.VersionMax[3]};
arr.push_back(obj);
}
obj["depend"] = arr;
}
{
js::array arr;
for(const auto& depend : Optional) {
js::object obj;
obj["id"] = depend.Id;
obj["version_min"] = {depend.VersionMin[0], depend.VersionMin[1], depend.VersionMin[2], depend.VersionMin[3]};
obj["version_max"] = {depend.VersionMax[0], depend.VersionMax[1], depend.VersionMax[2], depend.VersionMax[3]};
arr.push_back(obj);
}
obj["optional_depend"] = arr;
}
obj["load_priority"] = LoadPriority;
obj["path"] = Path.string();
return js::serialize(obj);
}
};
struct ModPreloadInfo {
@@ -71,7 +115,6 @@ ModPreloadInfo preLoadMods(const std::vector<fs::path>& dirs) {
std::vector<ModInfo> mods;
std::vector<std::string> errors;
for(const fs::path& p : dirs) {
try {
if(!fs::is_directory(p))
@@ -109,7 +152,96 @@ ModPreloadInfo preLoadMods(const std::vector<fs::path>& dirs) {
try {
js::object obj = js::parse(data).as_object();
ModInfo info;
info.Id = obj.at("id").as_string();
info.Name = obj.contains("title") ? obj["title"].as_string() : "";
info.Description = obj.contains("description") ? obj["description"].as_string() : "";
info.Author = obj.contains("author") ? obj["author"].as_string() : "";
{
js::array version = obj.at("version").as_array();
for(int iter = 0; iter < 4; iter++)
info.Version[iter] = version.at(iter).as_int64();
}
if(obj.contains("depends")) {
js::array arr = obj["depends"].as_array();
for(auto& iter : arr) {
ModDepend depend;
if(iter.is_string()) {
depend.Id = iter.as_string();
std::fill(depend.VersionMin, depend.VersionMin+4, 0);
std::fill(depend.VersionMax, depend.VersionMax+4, uint32_t(-1));
} else if(iter.is_object()) {
js::object d = iter.as_object();
depend.Id = d.at("id").as_string();
if(d.contains("version_min")) {
js::array v = d.at("version_min").as_array();
for(int iter = 0; iter < 4; iter++) {
depend.VersionMin[iter] = v.at(iter).as_int64();
}
} else
std::fill(depend.VersionMin, depend.VersionMin+4, 0);
if(d.contains("version_max")) {
js::array v = d.at("version_max").as_array();
for(int iter = 0; iter < 4; iter++) {
depend.VersionMax[iter] = v.at(iter).as_int64();
}
} else
std::fill(depend.VersionMax, depend.VersionMax+4, uint32_t(-1));
}
info.Dependencies.push_back(depend);
}
}
if(obj.contains("optional_depends")) {
js::array arr = obj["optional_depends"].as_array();
for(auto& iter : arr) {
ModDepend depend;
if(iter.is_string()) {
depend.Id = iter.as_string();
std::fill(depend.VersionMin, depend.VersionMin+4, 0);
std::fill(depend.VersionMax, depend.VersionMax+4, uint32_t(-1));
} else if(iter.is_object()) {
js::object d = iter.as_object();
depend.Id = d.at("id").as_string();
if(d.contains("version_min")) {
js::array v = d.at("version_min").as_array();
for(int iter = 0; iter < 4; iter++) {
depend.VersionMin[iter] = v.at(iter).as_int64();
}
} else
std::fill(depend.VersionMin, depend.VersionMin+4, 0);
if(d.contains("version_max")) {
js::array v = d.at("version_max").as_array();
for(int iter = 0; iter < 4; iter++) {
depend.VersionMax[iter] = v.at(iter).as_int64();
}
} else
std::fill(depend.VersionMax, depend.VersionMax+4, uint32_t(-1));
}
info.Optional.push_back(depend);
}
}
if(obj.contains("load_priority")) {
info.LoadPriority = obj.at("load_priority").as_double();
} else {
info.LoadPriority = 0.5;
}
info.Path = modPath;
mods.push_back(info);
} catch (const std::exception& exc) {
errors.push_back("Не удалось распарсить mod.json '" + modPath.string() + "': " + exc.what());
@@ -124,6 +256,107 @@ ModPreloadInfo preLoadMods(const std::vector<fs::path>& dirs) {
errors.push_back("Неопределённая ошибка при работе с директорией: " + p.string());
}
}
return {mods, errors};
}
struct ModLoadTree {
std::vector<ModInfo> UnloadChain, LoadChain;
};
std::variant<ModLoadTree, std::vector<std::string>> buildLoadChain(const std::vector<ModInfo>& loaded, const std::vector<ModInfo>& toUnload, const std::vector<ModInfo>& toLoad) {
// Проверить обязательные зависимости в конечном состоянии
{
std::vector<std::string> errors;
std::vector<ModInfo> endState;
for(const ModInfo& lmod : loaded) {
bool contains = false;
for(const ModInfo& umod : toUnload) {
if(lmod.Id == umod.Id) {
contains = true;
break;
}
}
if(!contains)
endState.push_back(lmod);
}
endState.insert(endState.end(), toLoad.begin(), toLoad.end());
for(const ModInfo& mmod : endState) {
for(const ModDepend& depend : mmod.Dependencies) {
std::vector<std::string> lerrors;
bool contains = false;
for(const ModInfo& mmod2 : endState) {
if(depend.Id != mmod2.Id)
continue;
if(depend.VersionMin[0] > mmod2.Version[0]
|| depend.VersionMin[1] > mmod2.Version[1]
|| depend.VersionMin[2] > mmod2.Version[2]
|| depend.VersionMin[3] > mmod2.Version[3]
) {
goto versionMismatch;
}
if(depend.VersionMax[0] != uint32_t(-1)) {
if(depend.VersionMax[0] < mmod2.Version[0]
|| depend.VersionMax[1] < mmod2.Version[1]
|| depend.VersionMax[2] < mmod2.Version[2]
|| depend.VersionMax[3] < mmod2.Version[3]
) {
goto versionMismatch;
}
}
contains = true;
continue;
versionMismatch:
std::stringstream ss;
ss << depend.VersionMin[0] << '.';
ss << depend.VersionMin[1] << '.';
ss << depend.VersionMin[2] << '.';
ss << depend.VersionMin[3];
std::string verMin = ss.str();
ss.str("");
if(depend.VersionMax[0] != uint32_t(-1)) {
ss << depend.VersionMax[0] << '.';
ss << (depend.VersionMax[1] == uint32_t(-1) ? "*" : std::to_string(depend.VersionMax[1])) << '.';
ss << (depend.VersionMax[2] == uint32_t(-1) ? "*" : std::to_string(depend.VersionMax[2])) << '.';
ss << (depend.VersionMax[3] == uint32_t(-1) ? "*" : std::to_string(depend.VersionMax[3])) << '.';
verMin += " -> " + ss.str();
ss.str("");
}
ss << mmod2.Version[0] << '.';
ss << mmod2.Version[1] << '.';
ss << mmod2.Version[2] << '.';
ss << mmod2.Version[3];
std::string ver = ss.str();
ss.str("");
lerrors.push_back("Мод " + mmod.Name+"("+mmod.Id+") требует "+mmod2.Name+"("+mmod2.Id+", "+verMin+"), найдена версия " + ver);
}
if(!contains)
errors.insert(errors.end(), lerrors.begin(), lerrors.end());
}
}
if(!errors.empty())
return errors;
}
std::vector<ModInfo> unloadChain, loadChain;
return ModLoadTree{unloadChain, loadChain};
}
GameServer::GameServer(asio::io_context &ioc, fs::path worldPath)
@@ -151,22 +384,24 @@ GameServer::GameServer(asio::io_context &ioc, fs::path worldPath)
// Тест луа
sol::state lua;
// lua.open_libraries();
// lua.set_panic(luaPanic);
lua.set_exception_handler(luaAtException);
// sol::state lua;
// lua.set_exception_handler(luaAtException);
sol::load_result res = lua.load_file("/home/mr_s/Workspace/Alpha/LuaVox/Work/mods/init.lua");
sol::function func = res.call<>();
int type = func();
// lua.script(R"(
// sol::load_result res = lua.load_file("/home/mr_s/Workspace/Alpha/LuaVox/Work/mods/init.lua");
// sol::function func = res.call<>();
// int type = func();
// )");
LOG.debug() << type;
// LOG.debug() << type;
fs::path mods = "mods";
auto [info, errors] = preLoadMods({mods});
for(const auto& mod : info) {
LOG.debug() << mod.dump();
}
for(const std::string& error : errors) {
LOG.warn() << error;
}
}
GameServer::~GameServer() {
@@ -1049,60 +1284,6 @@ void GameServer::run() {
LOG.info() << "Сервер завершил работу";
}
std::vector<GameServer::ModInfo> GameServer::readModDataPath(const fs::path& modsDir) {
if(!fs::exists(modsDir))
return {};
std::vector<GameServer::ModInfo> infos;
try {
fs::directory_iterator begin(modsDir), end;
for(; begin != end; begin++) {
if(!begin->is_directory())
continue;
fs::path mod_conf = begin->path() / "mod.json";
if(!fs::exists(mod_conf)) {
LOG.debug() << "Директория в папке с модами не содержит файл mod.json: " << begin->path().filename();
continue;
}
try {
std::ifstream fd(mod_conf);
js::object obj = js::parse(fd).as_object();
GameServer::ModInfo info;
info.Id = obj.at("Id").as_string();
info.Title = obj.contains("Title") ? obj["Title"].as_string() : "";
info.Description = obj.contains("Description") ? obj["Description"].as_string() : "";
if(obj.contains("Dependencies")) {
js::array arr = obj["Dependencies"].as_array();
for(auto& iter : arr) {
info.Dependencies.push_back((std::string) iter.as_string());
}
}
if(obj.contains("OptionalDependencies")) {
js::array arr = obj["OptionalDependencies"].as_array();
for(auto& iter : arr) {
info.OptionalDependencies.push_back((std::string) iter.as_string());
}
}
} catch(const std::exception &exc) {
LOG.warn() << "Не удалось прочитать " << mod_conf.string();
}
}
} catch(const std::exception &exc) {
LOG.warn() << "Не удалось прочитать моды из директории " << modsDir.string() << "\n" << exc.what();
}
return infos;
}
void GameServer::stepConnections() {
// Подключить новых игроков
if(!External.NewConnectedPlayers.no_lock_readable().empty()) {