From 1021e29c98d37674db78c4fb26c2e96fde4f4210 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Thu, 8 Feb 2024 18:30:59 +0100 Subject: [PATCH] Support shorthand notation for colours Implement the shorthand notations that Factorio recognises for colours where individual components may be left out or the colour specified as an array of values instead of an object. --- .eslintrc.js | 2 +- CHANGELOG.md | 1 + packages/host/src/export.ts | 13 ++------ packages/lib/browser.ts | 1 + packages/lib/src/data/ModPack.ts | 7 ++++- packages/lib/src/lua_tools.ts | 48 ++++++++++++++++++++++++++++++ packages/lib/tsconfig.browser.json | 1 + test/lib/lua_tools.js | 19 ++++++++++++ 8 files changed, 80 insertions(+), 12 deletions(-) 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");