local Color = require 'resources.color_presets' local type = type local tonumber = tonumber local tostring = tostring local gmatch = string.gmatch local pairs = pairs local concat = table.concat local size = table.size local sqrt = math.sqrt local floor = math.floor local color_key_table = {'r', 'g', 'b', 'a'} local function to_valid_rgba_table(input_table) local output = { r = input_table.r or input_table[1] or 0, g = input_table.g or input_table[2] or 0, b = input_table.b or input_table[3] or 0, a = input_table.a or input_table[4], } if output.r <= 1 and output.g <= 1 and output.b <= 1 and (output.a and output.a <= 1 or not output.a) then output.r = floor(output.r * 255) output.g = floor(output.g * 255) output.b = floor(output.b * 255) if output.a and output.a <= 1 then output.a = floor(output.a * 255) end end return output end local function raw(input) return input end local function equals_by_value(a, b) return a == b end local function equals_by_table_values(a, b) if type(a) ~= 'table' or type(b) ~= 'table' then return a == b end if size(a) ~= size(b) then return false end for index, value in pairs(a) do local value_b = b[index] if value_b == nil or value ~= value_b then return false end end for index, value in pairs(b) do local value_a = a[index] if value_a == nil or value ~= value_a then return false end end return true end local function color_to_scalar(input) if type(input) ~= 'table' then return '' end local out = {} local i = 0 for _, value in pairs(input) do i = i + 1 out[i] = value end return concat(out, ' ') end --- accepts either a table or a string --- string must be in an "r g b" or "r g b a" format --- optionally a preset name may be given instead (from resources/color_presets.lua) --- table must contain the "r", "g" and "b" keys and may optionally contain an "a" key --- the output will always be a valid color table for Factorio local function color_sanitizer(input) if input == nil or input == '' then return true, nil end local input_type = type(input) if input_type == 'string' then local color = Color[input:match('^%s*(.-)%s*$'):gsub(' ', '_')] if color then return true, to_valid_rgba_table(color) end local data = {} local index = 0 for value in gmatch(input, '%S+') do index = index + 1 if index < 5 then value = tonumber(value) if value == nil then return false, {'redmew_settings_util.color_invalid_string_value'} end if value < 0 then value = 0 end if value > 255 then value = 255 end data[color_key_table[index]] = value end end if size(data) < 3 then return false, {'redmew_settings_util.color_invalid_string_value'} end return true, to_valid_rgba_table(data) end if input_type == 'table' then local table_size = size(input) if table_size < 3 or table_size > 4 then return false, {'redmew_settings_util.color_invalid_table_value'} end return true, to_valid_rgba_table({ r = input.r, g = input.g, b = input.b, a = input.a, }) end return false, {'redmew_settings_util.invalid_color_value'} end --- Contains a set of callables that will attempt to sanitize and transform the input --- sanitizer = takes any raw input and converts it to the final value used and stored --- to_string = takes stored input and converts it to its string representation return { fraction = { equals = equals_by_value, toScalar = raw, sanitizer = function(input) input = tonumber(input) if input == nil then return false, {'redmew_settings_util.fraction_invalid_value'} end if input < 0 then input = 0 end if input > 1 then input = 1 end return true, input end }, integer = { equals = equals_by_value, toScalar = raw, sanitizer = function(input) input = tonumber(input) if input == nil then return false, {'redmew_settings_util.integer_invalid_value'} end return true, floor(input) end }, positive_integer = { equals = equals_by_value, toScalar = raw, sanitizer = function(input) input = tonumber(input) if input == nil or input <= 0 then return false, {'redmew_settings_util.integer_invalid_value'} end return true, floor(input) end }, string = { equals = equals_by_value, toScalar = raw, sanitizer = function(input) if input == nil then return true, '' end local input_type = type(input) if input_type == 'string' then return true, input end if input_type == 'number' or input_type == 'boolean' then return true, tostring(input) end return false, {'redmew_settings_util.string_invalid_value'} end }, boolean = { equals = equals_by_value, toScalar = raw, sanitizer = function(input) local input_type = type(input) if input_type == 'boolean' then return true, input end if input_type == 'string' then if input == '0' or input == '' or input == 'false' or input == 'no' then return true, false end if input == '1' or input == 'true' or input == 'yes' then return true, true end return true, tonumber(input) ~= nil end if input_type == 'number' then return true, input ~= 0 end return false, {'redmew_settings_util.boolean_invalid_value'} end }, color = { equals = equals_by_table_values, toScalar = color_to_scalar, sanitizer = color_sanitizer }, chat_color = { equals = equals_by_table_values, toScalar = color_to_scalar, sanitizer = function(input) local suc, value = color_sanitizer(input) if not suc then return false, value end if not value then return true, nil end local r, g, b = value.r, value.g, value.b local brightness = sqrt(0.241 * r * r + 0.691 * g * g, 0.068 * b * b) brightness = floor(brightness) if brightness < 50 then return false, {'redmew_settings_util.chat_color_too_dark'} end return true, value end } }