diff --git a/.eslintrc.js b/.eslintrc.js index d4cbf768..0ff9374b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -320,7 +320,7 @@ module.exports = { "semi-style": "off", "sort-imports": "off", "sort-keys": "off", - "sort-vars": "error", + "sort-vars": "off", "space-before-blocks": "error", "space-before-function-paren": "off", "space-in-parens": ["error", "never"], diff --git a/CHANGELOG.md b/CHANGELOG.md index cac58b2a..944084bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Fixed Web UI not showing instance saves when the permission to list and subscribe the saves is granted but the user is not a cluster admin. - Similarly fixed Web UI not showing the mod pack create, delete, list and host revoke token actions if the user is not a cluster admin. - Fixed Research Sync applying progress of a previous techology level to the level on the controller when it's the active research during instance startup. +- Fixed data export with a mod using shorthand notation for the default value of a color-setting causing the ModPack data to be set to invalid data. ## Version 2.0.0-alpha.16 diff --git a/packages/host/src/export.ts b/packages/host/src/export.ts index 938836d4..337a89f5 100644 --- a/packages/host/src/export.ts +++ b/packages/host/src/export.ts @@ -18,11 +18,10 @@ interface SimpleIconSpecification { icon_size: number; icon_mipmaps?: number; } -type Color = { r: number, g: number, b: number, a: number }; interface IconLayer { icon: string; icon_size: number; - tint?: Color, + tint?: lib.FactorioColor, shift?: [number, number], scale?: number, icon_mipmaps?: number, @@ -202,15 +201,9 @@ async function loadLayeredIcon( iconLayer = iconLayer.clone(); - let tint: Color; + let tint: lib.Color; if (layer.tint) { - let divisor = (layer.tint.r > 1 || layer.tint.g > 1 || layer.tint.b > 1) ? 255 : 1; - tint = { - r: (layer.tint.r || 0) / divisor, - g: (layer.tint.g || 0) / divisor, - b: (layer.tint.b || 0) / divisor, - a: layer.tint.a || 1, - }; + tint = lib.normalizeColor(layer.tint); } else { tint = { r: 1, b: 1, g: 1, a: 1 }; } diff --git a/packages/lib/browser.ts b/packages/lib/browser.ts index 5c430809..97525776 100644 --- a/packages/lib/browser.ts +++ b/packages/lib/browser.ts @@ -6,6 +6,7 @@ export * from "./src/factorio/exchange_string"; export * from "./src/helpers"; export * from "./src/ini"; export * from "./src/link"; +export * from "./src/lua_tools"; export * from "./src/logging"; export * from "./src/permissions"; export * from "./src/plugin"; diff --git a/packages/lib/src/data/ModPack.ts b/packages/lib/src/data/ModPack.ts index 094addb4..85dba1fb 100644 --- a/packages/lib/src/data/ModPack.ts +++ b/packages/lib/src/data/ModPack.ts @@ -2,6 +2,7 @@ import zlib from "zlib"; import { Type, Static } from "@sinclair/typebox"; import * as libSchema from "../schema"; +import * as libLuaTools from "../lua_tools"; import ExportManifest from "./ExportManifest"; import { integerFactorioVersion } from "./version"; @@ -363,7 +364,11 @@ export default class ModPack { continue; } - this.settings[settingType].set(prototype.name, { value: prototype.default_value }); + let value = prototype.default_value; + if (prototype.type === "color-setting") { + value = libLuaTools.normalizeColor(value); + } + this.settings[settingType].set(prototype.name, { value }); } } } diff --git a/packages/lib/src/lua_tools.ts b/packages/lib/src/lua_tools.ts index b9dbba13..007f9a16 100644 --- a/packages/lib/src/lua_tools.ts +++ b/packages/lib/src/lua_tools.ts @@ -3,6 +3,54 @@ * @module lib/lua_tools */ +/** + * A colour as it may originate from Factorio Lua code, see + * https://lua-api.factorio.com/latest/types/Color.html + */ +export type FactorioColor = { r?: number, g?: number, b?: number, a?: number } | [number, number, number, number?]; + +export type Color = { r: number, g: number, b: number, a: number }; + +/** + * Normalise a color as it may appear in Factorio Lua code to be an object + * with r, g, b, a properties in the range 0 to 1. + * + * @param color - + * A colour that may be specified with left out components or as an + * array and have the range 1 to 0 or 255 to 0. + * @returns + * Normalised colour where all 4 components are present as properties in + * the range 0 to 1. + */ +export function normalizeColor(color: FactorioColor) { + let r, g, b, a; + if (color instanceof Array) { + ([r, g, b, a] = color); + } else { + ({ r, g, b, a } = color); + } + + r ??= NaN; + g ??= NaN; + b ??= NaN; + a ??= NaN; + + // Note NaN > 1 is false and NaN / 255 is NaN + if (r > 1 || g > 1 || b > 1 || a > 1) { + r /= 255; + g /= 255; + b /= 255; + a /= 255; + } + + return { + r: Number.isNaN(r) ? 0 : r, + g: Number.isNaN(g) ? 0 : g, + b: Number.isNaN(b) ? 0 : b, + a: Number.isNaN(a) ? 1 : a, + }; +} + /** * Escapes a string for inclusion into a lua string * diff --git a/packages/lib/tsconfig.browser.json b/packages/lib/tsconfig.browser.json index a53b11d7..e3c20c4e 100644 --- a/packages/lib/tsconfig.browser.json +++ b/packages/lib/tsconfig.browser.json @@ -12,6 +12,7 @@ "./src/hash.ts", "./src/ini.ts", "./src/link/*.ts", + "./src/lua_tools.ts", "./src/logging.ts", "./src/permissions.ts", "./src/prometheus.ts", diff --git a/test/lib/lua_tools.js b/test/lib/lua_tools.js index 8fcbac74..8b70b274 100644 --- a/test/lib/lua_tools.js +++ b/test/lib/lua_tools.js @@ -5,6 +5,25 @@ const lib = require("@clusterio/lib"); describe("lib/lua_tools", function() { + describe("normalizeColor()", function() { + it("should normalise colours according to the documentation", function() { + const nc = lib.normalizeColor; + assert.deepEqual(nc({ r: 1, g: 0.2, b: 0.4, a: 0.8 }), { r: 1, g: 0.2, b: 0.4, a: 0.8 }); + assert.deepEqual(nc({ r: 1, g: 0.2, b: 0.4 }), { r: 1, g: 0.2, b: 0.4, a: 1 }); + assert.deepEqual(nc({ r: 1, b: 0.4 }), { r: 1, g: 0, b: 0.4, a: 1 }); + assert.deepEqual(nc({ a: 0.4 }), { r: 0, g: 0, b: 0, a: 0.4 }); + assert.deepEqual(nc({}), { r: 0, g: 0, b: 0, a: 1 }); + assert.deepEqual(nc([1, 0.2, 0.4, 0.8]), { r: 1, g: 0.2, b: 0.4, a: 0.8 }); + assert.deepEqual(nc([1, 0.2, 0.4]), { r: 1, g: 0.2, b: 0.4, a: 1 }); + assert.deepEqual(nc({ r: 255, g: 51, b: 102, a: 204 }), { r: 1, g: 0.2, b: 0.4, a: 0.8 }); + assert.deepEqual(nc({ r: 255, g: 51, b: 102 }), { r: 1, g: 0.2, b: 0.4, a: 1 }); + assert.deepEqual(nc({ r: 255, b: 102 }), { r: 1, g: 0, b: 0.4, a: 1 }); + assert.deepEqual(nc({ a: 102 }), { r: 0, g: 0, b: 0, a: 0.4 }); + assert.deepEqual(nc({}), { r: 0, g: 0, b: 0, a: 1 }); + assert.deepEqual(nc([255, 51, 102, 204]), { r: 1, g: 0.2, b: 0.4, a: 0.8 }); + assert.deepEqual(nc([255, 51, 102]), { r: 1, g: 0.2, b: 0.4, a: 1 }); + }); + }); describe("escapeString()", function() { it("should escape backslashes", function() { assert.equal(lib.escapeString("\\a"), "\\\\a");