diff --git a/packages/lib/src/factorio/exchange_string.ts b/packages/lib/src/factorio/exchange_string.ts index cc87f24f..3749fd6a 100644 --- a/packages/lib/src/factorio/exchange_string.ts +++ b/packages/lib/src/factorio/exchange_string.ts @@ -3,14 +3,19 @@ import zlib from "zlib"; class MapReaderState { pos = 0; last_position = { x: 0, y: 0 }; - /** True when a version greater than 2.0.0 is detected */ - gt_v2_0 = false; - /** True when a version greater than 2.1.0 is detected */ - gt_v2_1 = false; - // If more versions are required then we should expose version directly + /** Version of Factorio the string was created with */ + version = [0, 0, 0, 0]; constructor( public buf: Buffer ) { } + + /** True when the version is at least the given major.minor.patch */ + versionAtLeast(major: number, minor: number, patch = 0) { + const [vMajor, vMinor, vPatch] = this.version; + if (vMajor !== major) { return vMajor > major; } + if (vMinor !== minor) { return vMinor > minor; } + return vPatch >= patch; + } } function readUInt8(state: MapReaderState) { @@ -164,7 +169,7 @@ function readBoundingBox(state: MapReaderState) { } function readCliffSettings(state: MapReaderState) { - return state.gt_v2_0 ? { + return state.versionAtLeast(2, 0) ? { name: readString(state), control: readString(state), // v2 cliff_elevation_0: readFloat(state), @@ -189,7 +194,7 @@ function readTerritorySettings(state: MapReaderState) { } function readMapGenSettings(state: MapReaderState) { - return state.gt_v2_0 ? { + return state.versionAtLeast(2, 0) ? { autoplace_controls: Object.fromEntries(readDict(state, readString, readFrequencySizeRichness)), autoplace_settings: Object.fromEntries(readDict(state, readString, readAutoplaceSetting)), default_enable_all_autoplace_controls: readBool(state), @@ -265,7 +270,7 @@ function readEnemyEvolution(state: MapReaderState) { } function readEnemyExpansion(state: MapReaderState) { - return state.gt_v2_1 ? { + return state.versionAtLeast(2, 1) ? { enabled: readOptional(state, readBool), max_expansion_distance: readOptional(state, readUInt32), min_expansion_distance: readOptional(state, readUInt32), // v2.1 @@ -281,6 +286,9 @@ function readEnemyExpansion(state: MapReaderState) { evolution_group_size_factor: readOptional(state, readDouble), // v2.1 min_expansion_cooldown: readOptional(state, readUInt32), max_expansion_cooldown: readOptional(state, readUInt32), + ...state.versionAtLeast(2, 1, 13) ? { + build_base_unit_dispatch_cooldown: readOptional(state, readUInt32), // v2.1.13 + } : {}, } : { enabled: readOptional(state, readBool), max_expansion_distance: readOptional(state, readUInt32), @@ -355,7 +363,7 @@ function readPathFinder(state: MapReaderState) { } function readDifficultySettings(state: MapReaderState) { - return state.gt_v2_0 ? { + return state.versionAtLeast(2, 0) ? { technology_price_multiplier: readDouble(state), spoil_time_modifier: readDouble(state), // v2 } : { @@ -374,7 +382,7 @@ function readAsteroids(state: MapReaderState) { } function readMapSettings(state: MapReaderState) { - if (state.gt_v2_1) { + if (state.versionAtLeast(2, 1)) { return { pollution: readPollution(state), // steering removed v2.1 @@ -386,7 +394,7 @@ function readMapSettings(state: MapReaderState) { difficulty_settings: readDifficultySettings(state), asteroids: readAsteroids(state), // v2 }; - } else if (state.gt_v2_0) { + } else if (state.versionAtLeast(2, 0)) { return { pollution: readPollution(state), steering: readSteering(state), @@ -460,8 +468,7 @@ export function readMapExchangeString(exchangeString: string) { try { const version = readVersion(state); - state.gt_v2_0 = version[0] >= 2; - state.gt_v2_1 = version[0] >= 2 && version[1] >= 1; + state.version = version; data = { version: version, unknown: readUInt8(state), diff --git a/test/integration/clusterio.js b/test/integration/clusterio.js index b44a5e18..f0d15f83 100644 --- a/test/integration/clusterio.js +++ b/test/integration/clusterio.js @@ -1211,10 +1211,14 @@ describe("Integration of Clusterio", function() { await execCtl("instance start 44"); const instance = (await getInstances()).get(44); - const isV2 = lib.integerPartialVersion(instance.factorioVersion) > lib.integerPartialVersion("2.0"); - const isV2_1 = lib.integerPartialVersion(instance.factorioVersion) > lib.integerPartialVersion("2.1"); + const version = lib.integerPartialVersion(instance.factorioVersion); + const isV2 = version > lib.integerPartialVersion("2.0"); + const isV2_1 = version > lib.integerPartialVersion("2.1"); + const isV2_1_13 = version >= lib.integerPartialVersion("2.1.13"); let exchangeString = testStrings.modified; - if (isV2_1) { + if (isV2_1_13) { + exchangeString = testStrings.modified_v2_1_13; + } else if (isV2_1) { exchangeString = testStrings.modified_v2_1; } else if (isV2) { exchangeString = testStrings.modified_v2; diff --git a/test/lib/factorio/exchange_string.js b/test/lib/factorio/exchange_string.js index 10d1421f..eaea8458 100644 --- a/test/lib/factorio/exchange_string.js +++ b/test/lib/factorio/exchange_string.js @@ -69,6 +69,16 @@ describe("lib/factorio/exchange_string", function() { }); }); + it("should parse post v2.1.13 strings", function() { + assert.deepEqual(lib.readMapExchangeString(testStrings.modified_v2_1_13), { + version: [2, 1, 14, 1], + unknown: 0, + map_gen_settings: testSettings.modified_v2_1_map_gen_settings, + map_settings: testSettings.modified_v2_1_13_map_settings, + checksum: 272054359, + }); + }); + it("should handle malformed strings", function() { assert.throws( () => lib.readMapExchangeString("<>"), diff --git a/test/lib/factorio/test_settings.js b/test/lib/factorio/test_settings.js index 3c295c14..fea7eb77 100644 --- a/test/lib/factorio/test_settings.js +++ b/test/lib/factorio/test_settings.js @@ -615,6 +615,14 @@ const modified_v2_1_map_settings = { }, }; +const modified_v2_1_13_map_settings = { + ...modified_v2_1_map_settings, + "enemy_expansion": { + ...modified_v2_1_map_settings.enemy_expansion, + "build_base_unit_dispatch_cooldown": 1234, + }, +}; + const modified_space_age_map_gen_settings = { autoplace_controls: { "aquilo_crude_oil": { frequency: 1, size: 1, richness: 1 }, @@ -703,5 +711,6 @@ module.exports = { modified_v2_map_settings, modified_v2_1_map_gen_settings, modified_v2_1_map_settings, + modified_v2_1_13_map_settings, modified_space_age_map_gen_settings, }; diff --git a/test/lib/factorio/test_strings.js b/test/lib/factorio/test_strings.js index 8b81be75..eebad109 100644 --- a/test/lib/factorio/test_strings.js +++ b/test/lib/factorio/test_strings.js @@ -86,10 +86,27 @@ Gx0+5gy4RUpPimXd3JJg0hSVAZVaD42oXlGs+OdLeL8NIjXUO043NJp 38bydxUydeQi4j66kCmzxzUMyYTVaPneF1Uj+vlxQH+ZT82gl0Du2he Y+lJ/JEpK/WZNmBM/mgXgnKlfCACLzljjH+s/hYo=<<<`; +const modified_v2_1_13 = ` +>>>eNp1U89rE0EUftMmNYm15hAFRWoOPYiQEKKgFMmOXsSDB0HwJHGy +mejSzU47u1utHszBgwdBkF70olcrehHvAS/2JvUfqNSDvbVSpAehzsv +sTNYEH7zHt9/7/YadAAIzSmE64wrmA/QcowVXLC5yWRGSA4Cl866M27 +wiPD/F0gIPeHel0mIhBlOA/sCV86QIdIW+rTAdsHjZC5uu73U66Y5ZK +dyFMM0cDyMmIy+422SSs2ZXeGEUY7VUUhiJIGmaMJHkPHy3tuYobSBz +OJYs8OKunqSHvI68zyIucQ8TCeTjr9vrvSezgHrwGMoHB6gKbao4VCA +9HU0UmUi26IogksKfZ/GD+ZbHwnylVq3XUI6lXR3Jl2IeuCu5erX2r9 +ssN5J+csw/rFGrnhtInvt8mUWeCI5a1PTZAg+zyZGhfFnpFXs4Qsij0 +oer3x+uOkSvUKUJ2E+Yfssw1xLw4j39nytjXHDegB1HNdm9t/X00/5e +g/x5u7txvXXHITd/FJfCs9sN5TyCx5xEM2WRzt00U+XoKKPAq5co2w7 +JYkZpmFakZGd1AkjtxhSQjYziizPKvHmmTPmUDWuYoiVKOgP57RB9yS +0Dvo1NMEfJJWw1i+YLmuywM1C1jwbPKaGnjffEMETl1yE9Qxu/9C7rp +u3nVP+RQebGLpzeY4SxwbkUKGDDtjU/J9P3/nrIfNHXdPAWgFF7JHkZ +0L+YLmXPjcc3XoAz1D69BVjk1sULxb83nuUA<<<`; + module.exports = { default: default_, modified, modified_v2, modified_v2_1, + modified_v2_1_13, modified_space_age, };