Импорт пресета песочницы .cfg и списка модов
Сборки нередко раздают только пресет одиночной игры. Внутри это плоский список «Параметр=значение» с точками вместо вложенности, поэтому значения переносятся в SandboxVars.lua профиля по одному: комментарии игры и порядок остаются на месте, а параметры модов, которых в файле ещё нет, пропускаются со счётом. Если файла нет вовсе, он собирается из пресета целиком. Заодно панель принимает строку из игровой кнопки «Поделиться» («Название:ModA;ModB;…»): она заменяет список включённых модов, сохраняя порядок загрузки. Идентификаторов мастерской в такой строке нет, поэтому пакеты добавляются только для модов, чьи файлы уже на диске, а про остальные панель предупреждает. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+61
-3
@@ -1327,6 +1327,51 @@ $("#mod-list").addEventListener("drop", (event) => {
|
||||
renderMods();
|
||||
});
|
||||
|
||||
// Игра умеет делиться набором модов строкой вида «Название:ModA;ModB;…».
|
||||
// Workshop ID в ней нет, поэтому список задаёт только порядок загрузки: файлы
|
||||
// сервер возьмёт из пакетов мастерской, которые уже перечислены в конфиге.
|
||||
function parseModList(text) {
|
||||
// Перед двоеточием стоит имя набора; у самих модов двоеточий не бывает.
|
||||
const colon = text.indexOf(":");
|
||||
const body = colon >= 0 ? text.slice(colon + 1) : text;
|
||||
|
||||
const seen = new Set();
|
||||
const out = [];
|
||||
body.split(/[;\n\r]+/).forEach((raw) => {
|
||||
const id = raw.trim();
|
||||
if (!id || seen.has(id)) return;
|
||||
seen.add(id);
|
||||
out.push(id);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
$("#mod-list-form").addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
const ids = parseModList($("#mod-list-input").value);
|
||||
if (!ids.length) {
|
||||
toast(t("mods.listEmpty"), "err");
|
||||
return;
|
||||
}
|
||||
if (!confirm(t("mods.listConfirm", { count: ids.length }))) return;
|
||||
|
||||
mods.enabled = ids;
|
||||
// Пакет мастерской известен для тех модов, чьи файлы уже лежат на диске:
|
||||
// добавляем их, чтобы сервер не потерял мод при следующем запуске.
|
||||
mods.installed.forEach((mod) => {
|
||||
if (!ids.includes(mod.mod_id) || !mod.workshop_id) return;
|
||||
if (!mods.workshop.includes(mod.workshop_id)) mods.workshop.push(mod.workshop_id);
|
||||
});
|
||||
|
||||
const installed = new Set(mods.installed.map((mod) => mod.mod_id));
|
||||
const missing = ids.filter((id) => !installed.has(id)).length;
|
||||
|
||||
$("#mod-list-input").value = "";
|
||||
renderMods();
|
||||
ok(t("mods.listDone", { count: ids.length }));
|
||||
if (missing) toast(t("mods.listNoFiles", { count: missing }));
|
||||
});
|
||||
|
||||
$("#map-list").addEventListener("input", refreshModsSaveState);
|
||||
$("#mod-filter").addEventListener("input", renderMods);
|
||||
$("#mod-only-enabled").addEventListener("change", renderMods);
|
||||
@@ -1642,11 +1687,21 @@ function renderPresetSummary() {
|
||||
if (set.has_ini) {
|
||||
lines.push(t("import.mods", { mods: set.mods, workshop: set.workshop_items }));
|
||||
if ((set.maps || []).length) lines.push(t("import.maps", { maps: set.maps.join(", ") }));
|
||||
} else {
|
||||
} else if (!set.has_sandbox_preset) {
|
||||
// У пресета песочницы конфига сервера и не должно быть — про него сказано
|
||||
// отдельной строкой ниже.
|
||||
lines.push(t("import.noINI"));
|
||||
}
|
||||
lines.push(t("import.counts", { settings: set.settings, sandbox: set.sandbox_settings }));
|
||||
lines.push(t(set.has_spawn_regions ? "import.spawnYes" : "import.spawnNo"));
|
||||
if (set.has_sandbox_preset) {
|
||||
// Пресет одиночной игры — не файл сервера: он переносится по значениям.
|
||||
lines.push(t("import.sandboxPreset", { count: set.sandbox_settings }));
|
||||
lines.push(t("import.sandboxPresetNote"));
|
||||
} else {
|
||||
lines.push(t("import.counts", { settings: set.settings, sandbox: set.sandbox_settings }));
|
||||
}
|
||||
if (set.has_ini || set.has_spawn_regions) {
|
||||
lines.push(t(set.has_spawn_regions ? "import.spawnYes" : "import.spawnNo"));
|
||||
}
|
||||
|
||||
$("#preset-summary").replaceChildren(...lines.map((line) => {
|
||||
const el = document.createElement("div");
|
||||
@@ -1702,6 +1757,9 @@ $("#btn-preset-import").addEventListener("click", async () => {
|
||||
if ((result.kept || []).length) {
|
||||
toast(t("import.kept", { profile: result.kept_from, keys: result.kept.join(", ") }));
|
||||
}
|
||||
if (result.applied !== undefined) {
|
||||
toast(t("import.applied", { applied: result.applied, skipped: result.skipped }));
|
||||
}
|
||||
$("#preset-file").value = "";
|
||||
$("#preset-details").hidden = true;
|
||||
await loadProfiles();
|
||||
|
||||
Reference in New Issue
Block a user