diff --git a/vmf/scripts/mods/vmf/modules/core/localization.lua b/vmf/scripts/mods/vmf/modules/core/localization.lua index 48890fa..3c7fb08 100644 --- a/vmf/scripts/mods/vmf/modules/core/localization.lua +++ b/vmf/scripts/mods/vmf/modules/core/localization.lua @@ -85,6 +85,17 @@ vmf.load_mod_localization = function (mod, localization_table) _localization_database[mod:get_name()] = localization_table end +-- Localize without parameters and return nil instead of if nothing found +vmf.quick_localize = function (mod, text_id) + local mod_localization_table = _localization_database[mod:get_name()] + if mod_localization_table then + local text_translations = mod_localization_table[text_id] + if text_translations then + return text_translations[_language_id] or text_translations["en"] + end + end +end + -- #################################################################################################################### -- ##### Script ####################################################################################################### -- #################################################################################################################### diff --git a/vmf/scripts/mods/vmf/modules/core/options.lua b/vmf/scripts/mods/vmf/modules/core/options.lua index f41828e..8542dbc 100644 --- a/vmf/scripts/mods/vmf/modules/core/options.lua +++ b/vmf/scripts/mods/vmf/modules/core/options.lua @@ -1,3 +1,335 @@ local vmf = get_mod("VMF") -vmf.options_widgets_definition = {} \ No newline at end of file +-- @TODO: rename stupid original field names +-- @TODO: rename to options_widget_data and probably add mod instead of mod name. Meaning adding mod data after +-- widget initialization? +vmf.options_widgets_definition = {} + +-- ##################################################################################################################### +-- ##### Local functions ############################################################################################### +-- ##################################################################################################################### + +---------------- +-- VALIDATION -- +---------------- + +local function validate_generic_widget_data(mod, data) + --[[ + string: + data.setting_id + data.title (optional if localize) + data.tooltip (optional) + ]] +end + + +local function validate_checkbox_data(mod, data) + +end + + +local function validate_dropdown_data(mod, data) + + -- default value - something? + -- options - table + -- options.text - string + -- options.value - something + some of them is default value + -- options.show_widgets - table + +end + + +local function validate_keybind_data(mod, data) + +end + + +local function validate_numeric_data(mod, data) + +end + +------------------ +-- LOCALIZATION -- +------------------ + +local function localize_generic_widget_data(mod, data) + if data.localize then + data.text = mod:localize(data.text or data.setting_name) + if data.tooltip then + data.tooltip = mod:localize(data.tooltip) + else + data.tooltip = vmf.quick_localize(mod, data.setting_name .. "_description") + end + end +end + + +local function localize_dropdown_data(mod, data) + local options = data.options + local localize = data.localize + if options.localize ~= nil then + localize = options.localize + end + if localize then + for _, option in ipairs(options) do + option.text = mod:localize(option.text) + end + end +end + +-------------------- +-- INITIALIZATION -- +-------------------- + +local function initialize_header_data(mod, data) + local new_data = {} + new_data.widget_index = data.widget_index + new_data.mod_name = mod:get_name() + new_data.readable_mod_name = mod:get_readable_name() + new_data.tooltip = mod:get_description() + new_data.is_mod_toggable = mod:get_internal_data("is_togglable") and not mod:get_internal_data("is_mutator") + new_data.is_widget_collapsed = vmf:get("options_menu_collapsed_mods")[mod:get_name()] + new_data.is_favorited = vmf:get("options_menu_favorite_mods")[mod:get_name()] + + new_data.widget_type = data.type + return new_data +end + + +-- The data that applies to any widget, except for header +local function initialize_generic_widget_data(mod, data, localize) + local new_data = {} + + -- Automatically generated values + new_data.widget_index = data.widget_index + new_data.parent_widget_number = data.parent_widget_index + new_data.widget_level = data.depth + new_data.mod_name = mod:get_name() + + -- Defined in widget + new_data.widget_type = data.type + new_data.setting_name = data.setting_id + new_data.text = data.title -- optional, if (localize == true) + new_data.tooltip = data.tooltip -- optional + new_data.default_value = data.default_value + + -- Overwrite global optons localization setting if widget defined it + if data.localize == nil then + new_data.localize = localize + else + new_data.localize = data.localize + end + + validate_generic_widget_data(mod, new_data) + localize_generic_widget_data(mod, new_data) + + return new_data +end + + +local function initialize_group_data(mod, data, localize, collapsed_widgets) + local new_data = initialize_generic_widget_data(mod, data, localize) + + new_data.is_widget_collapsed = collapsed_widgets[data.setting_id] + + return new_data +end + + +local function initialize_checkbox_data(mod, data, localize, collapsed_widgets) + local new_data = initialize_generic_widget_data(mod, data, localize) + + new_data.is_widget_collapsed = collapsed_widgets[data.setting_id] + + validate_checkbox_data(mod, new_data) + + return new_data +end + + +local function initialize_dropdown_data(mod, data, localize, collapsed_widgets) + local new_data = initialize_generic_widget_data(mod, data, localize) + + new_data.is_widget_collapsed = collapsed_widgets[data.setting_id] + new_data.options = data.options + + validate_dropdown_data(mod, new_data) + localize_dropdown_data(mod, new_data) + + -- Converting show_widgets from human-readable form to vmf-options-readable + -- i.e. {[1] = 2, [2] = 3, [3] = 5} -> {[113] = true, [114] = true, [116] = true} + -- Where the 2nd set of numbers are the real widget numbers of subwidgets + if data.sub_widgets ~= nil then + for i, option in ipairs(data.options) do + if option.show_widgets then + local new_show_widgets = {} + for j, sub_widget_index in ipairs(option.show_widgets) do + if data.sub_widgets[sub_widget_index] then + new_show_widgets[data.sub_widgets[sub_widget_index].widget_index] = true + else + error(string.format("'widget \"%s\" (dropdown) -> options -> [%d] -> show_widgets -> [%d] \"%s\"' points" .. + " to non-existing sub_widget", data.setting_id, i, j, sub_widget_index)) + end + end + option.show_widgets = new_show_widgets + end + end + end + + return new_data +end + + +local function initialize_keybind_data(mod, data, localize) + local new_data = initialize_generic_widget_data(mod, data, localize) + + new_data.keybind_global = data.keybind_global + new_data.keybind_trigger = data.keybind_trigger + new_data.keybind_type = data.keybind_type + new_data.action = data.action_name + new_data.view_name = data.view_name + + validate_keybind_data(mod, new_data) + + return new_data +end + + +local function initialize_numeric_data(mod, data, localize) + local new_data = initialize_generic_widget_data(mod, data, localize) + + new_data.unit_text = data.unit_text + new_data.range = data.range + new_data.decimals_number = data.decimals_number + + validate_numeric_data(mod, new_data) + + return new_data +end + + +local function initialize_widget_data(mod, data, localize, collapsed_widgets) + if data.type == "header" then + return initialize_header_data(mod, data) + elseif data.type == "group" then + return initialize_group_data(mod, data, localize, collapsed_widgets) + elseif data.type == "checkbox" then + return initialize_checkbox_data(mod, data, localize, collapsed_widgets) + elseif data.type == "dropdown" then + return initialize_dropdown_data(mod, data, localize, collapsed_widgets) + elseif data.type == "keybind" then + return initialize_keybind_data(mod, data, localize) + elseif data.type == "numeric" then + return initialize_numeric_data(mod, data, localize) + else + -- @TODO: throw an error or something + end +end + +----------- +-- OTHER -- +----------- + +-- unfold nested table? +local function unfold_table(unfolded_table, unfoldable_table, parent_widget_index, depth) + for i = 1, #unfoldable_table do + local nested_table = unfoldable_table[i] + if type(nested_table) == "table" then + table.insert(unfolded_table, nested_table) + nested_table.depth = depth + nested_table.widget_index = #unfolded_table + nested_table.parent_widget_index = parent_widget_index + local nested_table_sub_widgets = nested_table.sub_widgets + if nested_table_sub_widgets then + if type(nested_table_sub_widgets) == "table" then + unfold_table(unfolded_table, nested_table_sub_widgets, #unfolded_table, depth + 1) + else + vmf:dump(unfolded_table, "widgets", 1) + error(string.format("'sub_widgets' field of widget [%d] is not a table, it's %s. " .. + "See dumped table in game log for reference.", #unfolded_table, + type(nested_table_sub_widgets)), 0) + end + end + else + vmf:dump(unfolded_table, "widgets", 1) + error(string.format("sub_widget#%d of widget [%d] is not a table, it's %s. " .. + "See dumped table in game log for reference.", i, parent_widget_index, + type(nested_table)), 0) + end + end + return unfolded_table +end + + +local function initialize_mod_options_widgets_data(mod, widgets_data, localize) + local initialized_data = {} + + -- Define widget data for header, because it's not up to modders to define it. + local header_widget_data = {type = "header", widget_index = 1, sub_widgets = widgets_data} + -- Put data of all widgets in one-dimensional array in order they will be displayed in mod options. + local unfolded_raw_widgets_data = unfold_table({header_widget_data}, widgets_data, 1, 1) + -- Load info about widgets previously collapsed by user + local collapsed_widgets = vmf:get("options_menu_collapsed_widgets")[mod:get_name()] or {} + + for _, widget_data in ipairs(unfolded_raw_widgets_data) do + table.insert(initialized_data, initialize_widget_data(mod, widget_data, localize, collapsed_widgets)) + end + + -- Set setting to default value that were not set before (skipping header) + -- Also, initialize keybinds + for i = 2, #initialized_data do + local data = initialized_data[i] + if mod:get(data.setting_name) == nil then + mod:set(data.setting_name, data.default_value) + end + if data.widget_type == "keybind" then + mod:keybind(data.setting_name, data.action, mod:get(data.setting_name)) + end + end + + table.insert(vmf.options_widgets_definition, initialized_data) + + -- @DEBUG: + mod:dump(unfolded_raw_widgets_data, "unfolded_raw_widgets_data", 1) +end + +-- ##################################################################################################################### +-- ##### VMF internal functions and variables ########################################################################## +-- ##################################################################################################################### + +vmf.initialize_mod_options = function (mod, options) + -- Global localization (for all options elements) ('true' by defualt) + local localize_options_global = options.localize ~= false + -- Options widgets localization (inherits from global one, unless defined in 'widgets' table) + local localize_options_widgets = localize_options_global + if options.widgets.localize ~= nil then + localize_options_widgets = options.widgets.localize + end + + -- @TODO: remove "vmf" + local success, value = vmf:pcall(initialize_mod_options_widgets_data, mod, options.widgets, localize_options_widgets) + if not success then + mod:error("Could not initialize options widgets, options initialization aborted: %s", value) + return + end + + -- @DEBUG: + mod:echo("INITIALIZE OPTIONS") + return true +end + +-- ##################################################################################################################### +-- ##### Script ######################################################################################################## +-- ##################################################################################################################### + +if type(vmf:get("options_menu_favorite_mods")) ~= "table" then + vmf:set("options_menu_favorite_mods", {}) +end + +if type(vmf:get("options_menu_collapsed_mods")) ~= "table" then + vmf:set("options_menu_collapsed_mods", {}) +end + +if type(vmf:get("options_menu_collapsed_widgets")) ~= "table" then + vmf:set("options_menu_collapsed_widgets", {}) +end \ No newline at end of file diff --git a/vmf/scripts/mods/vmf/modules/legacy/options.lua b/vmf/scripts/mods/vmf/modules/legacy/options.lua index 425bd67..6f8145c 100644 --- a/vmf/scripts/mods/vmf/modules/legacy/options.lua +++ b/vmf/scripts/mods/vmf/modules/legacy/options.lua @@ -1,26 +1,7 @@ local vmf = get_mod("VMF") --- @TODO: Copypasted it from vmf_options_view for now. Decide what to do with this -local function build_keybind_string(keys) - local keybind_string = "" - for i, key in ipairs(keys) do - if i == 1 then - keybind_string = keybind_string .. vmf.readable_key_names[key] - else - keybind_string = keybind_string .. " + " .. vmf.readable_key_names[key] - end - end - return keybind_string -end - - --- #################################################################################################################### --- ##### VMFMod ####################################################################################################### --- #################################################################################################################### - - -vmf.initialize_options_legacy = function (mod, widgets_definition) +vmf.initialize_mod_options_legacy = function (mod, widgets_definition) local mod_settings_list_widgets_definitions = {} @@ -40,18 +21,16 @@ vmf.initialize_options_legacy = function (mod, widgets_definition) new_widget_definition = {} - new_widget_definition.widget_type = "header" - new_widget_definition.widget_index = new_widget_index - new_widget_definition.mod_name = mod:get_name() - new_widget_definition.readable_mod_name = mod:get_readable_name() - new_widget_definition.tooltip = mod:get_description() - new_widget_definition.default = true - new_widget_definition.is_mod_toggable = mod:get_internal_data("is_togglable") and + new_widget_definition.widget_type = "header" + new_widget_definition.widget_index = new_widget_index + new_widget_definition.mod_name = mod:get_name() + new_widget_definition.readable_mod_name = mod:get_readable_name() + new_widget_definition.tooltip = mod:get_description() + new_widget_definition.default = true + new_widget_definition.is_mod_toggable = mod:get_internal_data("is_togglable") and not mod:get_internal_data("is_mutator") + new_widget_definition.is_widget_collapsed = vmf:get("options_menu_collapsed_mods")[mod:get_name()] - if mod_collapsed_widgets then - new_widget_definition.is_widget_collapsed = mod_collapsed_widgets[mod:get_name()] - end if options_menu_favorite_mods then for _, current_mod_name in pairs(options_menu_favorite_mods) do @@ -68,6 +47,8 @@ vmf.initialize_options_legacy = function (mod, widgets_definition) if widgets_definition then + mod:info("(options): using legacy widget definitions") + local level = 1 local parent_number = new_widget_index local parent_widget = {["widget_type"] = "header", ["sub_widgets"] = widgets_definition} @@ -114,7 +95,6 @@ vmf.initialize_options_legacy = function (mod, widgets_definition) if current_widget.widget_type == "keybind" then local keybind = mod:get(current_widget.setting_name) - new_widget_definition.keybind_text = build_keybind_string(keybind) if current_widget.action then mod:keybind(current_widget.setting_name, current_widget.action, keybind) end diff --git a/vmf/scripts/mods/vmf/modules/ui/options/vmf_options_view.lua b/vmf/scripts/mods/vmf/modules/ui/options/vmf_options_view.lua index 90de63e..aee3013 100644 --- a/vmf/scripts/mods/vmf/modules/ui/options/vmf_options_view.lua +++ b/vmf/scripts/mods/vmf/modules/ui/options/vmf_options_view.lua @@ -1723,12 +1723,14 @@ local function create_dropdown_widget(widget_definition, scenegraph_id, scenegra local show_widget_condition = create_show_widget_condition(widget_definition) - local options_texts = {} - local options_values = {} + local options_texts = {} + local options_values = {} + local options_shown_widgets = {} - for _, option in ipairs(widget_definition.options) do - table.insert(options_texts, option.text) - table.insert(options_values, option.value) + for i, option in ipairs(widget_definition.options) do + options_texts[i] = option.text + options_values[i] = option.value + options_shown_widgets[i] = option.show_widgets or {} end local definition = { @@ -1912,9 +1914,11 @@ local function create_dropdown_widget(widget_definition, scenegraph_id, scenegra options_texts = options_texts, options_values = options_values, + options_shown_widgets = options_shown_widgets, total_options_number = #options_texts, current_option_number = 1, current_option_text = options_texts[1], + current_shown_widgets = nil, -- if nil, all subwidgets are shown default_value = widget_definition.default_value, parent_widget_number = widget_definition.parent_widget_number, show_widget_condition = show_widget_condition @@ -3223,36 +3227,46 @@ VMFOptionsView.callback_hide_sub_widgets = function (self, widget_content) widget_content.is_widget_collapsed = is_widget_collapsed_new - setting_name = setting_name or mod_name -- header + if setting_name then - local all_collapsed_widgets = vmf:get("options_menu_collapsed_widgets") + local all_collapsed_widgets = vmf:get("options_menu_collapsed_widgets") - local mod_collapsed_widgets = all_collapsed_widgets[mod_name] + local mod_collapsed_widgets = all_collapsed_widgets[mod_name] - if widget_content.is_widget_collapsed then + if widget_content.is_widget_collapsed then - mod_collapsed_widgets = mod_collapsed_widgets or {} - mod_collapsed_widgets[setting_name] = true + mod_collapsed_widgets = mod_collapsed_widgets or {} + mod_collapsed_widgets[setting_name] = true - all_collapsed_widgets[mod_name] = mod_collapsed_widgets - else - if mod_collapsed_widgets then - mod_collapsed_widgets[setting_name] = nil + all_collapsed_widgets[mod_name] = mod_collapsed_widgets + else + if mod_collapsed_widgets then + mod_collapsed_widgets[setting_name] = nil - local is_collapsed_widgets_list_empty = true + local is_collapsed_widgets_list_empty = true - for _, _ in pairs(mod_collapsed_widgets) do - is_collapsed_widgets_list_empty = false - end + for _, _ in pairs(mod_collapsed_widgets) do + is_collapsed_widgets_list_empty = false + end - if is_collapsed_widgets_list_empty then - all_collapsed_widgets[mod_name] = nil + if is_collapsed_widgets_list_empty then + all_collapsed_widgets[mod_name] = nil + end end end + + vmf:set("options_menu_collapsed_widgets", all_collapsed_widgets) + + -- header + else + local collapsed_mods = vmf:get("options_menu_collapsed_mods") + if widget_content.is_widget_collapsed then + collapsed_mods[mod_name] = true + else + collapsed_mods[mod_name] = nil + end + vmf:set("options_menu_collapsed_mods", collapsed_mods) end - - vmf:set("options_menu_collapsed_widgets", all_collapsed_widgets) - self:update_settings_list_widgets_visibility(mod_name) self:readjust_visible_settings_list_widgets_position() end @@ -3433,6 +3447,7 @@ VMFOptionsView.callback_draw_dropdown_menu = function (self, widget_content) widget_content.current_option_number = hotspot_content.num widget_content.current_option_text = widget_content.options_texts[widget_content.current_option_number] + widget_content.current_shown_widgets = widget_content.options_shown_widgets[widget_content.current_option_number] return true end @@ -3811,6 +3826,7 @@ VMFOptionsView.update_picked_option_for_settings_list_widgets = function (self) if loaded_setting_value == option_value then widget_content.current_option_number = i widget_content.current_option_text = widget_content.options_texts[i] + widget_content.current_shown_widgets = widget_content.options_shown_widgets[i] setting_not_found = false break @@ -3827,6 +3843,7 @@ VMFOptionsView.update_picked_option_for_settings_list_widgets = function (self) if widget_content.default_value == option_value then widget_content.current_option_number = i widget_content.current_option_text = widget_content.options_texts[i] + widget_content.current_shown_widgets = widget_content.options_shown_widgets[i] get_mod(widget_content.mod_name):set(widget_content.setting_name, widget_content.default_value) end end @@ -3847,6 +3864,8 @@ VMFOptionsView.update_picked_option_for_settings_list_widgets = function (self) widget_content.keys = widget_content.default_value end + widget_content.keybind_text = build_keybind_string(widget_content.keys) + elseif widget_type == "numeric" then loaded_setting_value = get_mod(widget_content.mod_name):get(widget_content.setting_name) @@ -3880,7 +3899,7 @@ VMFOptionsView.update_settings_list_widgets_visibility = function (self, mod_nam if not mod_name or mod_widgets[1].content.mod_name == mod_name then - for _, widget in ipairs(mod_widgets) do + for i, widget in ipairs(mod_widgets) do if widget.content.parent_widget_number then local parent_widget = mod_widgets[widget.content.parent_widget_number] @@ -3894,9 +3913,12 @@ VMFOptionsView.update_settings_list_widgets_visibility = function (self, mod_nam -- if 'dropdown' elseif widget_type == "dropdown" then if widget.content.show_widget_condition then - widget.content.is_widget_visible = widget.content.show_widget_condition[parent_widget.content.current_option_number] and parent_widget.content.is_widget_visible and not parent_widget.content.is_widget_collapsed + widget.content.is_widget_visible = (widget.content.show_widget_condition[parent_widget.content.current_option_number] or parent_widget.content.current_shown_widgets[i]) and parent_widget.content.is_widget_visible and not parent_widget.content.is_widget_collapsed + + -- Usually it had to throw an error by this point, but now it's another part of compatibility else - get_mod(widget.content.mod_name):error("(vmf_options_view): the dropdown widget in the options menu has sub_widgets, but some of its sub_widgets doesn't have 'show_widget_condition' (%s)" , widget.content.setting_name) + widget.content.is_widget_visible = parent_widget.content.current_shown_widgets[i] and parent_widget.content.is_widget_visible and not parent_widget.content.is_widget_collapsed + --get_mod(widget.content.mod_name):error("(vmf_options_view): the dropdown widget in the options menu has sub_widgets, but some of its sub_widgets doesn't have 'show_widget_condition' (%s)" , widget.content.setting_name) end -- if 'group' else @@ -4267,14 +4289,6 @@ end vmf.load_vmf_options_view_settings() -if type(vmf:get("options_menu_favorite_mods")) ~= "table" then - vmf:set("options_menu_favorite_mods", {}) -end - -if type(vmf:get("options_menu_collapsed_widgets")) ~= "table" then - vmf:set("options_menu_collapsed_widgets", {}) -end - diff --git a/vmf/scripts/mods/vmf/modules/vmf_mod_manager.lua b/vmf/scripts/mods/vmf/modules/vmf_mod_manager.lua index 562504b..1d107e7 100644 --- a/vmf/scripts/mods/vmf/modules/vmf_mod_manager.lua +++ b/vmf/scripts/mods/vmf/modules/vmf_mod_manager.lua @@ -56,7 +56,7 @@ function new_mod(mod_name, mod_resources) local success, localization_table = vmf.xpcall_dofile(mod, "(new_mod)('mod_localization' initialization)", mod_resources.mod_localization) if success then - vmf.load_mod_localization(mod, localization_table) + vmf.load_mod_localization(mod, localization_table) -- @TODO: return here if not sucessful?, rename to "initialize_" else return end @@ -66,14 +66,12 @@ function new_mod(mod_name, mod_resources) if mod_resources.mod_data then local success, mod_data_table = vmf.xpcall_dofile(mod, "(new_mod)('mod_data' initialization)", mod_resources.mod_data) - if success then - vmf.initialize_mod_data(mod, mod_data_table) - else + if success and not vmf.initialize_mod_data(mod, mod_data_table) then return end end - -- Load mod + -- Load mod @TODO: what will happen if mod_resources.mod_script == nil? if not vmf.xpcall_dofile(mod, "(new_mod)('mod_script' initialization)", mod_resources.mod_script) then return end @@ -121,10 +119,12 @@ function vmf.initialize_mod_data(mod, mod_data) end if mod_data.options then - vmf.initialize_options(mod, mod_data.options_widgets) + if not vmf.initialize_mod_options(mod, mod_data.options) then + return + end -- @TODO: move the 2nd block to the upper statement elseif mod_data.options_widgets or (mod_data.is_togglable and not mod_data.is_mutator) then - vmf.initialize_options_legacy(mod, mod_data.options_widgets) + vmf.initialize_mod_options_legacy(mod, mod_data.options_widgets) end if type(mod_data.custom_gui_textures) == "table" then @@ -150,6 +150,8 @@ function vmf.initialize_mod_data(mod, mod_data) end end end + + return true end -- VARIABLES