diff --git a/.gitignore b/.gitignore index 7f4bb816..3bdebd86 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ node_modules Factorio* /factorio +!/test/file/factorio /instances /mods /database diff --git a/CHANGELOG.md b/CHANGELOG.md index 5de689e0..436827f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ Many thanks to the following for contributing to this release: ## Version 2.0.0-alpha.19 +### Major Features + +- Added support for 2.0 builtin mods during mod pack creation. [#684](https://github.com/clusterio/clusterio/pull/684). + ### Features - Added Datastore and Datastore Provider classes to the library to support savable Maps [#629](https://github.com/clusterio/clusterio/pull/629). @@ -37,6 +41,7 @@ Many thanks to the following for contributing to this release: - Changed default role selection to be a dropdown menu rather than id [#664](https://github.com/clusterio/clusterio/pull/664). - Added credential option to config entries to allow writing sensitive data that can't be read back remotely. - Allow hidden and readonly graphical representations of config values. [#665](https://github.com/clusterio/clusterio/pull/665). +- Added locale exporting for all builtin mods, not just for the base mod. [#684](https://github.com/clusterio/clusterio/pull/684). - Added option for silent testing which only shows the results, can be ran using `pnpm silent-test` [#655](https://github.com/clusterio/clusterio/pull/655). ### Changes @@ -68,6 +73,8 @@ Many thanks to the following for contributing to this release: - Updated display name and description of `controller.external_address` to avoid confusion. [#674](https://github.com/clusterio/clusterio/pull/674) - Fixed invalid transient state during server start. [#676](https://github.com/clusterio/clusterio/issues/676) - Fixed host user sync to instances after reconnecting to the controller. [#678](https://github.com/clusterio/clusterio/pull/678) +- Fixed WebUI adding and displaying the wrong base mod version. [#684](https://github.com/clusterio/clusterio/pull/684). +- Fixed WebUI inconsistent pattern matching for factorio version. [#684](https://github.com/clusterio/clusterio/pull/684). ### Breaking Changes diff --git a/packages/host/src/Host.ts b/packages/host/src/Host.ts index f6f058b2..6690d304 100644 --- a/packages/host/src/Host.ts +++ b/packages/host/src/Host.ts @@ -618,11 +618,12 @@ export default class Host extends lib.Link { } async fetchMods(mods: Iterable) { + // This is better than the previous hard coded names + // But it really shouldn't be a hard coded version either + const builtinModNames = lib.ModPack.getBuiltinModNames("2.0"); const modInfos: Promise[] = []; for (const mod of mods) { - // XXX These mods ship with Factorio, but the list of mods may - // change and should not be hardcoded here. - if (["core", "base"].includes(mod.name)) { + if (builtinModNames.includes(mod.name)) { continue; } modInfos.push(this.fetchMod(mod)); diff --git a/packages/host/src/export.ts b/packages/host/src/export.ts index f5f83528..32e94a04 100644 --- a/packages/host/src/export.ts +++ b/packages/host/src/export.ts @@ -103,7 +103,8 @@ async function loadFile(server: FactorioServer, modVersions: Map let [, mod, filePath] = match; - if (["core", "base"].includes(mod)) { + const builtinModNames = ["core", ...lib.ModPack.getBuiltinModNames(server.version!)]; + if (builtinModNames.includes(mod)) { try { return await fs.readFile(server.dataPath(mod, filePath)); } catch (err: any) { @@ -396,11 +397,18 @@ async function exportLocale( } } - let baseLocaleFilePath = server.dataPath("base", "locale", languageCode, "base.cfg"); - mergeLocale(lib.parse(await fs.readFile(baseLocaleFilePath, "utf8"))); + const builtinModNames = [ + // Filter if on builtin mods so "core" does not need to be specified in modOrder + "core", ...lib.ModPack.getBuiltinModNames(server.version!).filter(mod => modOrder.includes(mod)), + ]; + for (const builtinModName of builtinModNames) { + const localeFilePath = server.dataPath(builtinModName, "locale", languageCode, `${builtinModName}.cfg`); + mergeLocale(lib.parse(await fs.readFile(localeFilePath, "utf8"))); + } + const builtinModNamesExport = ["export", ...builtinModNames]; for (let mod of modOrder) { - if (["core", "base", "export"].includes(mod)) { + if (builtinModNamesExport.includes(mod)) { continue; } diff --git a/packages/host/src/patch.ts b/packages/host/src/patch.ts index c0047667..9b7f1fb9 100644 --- a/packages/host/src/patch.ts +++ b/packages/host/src/patch.ts @@ -202,12 +202,6 @@ export class PatchInfo { } } - -const knownScenarios: Record = { - // First seen in 0.17.63 - "4e866186ebe297f1038fd325b09df1a1f5e2fdd1": new lib.ModuleInfo("freeplay", "0.17.63", ["freeplay", "silo-script"]), -}; - /** * Generates control.lua code for loading the Clusterio modules * @@ -349,6 +343,14 @@ function reorderDependencies(modules: SaveModule[]) { } } +const knownScenarios: Record = { + // First seen in 0.17.63 + "4e866186ebe297f1038fd325b09df1a1f5e2fdd1": new lib.ModuleInfo("freeplay", "0.17.63", [], ["scenario"]), + // First seen in 2.0 TODO Uncomment once clusterio lib supports 2.0 + // The rest of the code has already been updated to support the new control.lua file for 2.0 + // "bcbdde18ce4ec16ebfd93bd694cd9b12ef969c9a": new lib.ModuleInfo("freeplay", "2.0.0", [], ["scenario"]), +}; + /** * Patch a save with the given modules * @@ -387,6 +389,7 @@ export async function patch(savePath: string, modules: SaveModule[]) { if (controlHash in knownScenarios) { patchInfo = new PatchInfo(0, knownScenarios[controlHash], []); + root.file("scenario.lua", controlFile.nodeStream("nodebuffer")); } else { throw new Error(`Unable to patch save, unknown scenario (${controlHash})`); } diff --git a/packages/lib/src/data/ModPack.ts b/packages/lib/src/data/ModPack.ts index d8fa5b3a..df140c8b 100644 --- a/packages/lib/src/data/ModPack.ts +++ b/packages/lib/src/data/ModPack.ts @@ -173,8 +173,11 @@ export default class ModPack { if (json.updated_at_ms) { modPack.updatedAtMs = json.updated_at_ms; } if (json.is_deleted) { modPack.isDeleted = json.is_deleted; } - if (!modPack.mods.has("base")) { - modPack.mods.set("base", { name: "base", enabled: true, version: modPack.factorioVersion }); + const builtinMods = ModPack.getBuiltinMods(modPack.factorioVersion); + for (const builtinMod of builtinMods) { + if (!modPack.mods.has(builtinMod.name)) { + modPack.mods.set(builtinMod.name, builtinMod); + } } return modPack; @@ -379,4 +382,32 @@ export default class ModPack { this.settings[settingType].set(prototype.name, { value }); } } + + /** + * Returns an array of ModInfo containing all mods included in factorio + * + * @param factorioVersion - Factorio version to get the default mods for + * @return Built in mods for the given version + */ + static getBuiltinMods(factorioVersion: string) { + let defaultMods: ModRecord[] = [ + // "core" not included because core cannot be disabled + { name: "base", enabled: true, version: factorioVersion }, + ]; + + const integerVersion = integerFactorioVersion(factorioVersion); + if (integerVersion >= integerFactorioVersion("1.2")) { + defaultMods = defaultMods.concat([ + { name: "elevated-rails", enabled: false, version: factorioVersion }, + { name: "quality", enabled: false, version: factorioVersion }, + { name: "space-age", enabled: false, version: factorioVersion }, + ]); + } + + return defaultMods; + } + + static getBuiltinModNames(factorioVersion: string) { + return this.getBuiltinMods(factorioVersion).map(builtinMod => builtinMod.name); + } } diff --git a/packages/web_ui/src/components/ModPackViewPage.tsx b/packages/web_ui/src/components/ModPackViewPage.tsx index 7175ffd5..d16819af 100644 --- a/packages/web_ui/src/components/ModPackViewPage.tsx +++ b/packages/web_ui/src/components/ModPackViewPage.tsx @@ -679,7 +679,10 @@ function applyModPackChanges(modPack: lib.ModPack, changes: ModChange[]) { let modifiedModPack = modPack.shallowClone(); for (let change of changes) { // Only modify fields that change to reduce re-renders - if (["mods.set", "mods.delete"].includes(change.type) && modifiedModPack.mods === modPack.mods) { + if ( + ["mods.set", "mods.delete", "factorioVersion"].includes(change.type) + && modifiedModPack.mods === modPack.mods + ) { modifiedModPack.mods = new Map(modifiedModPack.mods); } if ( @@ -697,22 +700,30 @@ function applyModPackChanges(modPack: lib.ModPack, changes: ModChange[]) { modifiedModPack.name = change.value; } else if (change.type === "description") { modifiedModPack.description = change.value; - } else if (change.type === "factorioVersion") { - modifiedModPack.factorioVersion = change.value; } else if (change.type === "mods.set") { - if (change.name) { - modifiedModPack.mods.set(change.name, change.value as lib.ModRecord); - } + modifiedModPack.mods.set(change.name, change.value as lib.ModRecord); } else if (change.type === "mods.delete") { - modifiedModPack.mods.delete(change.name!); + modifiedModPack.mods.delete(change.name); } else if (change.type === "settings.set") { - if (change.name) { - modifiedModPack.settings[change.scope].set(change.name, change.value); - } + modifiedModPack.settings[change.scope].set(change.name, change.value); } else if (change.type === "settings.delete") { - if (change.name) { - modifiedModPack.settings[change.scope].delete(change.name); + modifiedModPack.settings[change.scope].delete(change.name); + } else if (change.type === "factorioVersion") { + const newBuiltinMods = lib.ModPack.getBuiltinMods(change.value); + for (const newBuiltinMod of newBuiltinMods) { + const builtinMod = modifiedModPack.mods.get(newBuiltinMod.name); + if (builtinMod) { newBuiltinMod.enabled = builtinMod.enabled; } + modifiedModPack.mods.set(newBuiltinMod.name, newBuiltinMod); } + + const oldBuiltinMods = lib.ModPack.getBuiltinMods(modifiedModPack.factorioVersion); + for (const oldBuiltinMod of oldBuiltinMods) { + if (!newBuiltinMods.some(builtinMod => builtinMod.name === oldBuiltinMod.name)) { + modifiedModPack.mods.delete(oldBuiltinMod.name); + } + } + + modifiedModPack.factorioVersion = change.value; } else { throw new Error(`Unknown change type ${change.type}`); } @@ -879,8 +890,9 @@ export default function ModPackViewPage() { name="factorioVersion" style={{ marginBottom: 0 }} rules={[{ - pattern: /^\d+\.\d+\.\d+?$/, - message: "Must be an a.b.c version number.", + required: true, + pattern: /^\d+\.\d+(\.\d+)?$/, + message: "Must be an a.b or a.b.c version number.", }]} > builtinMod.name) + } />