diff --git a/examples/mods/aim1_community_fix/my_mod.cpp b/examples/mods/aim1_community_fix/my_mod.cpp index e46681f..2e71f83 100644 --- a/examples/mods/aim1_community_fix/my_mod.cpp +++ b/examples/mods/aim1_community_fix/my_mod.cpp @@ -143,7 +143,7 @@ int main(int argc, char *argv[]) { // patch note: Database Changes // patch note: DB - auto db = mod.db(); + auto &db = mod.db(); // patch note: set glider GL_S3_PS_FINDER2 model to MOD_GL_S3_PS_FINDER2 (lz) db["Глайдеры"]["GL_S3_PS_FINDER2"]["MODEL"] = "MOD_GL_S3_PS_FINDER2"; // patch note: copy MOD_GL_S3_PS_FINDER2 model from aim2 (lz) @@ -170,24 +170,12 @@ int main(int argc, char *argv[]) { // patch note: double gun for config CFG_EYEDSTONE_2: from GUN_FAST_ELECTROMAGNETIC_BEAM to double GUN_FAST_ELECTROMAGNETIC_BEAM (lz) tblcfg["CFG_EYEDSTONE_2"]["LIGHTGUN1"] = "GUN_FAST_ELECTROMAGNETIC_BEAM"; // end of db changes -#ifdef NDEBUG - db.write(); -#endif + // patch note: INFORMATION - { - auto quest = mod.quest("ru_RU"); - // patch note: add name for SINIGR armor, it was unnamed before (lz) - quest["INFORMATION"]["EQP_ZERO_ARMOR_S_SIN"]["NAME"] = "Особая нуль-броня"; - } - { - auto quest = mod.quest("en_US"); - quest["INFORMATION"]["EQP_ZERO_ARMOR_S_SIN"]["NAME"] = "Special zero armor"; - } - // more known langs: cs_CZ, de_DE, et_EE, fr_FR - // you can find vanilla dbs here (not sure if it is 1.00 or 1.03, probably 1.00): - // Supercluster discord - // https://discord.gg/CFFKpTwYZD - // https://discord.com/channels/463656710666584064/516316063747538945/615934366593581067 + auto &quest = mod.quest(); + // patch note: add name for SINIGR armor, it was unnamed before (lz) + quest["ru_RU"]["INFORMATION"]["EQP_ZERO_ARMOR_S_SIN"]["NAME"] = "Особая нуль-броня"; + quest["en_US"]["INFORMATION"]["EQP_ZERO_ARMOR_S_SIN"]["NAME"] = "Special zero armor"; // patch note: // patch note: Game Changes @@ -217,13 +205,12 @@ int main(int argc, char *argv[]) { // patch note dev: make EQP_VACUUM_DRIVE_S4 more powerful db["Оборудование"]["EQP_VACUUM_DRIVE_S4"]["VALUE1"] = 4158000.f; // end of db changes in dev mode - auto m2_gliders = mod.open_aim2_db()["Глайдеры"]; + auto m2_gliders = mod.open_aim2_db().at("Глайдеры"); for (auto &&[n,_] : db["Глайдеры"]) { m2_gliders.erase(n); } m2_gliders.erase("GL_BOT"); m2_gliders.erase("GL_RACE1"); - db.write(); // patch note dev: copy gliders from m2: GL_M4_C_MASTODON, GL_M4_S_FLASH, GL_M4_A_FORWARD, GL_M4_A_FORWARD_BLACK auto add_glider = [&, after = "GL_M1_A_ATTACKER"s](auto &&name) mutable { mod.copy_glider_from_aim2(name); diff --git a/src/aim1_mod_maker/aim1_mod_maker.h b/src/aim1_mod_maker/aim1_mod_maker.h index 5ace4b7..810e140 100644 --- a/src/aim1_mod_maker/aim1_mod_maker.h +++ b/src/aim1_mod_maker/aim1_mod_maker.h @@ -74,7 +74,7 @@ struct aim_exe_v1_06_constants { struct mod_maker { struct db_wrapper { db2::files::db2_internal m; - db2::files::db2_internal *m2_{}; + db2::files::db2_internal m2; path fn; int codepage{1251}; bool written{}; @@ -88,33 +88,56 @@ struct mod_maker { m.save(fn, codepage); written = true; } + auto write(const path &fn) { + auto files = m.save(fn, codepage); + written = true; + return files; + } auto &operator[](this auto &&d, const std::string &s) { return d.m[s]; } - auto &m2() { - return *m2_; - } void copy_from_aim2(auto &&table_name, auto &&value_name, auto &&field_name) { - auto check_val = [](auto &&m, const std::string &key, auto &&err) { - if (auto it = m.find(key); it == m.end()) { - throw std::runtime_error{err}; - } - }; - check_val(m2().m, (const char *)table_name, "aim2: no such table"); - check_val(m2()[table_name], value_name, "aim2: no such value"); - check_val(m2()[table_name][value_name], field_name, "aim2: no such field"); - m[table_name][value_name][field_name] = m2()[table_name][value_name][field_name]; + if (m2.empty()) { + return; + } + m[table_name][value_name][field_name] = m2.at(table_name).at(value_name).at(field_name); } void copy_from_aim2(auto &&table_name, auto &&value_name) { - auto check_val = [](auto &&m, const std::string &key, auto &&err) { - if (auto it = m.find(key); it == m.end()) { - throw std::runtime_error{err}; - } - }; - check_val(m2().m, (const char *)table_name, "aim2: no such table"); - check_val(m2()[table_name], value_name, "aim2: no such value"); - m[table_name][value_name] = m2()[table_name][value_name]; + if (m2.empty()) { + return; + } + m[table_name][value_name] = m2.at(table_name).at(value_name); } + bool empty() const { return m.empty(); } + }; + struct quest_wrapper { + std::map m; + bool written{}; + + quest_wrapper() = default; + quest_wrapper(const quest_wrapper &) = delete; + auto write(const path &datadir) { + std::set files; + for (auto &&[fn,v] : m) { + files.merge(v.write(datadir / ("quest_" + fn))); + } + written = true; + return files; + } + auto &operator[](this auto &&d, const std::string &s) { + return d.m[s]; + } + void copy_from_aim2(auto &&table_name, auto &&value_name, auto &&field_name) { + for (auto &&[_, v] : m) { + v.copy_from_aim2(table_name, value_name, field_name); + } + } + void copy_from_aim2(auto &&table_name, auto &&value_name) { + for (auto &&[_, v] : m) { + v.copy_from_aim2(table_name, value_name); + } + } + bool empty() const { return m.empty(); } }; enum class file_type { unknown, @@ -138,6 +161,8 @@ struct mod_maker { std::set restored_files; std::set copied_files; std::source_location loc; + db_wrapper dw; + quest_wrapper qw; mod_maker(std::source_location loc = std::source_location::current()) : loc{loc} { init(fs::current_path()); @@ -171,6 +196,10 @@ struct mod_maker { } } void apply() { + dw.write(); + auto quest_dbs = qw.write(get_data_dir()); + files_to_distribute.merge(quest_dbs); + std::vector files; for (auto &&p : files_to_pak) { if (p.filename() == aim_exe) { @@ -398,9 +427,8 @@ struct mod_maker { add_resource(copied_fn); db().copy_from_aim2("Модели", path{object}.stem().string()); auto textures = read_lines(path{copied_fn} += ".textures.txt"); - auto &m2 = open_aim2_db(); for (auto &&t : textures) { - path fn = std::get(m2["Текстуры"][t]["FILENAME"]); + path fn = std::get(db().m2.at("Текстуры").at(t).at("FILENAME")); if (fn.empty()) { throw std::runtime_error{"Can't find texture: "s + t}; } @@ -437,44 +465,65 @@ struct mod_maker { copy_from_aim2("MOD_"s + object); db().copy_from_aim2("Глайдеры", path{object}.stem().string()); - // may be absent - try..catch? - quest("ru_RU").copy_from_aim2("INFORMATION", path{object}.stem().string()); + quest().copy_from_aim2("INFORMATION", path{object}.stem().string()); } - auto db() { - auto w = open_db("db", 1251); // always 1251 probably - if (aim2_available()) { - w.m2_ = &open_aim2_db(); - } - return w; - } - auto quest(const std::string &language = {}) { - // TODO: check if it's possible to use utf8/16 in aim game - // set codepages here until we fix or implement unicode - int db_codepage = 1251; - if (language == "fr_fr") { - // change cp. Also for other langs - } - if (language.empty()) { - auto w = open_db("quest", db_codepage); + auto &db() { + if (dw.empty()) { + auto cp = 1251; // always 1251 or 0 probably for db + dw = open_db("db", cp); if (aim2_available()) { - w.m2_ = &open_aim2_quest(); + dw.m2 = db2{aim2_game_dir / "data" / "db"}.open().to_map(cp); } - return w; - } else { - auto w = open_db("quest_" + language, db_codepage); - if (aim2_available()) { - w.m2_ = &open_aim2_quest(); - } - return w; } + return dw; } - db2::files::db2_internal &open_aim2_db() { + auto &quest() { + // check if it's possible to use utf8/16 in aim game + // | set codepages here until we fix or implement unicode + // probably not possible, so use default codepages + if (qw.empty()) { + // TODO: maybe add vanilla db into translations repository as well? + prepare_languages(); + } + return qw; + } + const auto &open_aim2_db() { if (!aim2_available()) { throw std::runtime_error{"aim2 is not available, setup it first"}; } - static auto m2 = db2{aim2_game_dir / "data" / "db"}.open().to_map(1251); - return m2; + return db().m2; + } + void prepare_languages() { + auto trdirname = "translations"; + auto trdir = get_mod_dir().parent_path() / trdirname; + primitives::Command c; + c.push_back("git"); + if (!fs::exists(trdir)) { + c.working_directory = get_mod_dir().parent_path(); + c.push_back("clone"); + c.push_back("https://github.com/aimrebirth/translations"); + c.push_back(trdirname); + } else { + c.working_directory = trdir; + c.push_back("pull"); + c.push_back("origin"); + c.push_back("master"); + } + run_command(c); + for (auto &&p : fs::directory_iterator{trdir / "aim1"}) { + if (!fs::is_regular_file(p) || p.path().extension() != ".json") { + continue; + } + auto s = split_string(p.path().stem().string(), "_"); + auto lang = std::format("{}_{}", s.at(1), s.at(2)); + qw[lang].m.load_from_json(p); + qw[lang].codepage = code_pages.at(s.at(1)); + auto m2fn = trdir / "aim2" / p.path().filename(); + if (fs::exists(m2fn)) { + qw[lang].m2.load_from_json(m2fn); + } + } } private: @@ -483,13 +532,6 @@ private: fs::copy_file(from, to, fs::copy_options::overwrite_existing); } } - db2::files::db2_internal &open_aim2_quest() { - if (!aim2_available()) { - throw std::runtime_error{"aim2 is not available, setup it first"}; - } - static auto m2 = db2{aim2_game_dir / "data" / "quest"}.open().to_map(1251); - return m2; - } bool aim2_available() const { return !aim2_game_dir.empty(); } @@ -871,7 +913,8 @@ FF D7 ; call edi game_dir = fs::absolute(game_dir).lexically_normal(); } void detect_tools() { - //check_in_path("git"); + // for languages/translations support + check_in_path("git"); // also --self-upgrade? check_in_path("sw"); } diff --git a/src/common/common.h b/src/common/common.h index e4c5495..3dd5756 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -30,7 +30,7 @@ static const std::map code_pages { "en", 0 }, { "cz", 1250 }, { "ru", 1251 }, - { "ge", 1252 }, + { "de", 1252 }, { "fr", 1252 }, { "et", 1257 }, }; diff --git a/src/common/db2.h b/src/common/db2.h index 5cfcd10..9596e7e 100644 --- a/src/common/db2.h +++ b/src/common/db2.h @@ -186,6 +186,9 @@ struct db2 { auto &operator[](this auto &&d, const std::string &s) { return d.m[s]; } + auto &at(this auto &&d, const std::string &s) { + return d.m.at(s); + } auto to_json() const { nlohmann::json ja; for (auto &&[tn,t] : m) { @@ -222,7 +225,7 @@ struct db2 { } } } - void save(const path &fn, int codepage) { + auto save(const path &fn, int codepage) { auto s_to_char20 = [&](char20 &dst, const std::string &in, int codepage) { auto s = utf8_to_dbstr(in, codepage); if (s.size() + 1 > sizeof(char20)) { @@ -291,9 +294,15 @@ struct db2 { ++table_id; } - write_file(path{fn} += ".tab", tabv.d); - write_file(path{fn} += ".ind", indv.d); - write_file(path{fn} += ".dat", datv.d); + std::set files; + auto f = [&](auto &&fn, auto &&d) { + write_file(fn, d); + files.insert(fn); + }; + f(path{fn} += ".tab", tabv.d); + f(path{fn} += ".ind", indv.d); + f(path{fn} += ".dat", datv.d); + return files; } };