diff --git a/config.lua b/config.lua index 95398b29..2b9d8e9c 100644 --- a/config.lua +++ b/config.lua @@ -276,9 +276,6 @@ global.config = { donator_commands = { enabled = true }, - player_colors = { - enabled = true - }, -- adds a command to generate a popup dialog box for players to see, useful for important announcements popup = { enabled = true diff --git a/control.lua b/control.lua index 92a78eff..fbe70568 100644 --- a/control.lua +++ b/control.lua @@ -27,6 +27,7 @@ require 'features.server_commands' require 'features.player_create' require 'features.rank_system' require 'features.redmew_settings_sync' +require 'features.player_colors' -- Feature modules -- Each can be disabled safely @@ -60,9 +61,6 @@ end if config.nuke_control.enabled then require 'features.nuke_control' end -if config.player_colors.enabled then - require 'features.player_colors' -end if config.reactor_meltdown.enabled then require 'features.reactor_meltdown' end diff --git a/features/gui/redmew_settings.lua b/features/gui/redmew_settings.lua index 444f0225..eb646de1 100644 --- a/features/gui/redmew_settings.lua +++ b/features/gui/redmew_settings.lua @@ -123,7 +123,7 @@ local function draw_main_frame(center, player) label_style.height = 35 label_style.vertical_align = 'center' - local value = Settings.get(player_index, name) + local value = Settings.toScalar(name, Settings.get(player_index, name)) local input_container = setting_grid.add({type = 'flow'}) local input_container_style = input_container.style input_container_style.height = 35 @@ -238,7 +238,8 @@ local function setting_set(event) return end - local element_data = data[event.setting_name] + local setting_name = event.setting_name + local element_data = data[setting_name] if not element_data then return @@ -249,8 +250,8 @@ local function setting_set(event) -- for some reason it has been removed already return end - set_element_value(input, event.new_value) - element_data.previous_value = event.old_value + set_element_value(input, Settings.toScalar(setting_name, event.new_value)) + element_data.previous_value = Settings.toScalar(setting_name, event.old_value) end Gui.on_custom_close(main_frame_name, function(event) diff --git a/features/player_colors.lua b/features/player_colors.lua index d3f85c89..cb2aa749 100644 --- a/features/player_colors.lua +++ b/features/player_colors.lua @@ -1,84 +1,75 @@ local Event = require 'utils.event' -local Command = require 'utils.command' local Server = require 'features.server' local Token = require 'utils.token' -local Utils = require 'utils.core' -local Ranks = require 'resources.ranks' +local Settings = require 'utils.redmew_settings' -local serialize = serpent.line +local player_color_name = 'player-color' +local player_chat_color_name = 'player-chat-color' +Settings.register(player_color_name, Settings.types.color, nil, 'player_colors.player_color_setting_label') +Settings.register(player_chat_color_name, Settings.types.color, nil, 'player_colors.player_chat_color_setting_label') local Public = {} -local color_callback = - Token.register( - function(data) - local key = data.key - local value = data.value - if not value then - return - end - local player = game.players[key] - if not player then - return - end - player.chat_color = value.chat_color - player.color = value.color - end -) +-- left in for migration purposes, remove at a later point +local color_callback = Token.register(function(data) + local key = data.key + local value = data.value + + if not value then + return + end + + local player = game.players[key] + if not player then + return + end + + Settings.set(player.index, player_color_name, value.color) + Settings.set(player.index, player_chat_color_name, value.chat_color) + +end) + +local function setting_set(event) + local value = event.new_value + if not value then + return + end + + local setting_name = event.setting_name + if setting_name ~= player_color_name and setting_name ~= player_chat_color_name then + return + end + + local player = game.get_player(event.player_index) + if not player or not player.valid then + return + end + + if setting_name == player_color_name then + player.color = value + end + + if setting_name == player_chat_color_name then + player.chat_color = value + end +end + +local function player_joined_game(event) + local player_index = event.player_index + local player = game.get_player(player_index) + if not player or not player.valid then + return + end + + -- already migrated + if Settings.get(player_index, player_color_name) then + return + end ---- Attempts to retrieve and get the saved color of a LuaPlayer -function Public.recall_player_color(player) Server.try_get_data('colors', player.name, color_callback) end ---- Assigns LuaPlayer random RGB values for color and player_color and returns the RGB table. -function Public.set_random_color(player) - return { - chat_color = Utils.set_and_return(player, 'chat_color', Utils.random_RGB()), - color = Utils.set_and_return(player, 'color', Utils.random_RGB()) - } -end - -Command.add( - 'redmew-color', - { - description = {'command_description.redmew_color'}, - arguments = {'set-reset-random'}, - required_rank = Ranks.regular - }, - function(args, player) - local player_name = player.name - local arg = args['set-reset-random'] - if arg == 'set' then - local data = { - color = player.color, - chat_color = player.chat_color - } - Server.set_data('colors', player_name, data) - player.print({'player_colors.color_saved'}) - Utils.print_except({'player_colors.color_saved_advert', player_name}) - elseif arg == 'reset' then - Server.set_data('colors', player_name, nil) - player.print({'player_colors.color_reset'}) - elseif arg == 'random' then - local color_data = Public.set_random_color(player) - player.print({'player_colors.color_random', serialize(color_data)}) - else - player.print({'player_colors.fail_wrong_argument'}) - end - end -) - -Event.add( - defines.events.on_player_joined_game, - function(event) - local player = game.get_player(event.player_index) - if not player or not player.valid then - return - end - - Public.recall_player_color(player) - end -) +Event.add(defines.events.on_player_joined_game, player_joined_game) +Event.add(Settings.events.on_setting_set, setting_set) return Public diff --git a/locale/en/redmew_features.cfg b/locale/en/redmew_features.cfg index be78890c..6e5bd1a9 100644 --- a/locale/en/redmew_features.cfg +++ b/locale/en/redmew_features.cfg @@ -80,6 +80,8 @@ color_saved_advert=__1__ has saved their color server-side for future maps. You color_reset=Your saved color (if you had one) has been removed. color_random=Your color has been changed to: __1__ fail_wrong_argument=Only set, reset, and random are accepted arguments +player_color_setting_label=Character color +player_chat_color_setting_label=Chat color [performance] fail_wrong_argument=Scale must be a valid number ranging from 0.05 to 1 diff --git a/locale/en/redmew_utils.cfg b/locale/en/redmew_utils.cfg index 8d83a2e0..66cf02ab 100644 --- a/locale/en/redmew_utils.cfg +++ b/locale/en/redmew_utils.cfg @@ -29,8 +29,10 @@ print_admins=__1__(ADMIN) __2__: __3__ [gui_util] button_tooltip=Shows / hides the Redmew Gui buttons. - [redmew_settings_util] fraction_invalid_value=fraction setting type requires the input to be a valid number between 0 and 1. string_invalid_value=string setting type requires the input to be either a valid string or something that can be converted to a string. boolean_invalid_value=boolean setting type requires the input to be either a boolean, number or string that can be transformed to a boolean. +color_invalid_string_value=color setting type requires the input to be either a valid preset such as "red" or "green", or a valid "r g b" or "r g b a" value. +color_invalid_table_value=color setting type with a table requires a valid {r, g, b} or {r, g, b, a} table with these keys. +invalid_color_value=color setting type only supports strings or tables as value type. diff --git a/resources/setting_types.lua b/resources/setting_types.lua new file mode 100644 index 00000000..f76ff7e0 --- /dev/null +++ b/resources/setting_types.lua @@ -0,0 +1,161 @@ +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 color_key_table = {'r', 'g', 'b', 'a'} + +local function raw(input) + return input +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 = { + 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 + }, + string = { + 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 = { + 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 = { + toScalar = function (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 + sanitizer = function (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] + if color and tonumber(input) == nil then + -- we have some numeric keys in there + return true, 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 or value < 0 or value > 255 then + return false, {'redmew_settings_util.color_invalid_string_value'} + 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, data + end + + if input_type == 'table' then + if size(input) > 4 or not input.r or not input.g or not input.b then + return false, {'redmew_settings_util.color_invalid_table_value'} + end + + local data = { + r = input.r, + g = input.g, + b = input.b + } + if input.a then + data.a = input.a + end + + return true, data + end + + return false, {'redmew_settings_util.invalid_color_value'} + end + }, +} diff --git a/utils/command.lua b/utils/command.lua index 0faec01c..ad056b9b 100644 --- a/utils/command.lua +++ b/utils/command.lua @@ -15,6 +15,8 @@ local next = next local serialize = serpent.line local gmatch = string.gmatch local get_rank_name = Rank.get_rank_name +local pairs = pairs +local pcall = pcall local Command = {} @@ -24,12 +26,11 @@ local deprecated_command_alternatives = { ['tpplayer'] = 'tp ', ['tppos'] = 'tp', ['tpmode'] = 'tp mode', - ['color-redmew'] = 'redmew-color' } local notify_on_commands = { ['version'] = 'RedMew has a version as well, accessible via /redmew-version', - ['color'] = 'RedMew allows color saving and a color randomizer: check out /redmew-color', + ['color'] = 'You can also use the Redmew Settings (gear icon) to set the character and chat colors, this will be synchronized to all Redmew servers', ['ban'] = 'In case your forgot: please remember to include a message on how to appeal a ban' } diff --git a/utils/redmew_settings.lua b/utils/redmew_settings.lua index aa6b04af..09f24ca5 100644 --- a/utils/redmew_settings.lua +++ b/utils/redmew_settings.lua @@ -1,80 +1,29 @@ local Global = require 'utils.global' local Event = require 'utils.event' -local type = type local error = error -local tonumber = tonumber -local tostring = tostring local pairs = pairs local format = string.format +local tostring = tostring +local type = type local raise_event = script.raise_event --- Contains a set of callables that will attempt to sanitize and transform the input -local settings_type = { - fraction = 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, - string = 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 = 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, -} - +local settings_type = require 'resources.setting_types' local settings = {} local memory = {} -local raw_callback_setting = { - callback = function (input) - return true, input - end +local missing_setting = { + data_transformation = { + toScalar = function(input) + if type(input) ~= 'table' then + return input + end + + return tostring(input) + end, + sanitizer = function (input) + return true, input + end + } } Global.register(memory, function (tbl) memory = tbl end) @@ -94,7 +43,7 @@ Public.events = { on_setting_set = Event.generate_event_name('on_setting_set'), } -Public.types = {fraction = 'fraction', string = 'string', boolean = 'boolean'} +Public.types = {fraction = 'fraction', string = 'string', boolean = 'boolean', color = 'color'} ---Register a specific setting with a sensitization setting type. --- @@ -118,15 +67,15 @@ function Public.register(name, setting_type, default, localisation_key) error(format('Trying to register setting for "%s" while it has already been registered.', name), 2) end - local callback = settings_type[setting_type] - if not callback then - error(format('Trying to register setting for "%s" with type "%s" while this type does not exist.', name, setting_type), 2) + local data_transformation = settings_type[setting_type] + if not data_transformation then + error(format('Trying to register data_transformation for "%s" with type "%s" while this type does not exist.', name, setting_type), 2) end local setting = { type = setting_type, default = default, - callback = callback, + data_transformation = data_transformation, localised_string = localisation_key and {localisation_key} or name, } @@ -144,7 +93,7 @@ function Public.validate(name, value) return format('Setting "%s" does not exist.', name) end - local success, sanitized_value = setting.callback(value) + local success, sanitized_value = setting.data_transformation.sanitizer(value) if not success then return sanitized_value @@ -163,10 +112,10 @@ end function Public.set(player_index, name, value) local setting = settings[name] if not setting then - setting = raw_callback_setting + setting = missing_setting end - local success, sanitized_value = setting.callback(value) + local success, sanitized_value = setting.data_transformation.sanitizer(value) if not success then error(format('Setting "%s" failed: %s', name, sanitized_value), 2) @@ -217,6 +166,19 @@ function Public.get(player_index, name) return player_setting end +---Returns the string representation of a given value based on a setting name. +--- +---@param name string +---@param raw_value any +function Public.toScalar(name, raw_value) + local setting = settings[name] + if not setting then + setting = missing_setting + end + + return setting.data_transformation.toScalar(raw_value) +end + ---Returns a table of all settings for a given player in a key => value setup ---@param player_index number function Public.all(player_index)