diff --git a/vmf/core/import_config/texture_format_spec.config b/vmf/core/import_config/texture_format_spec.config deleted file mode 100644 index 8a09b05..0000000 --- a/vmf/core/import_config/texture_format_spec.config +++ /dev/null @@ -1,37 +0,0 @@ -formats = { - R8G8B8A8 = {compressed=false, alpha=true} - A8R8G8B8 = {compressed=false, alpha=true} - - R16G16B16A16F = {compressed=false, alpha=true} - R32G32B32A32F = {compressed=false, alpha=true, hidden=true} - - R32F = {compressed=false, alpha=true, hidden=true} - R16F = {compressed=false, alpha=true, hidden=true} - R16UNORM = {compressed=false, alpha=true, hidden=true} - R16G16F = {compressed=false, alpha=true, hidden=true} - - BC1 = {min_width=4, min_height=4, alpha=false, hidden=true} - BC2 = {min_width=4, min_height=4, alpha=false, hidden=true} - BC3 = {min_width=4, min_height=4, alpha=false, hidden=true} - BC4 = {min_width=4, min_height=4, alpha=false} - BC5 = {min_width=4, min_height=4, alpha=false} - BC7 = {min_width=4, min_height=4, alpha=true} - - DXT1 = {min_width=4, min_height=4, alpha=true} - DXT1a = {min_width=4, min_height=4, alpha=true} - DXT3 = {min_width=4, min_height=4, alpha=false} - DXT5 = {min_width=4, min_height=4, alpha=true} - DXT5n = {min_width=4, min_height=4, alpha=false} -} - -platforms = { - win32 = { - formats = [ - "R8G8B8A8", "A8R8G8B8", - "R16G16B16A16F", "R32G32B32A32F", - "R32F", "R16F", "R16UNORM", "R16G16F", - "BC1", "BC2", "BC3", "BC4", "BC5", - "DXT1", "DXT1a", "DXT3", "DXT5", "DXT5n" - ] - } -} diff --git a/vmf/core/physx_metadata/physx_334_win32.physx_metadata b/vmf/core/physx_metadata/physx_334_win32.physx_metadata deleted file mode 100644 index ec592bd..0000000 Binary files a/vmf/core/physx_metadata/physx_334_win32.physx_metadata and /dev/null differ diff --git a/vmf/core/physx_metadata/physx_334_win32_64bit.physx_metadata b/vmf/core/physx_metadata/physx_334_win32_64bit.physx_metadata deleted file mode 100644 index d1ac91a..0000000 Binary files a/vmf/core/physx_metadata/physx_334_win32_64bit.physx_metadata and /dev/null differ diff --git a/vmf/core/physx_metadata/physx_341_win32.physx_metadata b/vmf/core/physx_metadata/physx_341_win32.physx_metadata deleted file mode 100644 index 5fb35d4..0000000 Binary files a/vmf/core/physx_metadata/physx_341_win32.physx_metadata and /dev/null differ diff --git a/vmf/core/physx_metadata/physx_341_win32_64bit.physx_metadata b/vmf/core/physx_metadata/physx_341_win32_64bit.physx_metadata deleted file mode 100644 index 94fa8ba..0000000 Binary files a/vmf/core/physx_metadata/physx_341_win32_64bit.physx_metadata and /dev/null differ diff --git a/vmf/core/physx_metadata/physx_342_win32.physx_metadata b/vmf/core/physx_metadata/physx_342_win32.physx_metadata deleted file mode 100644 index 7097c7d..0000000 Binary files a/vmf/core/physx_metadata/physx_342_win32.physx_metadata and /dev/null differ diff --git a/vmf/core/physx_metadata/physx_342_win32_64bit.physx_metadata b/vmf/core/physx_metadata/physx_342_win32_64bit.physx_metadata deleted file mode 100644 index ace4922..0000000 Binary files a/vmf/core/physx_metadata/physx_342_win32_64bit.physx_metadata and /dev/null differ diff --git a/vmf/core/physx_metadata/physx_411_win32.physx_metadata b/vmf/core/physx_metadata/physx_411_win32.physx_metadata deleted file mode 100644 index 4812a4f..0000000 Binary files a/vmf/core/physx_metadata/physx_411_win32.physx_metadata and /dev/null differ diff --git a/vmf/core/physx_metadata/physx_411_win32_64bit.physx_metadata b/vmf/core/physx_metadata/physx_411_win32_64bit.physx_metadata deleted file mode 100644 index 4812a4f..0000000 Binary files a/vmf/core/physx_metadata/physx_411_win32_64bit.physx_metadata and /dev/null differ diff --git a/vmf/gui/vmf/vmf_atlas.dds b/vmf/gui/vmf/vmf_atlas.dds deleted file mode 100644 index a801211..0000000 Binary files a/vmf/gui/vmf/vmf_atlas.dds and /dev/null differ diff --git a/vmf/gui/vmf/vmf_atlas.texture b/vmf/gui/vmf/vmf_atlas.texture deleted file mode 100644 index 5051d37..0000000 --- a/vmf/gui/vmf/vmf_atlas.texture +++ /dev/null @@ -1,18 +0,0 @@ -common = { - input = { - filename = "gui/vmf/vmf_atlas" - } - output = { - apply_processing = true - correct_gamma = true - cut_alpha_threshold = 0.5 - enable_cut_alpha_threshold = false - format = "A8R8G8B8" - mipmap_filter = "kaiser" - mipmap_filter_wrap_mode = "mirror" - mipmap_keep_original = false - mipmap_num_largest_steps_to_discard = 0 - mipmap_num_smallest_steps_to_discard = 0 - srgb = true - } -} diff --git a/vmf/itemV1.cfg b/vmf/itemV1.cfg deleted file mode 100644 index b1554b5..0000000 --- a/vmf/itemV1.cfg +++ /dev/null @@ -1,7 +0,0 @@ -title = "[Beta] Vermintide Mod Framework"; -description = "The latest VMF version for testing purposes. It is intended for modders. Regular players should ingore it and subscribe to [url=https://steamcommunity.com/sharedfiles/filedetails/?id=1289946781]stable version[/url] instead."; -preview = "previewV1.jpg"; -content = "bundleV1"; -language = "english"; -visibility = "public"; -published_id = 1500136933L; diff --git a/vmf/itemV1_stable.cfg b/vmf/itemV1_stable.cfg deleted file mode 100644 index 4db4a79..0000000 --- a/vmf/itemV1_stable.cfg +++ /dev/null @@ -1,26 +0,0 @@ -title = "Vermintide Mod Framework"; -description = "The Vermintide Mod Framework (VMF) is an open-source, community-run framework of modules that provides enhanced modding capabilities and support. The framework is designed to be both independent and lightweight; making no changes to gameplay on its own. - -Mods created for the project may utilize: -[list] - [*]Mod options - [*]Shared function hooks - [*]Chat commands - [*]Keybinds - [*]Mutator support - [*]Network calls - [*]QHD+ UI re-scaling - [*]Rewritten, lightweight mod functions - [*]An on-event call system -[/list] - -The Vermintide Mod Framework originally started in Warhammer End Times: Vermintide as an unofficial modding platform. In the time since, VMF has been rewritten and redesigned with contributions from many unique members of the community; culminating in this unified project made for the arrival of official mod support. - -If you're interested in creating mods with VMF, please check out [url=https://vmf-docs.verminti.de/]the project's wiki[/url]. - -If you'd like to contribute to the code behind VMF, visit [url=https://github.com/Vermintide-Mod-Framework/Vermintide-Mod-Framework]the project's GitHub repository[/url]."; -preview = "previewV1_stable.jpg"; -content = "bundleV1"; -language = "english"; -visibility = "public"; -published_id = 1289946781L; diff --git a/vmf/itemV2.cfg b/vmf/itemV2.cfg deleted file mode 100644 index 7ce829e..0000000 --- a/vmf/itemV2.cfg +++ /dev/null @@ -1,9 +0,0 @@ -title = "[Beta] Vermintide Mod Framework"; -description = "The latest VMF version for testing purposes. It is intended for modders. Regular players should ingore it and subscribe to [url=https://steamcommunity.com/sharedfiles/filedetails/?id=1369573612]stable version[/url] instead."; -preview = "previewV2.png"; -content = "bundleV2"; -language = "english"; -visibility = "public"; -published_id = 1500112422L; -apply_for_sanctioned_status = false; -tags = ["Tools"]; diff --git a/vmf/itemV2_stable.cfg b/vmf/itemV2_stable.cfg deleted file mode 100644 index dfebc23..0000000 --- a/vmf/itemV2_stable.cfg +++ /dev/null @@ -1,26 +0,0 @@ -title = "Vermintide Mod Framework"; -description = "The Vermintide Mod Framework (VMF) is an open-source, community-run framework of modules that provides enhanced modding capabilities and support. The framework is designed to be both independent and lightweight; making no changes to gameplay on its own. - -Mods created for the project may utilize: -[list] - [*]Mod options - [*]Shared function hooks - [*]Chat commands - [*]Keybinds - [*]Mutator support (only available in Vermintide 1 at this time) - [*]Network calls - [*]Rewritten, lightweight mod functions - [*]An on-event call system -[/list] - -The Vermintide Mod Framework originally started in Warhammer End Times: Vermintide as an unofficial modding platform. In the time since, VMF has been rewritten and redesigned with contributions from many unique members of the community; culminating in this unified project made for the arrival of official mod support. - -If you're interested in creating mods with VMF, please check out [url=https://vmf-docs.verminti.de/]the project's wiki[/url]. - -If you'd like to contribute to the code behind VMF, visit [url=https://github.com/Vermintide-Mod-Framework/Vermintide-Mod-Framework]the project's GitHub repository[/url]."; -preview = "previewV2_stable.png"; -content = "bundleV2"; -language = "english"; -visibility = "public"; -tags = ["Tools"]; -published_id = 1369573612L; diff --git a/vmf/localization/vmf.lua b/vmf/localization/vmf.lua index a26c8f7..70158e5 100644 --- a/vmf/localization/vmf.lua +++ b/vmf/localization/vmf.lua @@ -98,6 +98,9 @@ return { es = "Personalizado", ru = "Пользовательские", }, + output_mode_notification = { + en = "'Notification' Output", + }, output_mode_echo = { en = "'Echo' Output", es = "Mensajes de 'Echo'", @@ -138,11 +141,23 @@ return { es = "Chat", ru = "Чат", }, + output_notification = { + en = "Notification", + }, output_log_and_chat = { en = "Log & Chat", es = "Registro (log) y chat", ru = "Лог и чат", }, + output_all = { + en = "All", + }, + output_log_and_notification = { + en = "Log & Notification", + }, + output_chat_and_notification = { + en = "Chat & Notification", + }, chat_history_enable = { en = "Chat Input History", es = "Historial de chat", @@ -224,12 +239,17 @@ return { "ВНИМАНИЕ: изменение этой настройки очистит вашу историю ввода.", }, - + chat_command_not_recognized = { + en = "Command not recognized", + }, clean_chat_history = { en = "cleans chat input history", es = "Borra el historial de usuario", ru = "очищает историю ввода", }, + clean_chat_notifications = { + en = "cleans chat notification alerts" + }, dev_console_opened = { en = "Developer console opened.", es = "Abierto la consola de desarrollo.", @@ -249,30 +269,21 @@ return { es = "No se proporcionó una descripción.", }, - -- Difficulties' names [V1] - easy = { - en = "Easy" + -- Difficulties' names + lowest = { + en = "Sedition" }, - normal = { - en = "Normal" + low = { + en = "Uprising" }, - hard = { - en = "Hard" + medium = { + en = "Malice" }, - harder = { - en = "Nightmare" + high = { + en = "Heresy" }, - hardest = { - en = "Cataclysm" - }, - survival_hard = { - en = "Veteran" - }, - survival_harder = { - en = "Champion" - }, - survival_hardest = { - en = "Heroic" + highest = { + en = "Damnation" }, -- Chat messages diff --git a/vmf/lua_preprocessor_defines.config b/vmf/lua_preprocessor_defines.config deleted file mode 100644 index 1a1add8..0000000 --- a/vmf/lua_preprocessor_defines.config +++ /dev/null @@ -1 +0,0 @@ -valid_tags = {} \ No newline at end of file diff --git a/vmf/materials/vmf/vmf_atlas.lua b/vmf/materials/vmf/vmf_atlas.lua deleted file mode 100644 index 1ce9904..0000000 --- a/vmf/materials/vmf/vmf_atlas.lua +++ /dev/null @@ -1,38 +0,0 @@ --- Image Source: -return { - header_fav_arrow = { - size = { 48, 48, }, - uv00 = { 0.181641, 0.880859, }, - uv11 = { 0.228516, 0.974609, }, - }, - header_fav_icon = { - size = { 48, 48, }, - uv00 = { 0.130859, 0.880859, }, - uv11 = { 0.177734, 0.974609, }, - }, - header_fav_icon_lit = { - size = { 48, 48, }, - uv00 = { 0.052734, 0.880859, }, - uv11 = { 0.099609, 0.974609, }, - }, - search_bar_icon = { - size = { 48, 48, }, - uv00 = { 0.001953, 0.880859, }, - uv11 = { 0.048828, 0.974609, }, - }, - map_view_party_button = { - size = { 128, 128, }, - uv00 = { 0.130859, 0.623047, }, - uv11 = { 0.255859, 0.873047, }, - }, - map_view_party_button_lit = { - size = { 128, 128, }, - uv00 = { 0.001953, 0.623047, }, - uv11 = { 0.126953, 0.873047, }, - }, - map_view_mutators_area = { - size = { 547, 313, }, - uv00 = { 0.001953, 0.003906, }, - uv11 = { 0.536133, 0.615234, }, - }, -} diff --git a/vmf/materials/vmf/vmf_atlas.material b/vmf/materials/vmf/vmf_atlas.material deleted file mode 100644 index 8e4bf44..0000000 --- a/vmf/materials/vmf/vmf_atlas.material +++ /dev/null @@ -1,26 +0,0 @@ -vmf_atlas = { - material_contexts = { - surface_material = "" - } - - shader = "gui:DIFFUSE_MAP" - - textures = { - diffuse_map = "gui/vmf/vmf_atlas" - } - - variables = { - } -} - -vmf_atlas_masked = { - material_contexts = { - surface_material = "" - } - shader = "gui_gradient:DIFFUSE_MAP:MASKED" - textures = { - diffuse_map = "gui/vmf/vmf_atlas" - } - variables = { - } -} diff --git a/vmf/previewV1.jpg b/vmf/previewV1.jpg deleted file mode 100644 index da73e93..0000000 Binary files a/vmf/previewV1.jpg and /dev/null differ diff --git a/vmf/previewV1_stable.jpg b/vmf/previewV1_stable.jpg deleted file mode 100644 index 6104d0b..0000000 Binary files a/vmf/previewV1_stable.jpg and /dev/null differ diff --git a/vmf/previewV2.png b/vmf/previewV2.png deleted file mode 100644 index 682b36b..0000000 Binary files a/vmf/previewV2.png and /dev/null differ diff --git a/vmf/previewV2_stable.png b/vmf/previewV2_stable.png deleted file mode 100644 index ff8be0e..0000000 Binary files a/vmf/previewV2_stable.png and /dev/null differ diff --git a/vmf/resource_packages/vmf.package b/vmf/resource_packages/vmf.package deleted file mode 100644 index d94302b..0000000 --- a/vmf/resource_packages/vmf.package +++ /dev/null @@ -1,28 +0,0 @@ -mod = [ - "vmf" -] - -package = [ - "resource_packages/vmf" -] - -material = [ - "materials/vmf/*" -] - -lua = [ - "localization/*" - - "scripts/mods/vmf/*" - "scripts/mods/vmf/modules/*" - "scripts/mods/vmf/modules/core/*" - "scripts/mods/vmf/modules/core/mutators/*" - "scripts/mods/vmf/modules/core/mutators/test/*" - "scripts/mods/vmf/modules/debug/*" - "scripts/mods/vmf/modules/gui/*" - "scripts/mods/vmf/modules/ui/options/*" - "scripts/mods/vmf/modules/ui/chat/*" - "scripts/mods/vmf/modules/ui/mutators/*" - - "materials/vmf/vmf_atlas" -] diff --git a/vmf/scripts/mods/vmf/modules/core/chat.lua b/vmf/scripts/mods/vmf/modules/core/chat.lua index 3d5e4ae..dea76e4 100644 --- a/vmf/scripts/mods/vmf/modules/core/chat.lua +++ b/vmf/scripts/mods/vmf/modules/core/chat.lua @@ -1,38 +1,18 @@ local vmf = get_mod("VMF") --- Constants used as parameters in some 'chat_manager's functions -local CHANNEL_ID = 1 -local MESSAGE_SENDER = "" -local LOCAL_PLAYER_ID = 0 -- VT2 only -local LOCALIZATION_PARAMETERS = {} -- VT2 only -local LOCALIZE = false -- VT2 only -local LOCALIZE_PARAMETERS = false -- VT2 only -local LOCALIZATION_PARAM = "" -- VT1 only -local IS_SYSTEM_MESSAGE = false -local POP_CHAT = true -local IS_DEV = true - -- ##################################################################################################################### -- ##### Local functions ############################################################################################### -- ##################################################################################################################### -local function send_system_message(peer_id, message) - if VT1 then - RPC.rpc_chat_message(peer_id, CHANNEL_ID, MESSAGE_SENDER, message, LOCALIZATION_PARAM, IS_SYSTEM_MESSAGE, POP_CHAT, - IS_DEV) - else - RPC.rpc_chat_message(PEER_ID_TO_CHANNEL[peer_id], CHANNEL_ID, MESSAGE_SENDER, LOCAL_PLAYER_ID, message, - LOCALIZATION_PARAMETERS, LOCALIZE, LOCALIZE_PARAMETERS, IS_SYSTEM_MESSAGE, POP_CHAT, IS_DEV, - Irc.PARTY_MSG) - end -end +local function broadcast_message(message, channel_tag) + local chat_manager = Managers.chat -local function add_system_message_to_chat(chat_manager, message) - if VT1 then - chat_manager:_add_message_to_list(CHANNEL_ID, MESSAGE_SENDER, message, IS_SYSTEM_MESSAGE, POP_CHAT, IS_DEV) - else - chat_manager:_add_message_to_list(CHANNEL_ID, MESSAGE_SENDER, LOCAL_PLAYER_ID, message, IS_SYSTEM_MESSAGE, POP_CHAT, - IS_DEV) + if chat_manager and channel_tag then + for channel_handle, channel in pairs(chat_manager:connected_chat_channels()) do + if channel and channel.tag == channel_tag then + chat_manager:send_channel_message(channel_handle, tostring(message)) + end + end end end @@ -43,26 +23,10 @@ end --[[ Broadcasts the message to all players in a lobby. * message [string]: message to broadcast + * channel_tag [string]: tag of target chat channel --]] -function VMFMod:chat_broadcast(message) - local chat = Managers.chat - if chat and chat:has_channel(1) then - if chat.is_server then - local members = chat:channel_members(CHANNEL_ID) - local my_peer_id = chat.my_peer_id - for _, member_peer_id in pairs(members) do - if member_peer_id ~= my_peer_id then - send_system_message(member_peer_id, message) - end - end - else - local host_peer_id = chat.host_peer_id - if host_peer_id then - send_system_message(host_peer_id, message) - end - end - add_system_message_to_chat(chat, message) - end +function VMFMod:chat_broadcast(message, channel_tag) + broadcast_message(message, channel_tag) end --[[ @@ -71,8 +35,6 @@ end * message [string] : message to send --]] function VMFMod:chat_whisper(peer_id, message) - local chat = Managers.chat - if chat and chat:has_channel(1) and chat.is_server and peer_id ~= chat.host_peer_id then - send_system_message(peer_id, message) - end + -- @TODO: Rewrite for Darktide + vmf:notify("Chat whisper is not yet implemented!") end diff --git a/vmf/scripts/mods/vmf/modules/core/hooks.lua b/vmf/scripts/mods/vmf/modules/core/hooks.lua index 3df68ba..b68bc09 100644 --- a/vmf/scripts/mods/vmf/modules/core/hooks.lua +++ b/vmf/scripts/mods/vmf/modules/core/hooks.lua @@ -8,7 +8,7 @@ local vmf = get_mod("VMF") local HOOK_TYPES = { hook = 1, hook_safe = 2, - hook_origin = 3, + hook_origin = 3 } -- Constants to ease on table lookups when not needed @@ -36,6 +36,7 @@ local _hooks = { setmetatable({}, auto_table_meta), -- safe {}, -- origin } +local _hooks_by_file = {} local _origs = {} -- #################################################################################################################### @@ -390,6 +391,33 @@ function VMFMod:hook_origin(obj, method, handler) return generic_hook(self, obj, method, handler, "hook_origin") end +-- :hook_file() allows you to hook a function across every past and future version of a game file, +-- allowing your handler to replace the function in the stack, +-- and control its execution. All hooks on the same function will be part of a chain, with the +-- original function at the end. Your handler has to call the next function in the chain manually. +-- The chain of event is determined by mod load order. +function VMFMod:hook_file(obj_str, method_str, handler) + -- Add hook create function to list for the file + _hooks_by_file[obj_str] = _hooks_by_file[obj_str] or {} + + local hook_create_func = function(this_filepath, this_index) + local dynamic_obj = + "vmf:get_require_store(\"" .. this_filepath .. "\")[" .. tostring(this_index) .. "]" + return generic_hook(self, dynamic_obj, method_str, handler, "hook") + end + table.insert(_hooks_by_file[obj_str], hook_create_func) + + -- Add the new hook to every instance of the file + local all_file_instances = vmf:get_require_store(obj_str) + if all_file_instances then + for i, item in ipairs(all_file_instances) do + if item then + hook_create_func(obj_str, i) + end + end + end +end + -- Enable/disable functions for all hook types: function VMFMod:hook_enable(obj, method) generic_hook_toggle(self, obj, method, true) @@ -439,3 +467,16 @@ vmf.apply_delayed_hooks = function(status, state) end end end + +vmf.apply_hooks_to_file = function(filepath, store_index) + local all_file_instances = vmf:get_require_store(filepath) + local file_instance = all_file_instances and all_file_instances[store_index] + + local all_file_hooks = _hooks_by_file[filepath] + + if all_file_hooks and file_instance then + for _, hook_create_func in ipairs(all_file_hooks) do + hook_create_func(filepath, store_index) + end + end +end diff --git a/vmf/scripts/mods/vmf/modules/core/keybindings.lua b/vmf/scripts/mods/vmf/modules/core/keybindings.lua index 38a55ff..4a8ec88 100644 --- a/vmf/scripts/mods/vmf/modules/core/keybindings.lua +++ b/vmf/scripts/mods/vmf/modules/core/keybindings.lua @@ -197,8 +197,8 @@ local ERRORS = { -- ##################################################################################################################### local function is_vmf_input_service_active() - local input_service = Managers.input:get_service("VMF") - return input_service and not input_service:is_blocked() + -- @TODO: Implement check for active VMF input service + return true end @@ -304,8 +304,7 @@ function vmf.generate_keybinds() shift = modifier_keys["shift"], function_name = raw_keybind_data.function_name, - view_name = raw_keybind_data.view_name, - transition_data = raw_keybind_data.transition_data + view_name = raw_keybind_data.view_name } _keybinds[primary_key] = _keybinds[primary_key] or { @@ -337,16 +336,14 @@ end -- Creates VMF input service. It is required to know when non-global keybinds can be triggered. -- (Called every time a level is loaded, or on mods reload) function vmf.create_keybinds_input_service() - -- VMF input has to be created only during the actual game - if Managers.state.game_mode and not Managers.input:get_service("VMF") then - rawset(_G, "EmptyKeyMap", {win32 = {}, xb1 = {}}) - Managers.input:create_input_service("VMF", "EmptyKeyMap") - rawset(_G, "EmptyKeyMap", nil) - - -- Synchronize state of VMF input service with Player input service - local is_blocked = Managers.input:get_service("Player"):is_blocked() - Managers.input:get_service("VMF"):set_blocked(is_blocked) - end + -- @TODO: Link this input service to the player's input service and find some way to see if it's blocked + --[[ + -- To create the VMF input service in Darktide + local input_manager = Managers.input + local service_type = "VMF" + input_manager:add_setting(service_type, aliases, raw_key_table, filter_table, default_devices) + input_manager:get_input_service(service_type) + --]] end diff --git a/vmf/scripts/mods/vmf/modules/core/localization.lua b/vmf/scripts/mods/vmf/modules/core/localization.lua index 163190c..0c6e8d4 100644 --- a/vmf/scripts/mods/vmf/modules/core/localization.lua +++ b/vmf/scripts/mods/vmf/modules/core/localization.lua @@ -12,6 +12,8 @@ Polish (pl) ]] local _language_id = Application.user_setting("language_id") + +local _global_localization_database = {} local _localization_database = {} -- #################################################################################################################### @@ -25,8 +27,35 @@ local function safe_string_format(mod, str, ...) if success then return message - else + elseif mod then mod:error("(localize) \"%s\": %s", tostring(str), tostring(message)) + else + vmf:error("(localize) \"%s\": %s", tostring(str), tostring(message)) + end +end + + +local function get_translated_or_english_message(mod, text_translations, ...) + + if text_translations then + + local message + + if text_translations[_language_id] then + + message = safe_string_format(mod, text_translations[_language_id], ...) + if message then + return message + end + end + + if text_translations["en"] then + + message = safe_string_format(mod, text_translations["en"], ...) + if message then + return message + end + end end end @@ -36,37 +65,41 @@ end VMFMod.localize = function (self, text_id, ...) + local message local mod_localization_table = _localization_database[self:get_name()] if mod_localization_table then local text_translations = mod_localization_table[text_id] - if text_translations then - - local message - - if text_translations[_language_id] then - - message = safe_string_format(self, text_translations[_language_id], ...) - if message then - return message - end - end - - if text_translations["en"] then - - message = safe_string_format(self, text_translations["en"], ...) - if message then - return message - end - end - end + message = get_translated_or_english_message(self, text_translations, ...) else self:error("(localize): localization file was not loaded for this mod") end - return "<" .. tostring(text_id) .. ">" + return message or ("<" .. tostring(text_id) .. ">") end + +VMFMod.add_global_localize_strings = function (self, text_translations) + for text_id, translations in ipairs(text_translations) do + if not _global_localization_database[text_id] then + _global_localization_database[text_id] = translations + end + end +end + +-- #################################################################################################################### +-- ##### VMF internal functions and variables ######################################################################### +-- #################################################################################################################### + +-- Handles the return of global localize text_ids +vmf:hook(_G, "Localize", function (func, text_id, ...) + + local text_translations = text_id and _global_localization_database[text_id] + local message = get_translated_or_english_message(nil, text_translations, ...) + + return message or func(text_id, ...) +end) + -- #################################################################################################################### -- ##### VMF internal functions and variables ######################################################################### -- #################################################################################################################### @@ -89,9 +122,13 @@ 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 @@ -102,5 +139,5 @@ end -- ##### Script ####################################################################################################### -- #################################################################################################################### -local localization_table = vmf:dofile("localization/vmf") +local localization_table = vmf:dofile("dmf/localization/vmf") vmf.initialize_mod_localization(vmf, localization_table) diff --git a/vmf/scripts/mods/vmf/modules/core/logging.lua b/vmf/scripts/mods/vmf/modules/core/logging.lua index e7a9740..0b6b6f2 100644 --- a/vmf/scripts/mods/vmf/modules/core/logging.lua +++ b/vmf/scripts/mods/vmf/modules/core/logging.lua @@ -2,26 +2,56 @@ local vmf = get_mod("VMF") local _unsent_chat_messages = {} local _logging_settings +local _logging_settings_lookup = { + [0] = {[1] = false, [2] = false, [3] = false}, -- Disabled + [1] = {[1] = true, [2] = false, [3] = false}, -- Log only + [2] = {[1] = false, [2] = true, [3] = false}, -- Chat only + [3] = {[1] = false, [2] = false, [3] = true}, -- Notification only + [4] = {[1] = true, [2] = true, [3] = false}, -- Log and chat + [5] = {[1] = true, [2] = false, [3] = true}, -- Log and Notification + [6] = {[1] = false, [2] = true, [3] = true}, -- Chat and Notification + [7] = {[1] = true, [2] = true, [3] = true}, -- All +} +local _notification_sound = "wwise/events/ui/play_ui_click" -- ##################################################################################################################### -- ##### Local functions ############################################################################################### -- ##################################################################################################################### -local function add_chat_message(message) - local chat_manager = Managers.chat - local new_message = { - channel_id = 1, - message_sender = "System", - message = message, - is_system_message = VT1 and true, - type = not VT1 and Irc.SYSTEM_MSG, -- luacheck: ignore Irc - pop_chat = true, - is_dev = false - } +local function add_chat_notification(message) + local event_manager = Managers.event + + if event_manager then + event_manager:trigger("event_add_notification_message", "default", message, nil, _notification_sound) + end +end - table.insert(chat_manager.chat_messages, new_message) - if not VT1 then - table.insert(chat_manager.global_messages, new_message) + +local function add_chat_message(message, sender) + local chat_manager = Managers.chat + local event_manager = Managers.event + + if chat_manager and event_manager then + local message_obj = { + message_body = message, + is_current_user = false, + } + + local participant = { + displayname = sender or "SYSTEM", + } + + local message_sent = false + + local channel_handle, channel = next(chat_manager:connected_chat_channels()) + if channel then + event_manager:trigger("chat_manager_message_recieved", channel_handle, participant, message_obj) + message_sent = true + end + + if not message_sent then + table.insert(_unsent_chat_messages, message) + end end end @@ -37,17 +67,18 @@ local function safe_format(mod, str, ...) end +local function send_to_notifications(self, message) + add_chat_notification(message) +end + + local function send_to_chat(self, msg_type, message) if msg_type ~= "echo" then message = string.format("[%s][%s] %s", self:get_name(), string.upper(msg_type), message) end - if Managers.chat and Managers.chat:has_channel(1) then - add_chat_message(message) - else - table.insert(_unsent_chat_messages, message) - end + add_chat_message(message) end @@ -60,6 +91,9 @@ local function log_message(self, msg_type, message, ...) message = safe_format(self, tostring(message), ...) if message then + if _logging_settings[msg_type].send_to_notifications then + send_to_notifications(self, message) + end if _logging_settings[msg_type].send_to_chat then send_to_chat(self, msg_type, message) end @@ -73,6 +107,13 @@ end -- ##### VMFMod ######################################################################################################## -- ##################################################################################################################### +function VMFMod:notify(message, ...) + if _logging_settings.notification.enabled then + log_message(self, "notification", message, ...) + end +end + + function VMFMod:echo(message, ...) if _logging_settings.echo.enabled then log_message(self, "echo", message, ...) @@ -119,8 +160,8 @@ end -- Can't be hooked right away, since hooking module is not initialized yet -- Sends unsent messages to chat when chat channel is finally created function vmf.delayed_chat_messages_hook() - vmf:hook_safe("ChatManager", "register_channel", function (self, channel_id) - if (channel_id == 1) and (#_unsent_chat_messages > 0) then + vmf:hook_safe("VivoxManager", "join_chat_channel", function (self) + if #_unsent_chat_messages > 0 and #self:connected_chat_channels() > 0 then for _, message in ipairs(_unsent_chat_messages) do add_chat_message(message) end @@ -135,18 +176,20 @@ end function vmf.load_logging_settings() _logging_settings = { - echo = vmf:get("logging_mode") == "custom" and vmf:get("output_mode_echo") or 3, - error = vmf:get("logging_mode") == "custom" and vmf:get("output_mode_error") or 3, - warning = vmf:get("logging_mode") == "custom" and vmf:get("output_mode_warning") or 3, - info = vmf:get("logging_mode") == "custom" and vmf:get("output_mode_info") or 1, - debug = vmf:get("logging_mode") == "custom" and vmf:get("output_mode_debug") or 0, + notification = vmf:get("logging_mode") == "custom" and vmf:get("output_mode_notification") or 5, + echo = vmf:get("logging_mode") == "custom" and vmf:get("output_mode_echo") or 4, + error = vmf:get("logging_mode") == "custom" and vmf:get("output_mode_error") or 4, + warning = vmf:get("logging_mode") == "custom" and vmf:get("output_mode_warning") or 4, + info = vmf:get("logging_mode") == "custom" and vmf:get("output_mode_info") or 1, + debug = vmf:get("logging_mode") == "custom" and vmf:get("output_mode_debug") or 0, } for method_name, logging_mode in pairs(_logging_settings) do _logging_settings[method_name] = { - send_to_chat = logging_mode and logging_mode >= 2, - send_to_log = logging_mode and logging_mode % 2 == 1, - enabled = logging_mode and logging_mode > 0 + send_to_notifications = logging_mode and _logging_settings_lookup[logging_mode][3], + send_to_chat = logging_mode and _logging_settings_lookup[logging_mode][2], + send_to_log = logging_mode and _logging_settings_lookup[logging_mode][1], + enabled = logging_mode and logging_mode > 0 } end end diff --git a/vmf/scripts/mods/vmf/modules/core/misc.lua b/vmf/scripts/mods/vmf/modules/core/misc.lua index e07e313..092a56d 100644 --- a/vmf/scripts/mods/vmf/modules/core/misc.lua +++ b/vmf/scripts/mods/vmf/modules/core/misc.lua @@ -18,12 +18,3 @@ function vmf.check_wrong_argument_type(mod, vmf_function_name, argument_name, ar table.concat(allowed_types, "/"), argument_type) return true end - -function vmf.check_old_vmf() - local old_vmf_table = rawget(_G, "Mods") - - if old_vmf_table and old_vmf_table.exec then - error("Unfortunately, workshop mods and old-fashioned mods (VMF-pack or QoL) are incompatible. " .. - "Either remove old mods or disable workshop mods.") - end -end diff --git a/vmf/scripts/mods/vmf/modules/core/mutators/mutators_default_config.lua b/vmf/scripts/mods/vmf/modules/core/mutators/mutators_default_config.lua index ac250d1..542a8ca 100644 --- a/vmf/scripts/mods/vmf/modules/core/mutators/mutators_default_config.lua +++ b/vmf/scripts/mods/vmf/modules/core/mutators/mutators_default_config.lua @@ -1,21 +1,17 @@ return { - dice = { - grims = 0, - tomes = 0, - bonus = 0 + reward = { + credits = 0, + plasteel = 0, + diamantine = 0 }, short_title = "", title_placement = "after", difficulty_levels = { - "easy", - "normal", - "hard", - "harder", - "hardest", - - "survival_hard", - "survival_harder", - "survival_hardest" + "lowest", + "low", + "medium", + "high", + "highest", }, incompatible_with_all = false, compatible_with_all = false, diff --git a/vmf/scripts/mods/vmf/modules/core/mutators/mutators_dice.lua b/vmf/scripts/mods/vmf/modules/core/mutators/mutators_dice.lua deleted file mode 100644 index 5fc675d..0000000 --- a/vmf/scripts/mods/vmf/modules/core/mutators/mutators_dice.lua +++ /dev/null @@ -1,92 +0,0 @@ ---[[ - Add additional dice to end game roll ---]] -local vmf = get_mod("VMF") - --- List of all die types -local MISSIONS = { - "bonus_dice_hidden_mission", - "tome_bonus_mission", - "grimoire_hidden_mission" -} - --- Amounts of additional dice to be added at level completion -local _num_dice_per_mission = { - bonus_dice_hidden_mission = 0, - tome_bonus_mission = 0, - grimoire_hidden_mission = 0 -} - --- ##################################################################################################################### --- ##### Local functions ############################################################################################### --- ##################################################################################################################### - --- Adds/remove dice -local function adjustDice(grims, tomes, bonus, multiplier) - if grims then - _num_dice_per_mission.grimoire_hidden_mission = _num_dice_per_mission.grimoire_hidden_mission + grims * multiplier - end - if tomes then - _num_dice_per_mission.tome_bonus_mission = _num_dice_per_mission.tome_bonus_mission + tomes * multiplier - end - if bonus then - _num_dice_per_mission.bonus_dice_hidden_mission = _num_dice_per_mission.bonus_dice_hidden_mission + bonus * - multiplier - end -end - --- ##################################################################################################################### --- ##### Hooks ######################################################################################################### --- ##################################################################################################################### - -vmf:hook(GameModeManager, "complete_level", function(func, ...) - local num_dice = 0 - local max_dice = 7 - local mission_system = Managers.state.entity:system("mission_system") - local active_mission = mission_system.active_missions - - -- Add additional dice - for _, mission in ipairs(MISSIONS) do - for _ = 1, _num_dice_per_mission[mission] do - mission_system:request_mission(mission, nil, Network.peer_id()) - mission_system:update_mission(mission, true, nil, Network.peer_id(), nil, true) - end - end - - -- Get total number of dice - for name, obj in pairs(active_mission) do - if table.contains(MISSIONS, name) then - num_dice = num_dice + obj.current_amount - end - end - - -- Remove excess dice - for _, mission in ipairs(MISSIONS) do - if active_mission[mission] then - for _ = 1, active_mission[mission].current_amount do - if num_dice > max_dice then - mission_system:request_mission(mission, nil, Network.peer_id()) - mission_system:update_mission(mission, false, nil, Network.peer_id(), nil, true) - num_dice = num_dice - 1 - else break end - end - end - if num_dice <= max_dice then break end - end - - func(...) -end) - --- ##################################################################################################################### --- ##### Return ######################################################################################################## --- ##################################################################################################################### - -return { - addDice = function(dice) - adjustDice(dice.grims, dice.tomes, dice.bonus, 1) - end, - - removeDice = function(dice) - adjustDice(dice.grims, dice.tomes, dice.bonus, -1) - end -} diff --git a/vmf/scripts/mods/vmf/modules/core/mutators/mutators_info.lua b/vmf/scripts/mods/vmf/modules/core/mutators/mutators_info.lua index 5f99948..5377dbd 100644 --- a/vmf/scripts/mods/vmf/modules/core/mutators/mutators_info.lua +++ b/vmf/scripts/mods/vmf/modules/core/mutators/mutators_info.lua @@ -3,8 +3,6 @@ --]] local vmf = get_mod("VMF") -local _were_enabled_before = false - -- ##################################################################################################################### -- ##### Local functions ############################################################################################### -- ##################################################################################################################### @@ -23,28 +21,7 @@ end -- Sets the lobby name local function set_lobby_data() - - if not Managers.matchmaking or - not Managers.matchmaking.lobby or - not Managers.matchmaking.lobby.set_lobby_data or - not Managers.matchmaking.lobby.get_stored_lobby_data - then - return - end - - local name = add_enabled_mutators_titles_to_string(" ", true) -- @TODO: change separator? - - local default_name = LobbyAux.get_unique_server_name() - if string.len(name) > 0 then - name = "||" .. name .. "|| " .. default_name - else - name = default_name - end - - local lobby_data = Managers.matchmaking.lobby:get_stored_lobby_data() - lobby_data.unique_server_name = name - - Managers.matchmaking.lobby:set_lobby_data(lobby_data) + -- @TODO: Add mutator titles to lobby name in matchmaking end @@ -56,49 +33,11 @@ end -- ##### Hooks ######################################################################################################### -- ##################################################################################################################### --- Append difficulty name with enabled mutators' titles -vmf:hook_origin(IngamePlayerListUI, "update_difficulty", function(self) - local difficulty_settings = Managers.state.difficulty:get_difficulty_settings() - local difficulty_name = difficulty_settings.display_name +-- @TODO: Hook to update difficulty name - local name = add_enabled_mutators_titles_to_string(", ", true) - local localized_difficulty_name = not self.is_in_inn and Localize(difficulty_name) or "" - if name == "" then -- no mutators - name = localized_difficulty_name - elseif localized_difficulty_name ~= "" then -- it can be "" if player is in the inn with no selected level - name = name .. " (" .. localized_difficulty_name .. ")" - end +-- @TODO: Hook to notify strike team of enabled mutators - self.set_difficulty_name(self, name) - - self.current_difficulty_name = difficulty_name -end) - - --- Notify everybody about enabled/disabled mutators when Play button is pressed on the map screen -vmf:hook_safe(MatchmakingStateHostGame, "host_game", function() - set_lobby_data() - local names = add_enabled_mutators_titles_to_string(", ") - if names ~= "" then - vmf:chat_broadcast(vmf:localize("broadcast_enabled_mutators") .. ": " .. names) - _were_enabled_before = true - elseif _were_enabled_before then - vmf:chat_broadcast(vmf:localize("broadcast_all_disabled")) - _were_enabled_before = false - end -end) - - --- @TODO: can't I do it with hook_safe? Also can't I just use 'sender' intead of extracting peer_id form cookie? --- Send special messages with enabled mutators list to players just joining the lobby -vmf:hook(MatchmakingManager, "rpc_matchmaking_request_join_lobby", function(func, self, sender, client_cookie, ...) - local name = add_enabled_mutators_titles_to_string(", ") - if name ~= "" then - local message = vmf:localize("whisper_enabled_mutators") .. ": " .. name - vmf:chat_whisper(get_peer_id_from_cookie(client_cookie), message) - end - func(self, sender, client_cookie, ...) -end) +-- @TODO: Hook to whisper incoming players about enabled mutators -- ##################################################################################################################### -- ##### Return ######################################################################################################## diff --git a/vmf/scripts/mods/vmf/modules/core/mutators/mutators_manager.lua b/vmf/scripts/mods/vmf/modules/core/mutators/mutators_manager.lua index 44b95aa..78853cb 100644 --- a/vmf/scripts/mods/vmf/modules/core/mutators/mutators_manager.lua +++ b/vmf/scripts/mods/vmf/modules/core/mutators/mutators_manager.lua @@ -1,5 +1,5 @@ --[[ - Manages everything related to mutators: loading order, enabling/disabling process, giving extra dice etc. + Manages everything related to mutators: loading order, enabling/disabling process, giving extra rewards etc. --]] local vmf = get_mod("VMF") @@ -23,11 +23,11 @@ local _mutators_sorted = false local _all_mutators_disabled = false -- External modules -local dice_manager = vmf:dofile("scripts/mods/vmf/modules/core/mutators/mutators_dice") -local set_lobby_data = vmf:dofile("scripts/mods/vmf/modules/core/mutators/mutators_info") +local reward_manager = vmf:dofile("dmf/scripts/mods/vmf/modules/core/mutators/mutators_reward") +local set_lobby_data = vmf:dofile("dmf/scripts/mods/vmf/modules/core/mutators/mutators_info") -- Get default configuration -local _default_config = vmf:dofile("scripts/mods/vmf/modules/core/mutators/mutators_default_config") +local _default_config = vmf:dofile("dmf/scripts/mods/vmf/modules/core/mutators/mutators_default_config") -- List of enabled mutators in case VMF is reloaded in the middle of the game local _enabled_mutators = vmf:persistent_table("enabled_mutators") @@ -49,7 +49,7 @@ end -- Called after mutator is enabled local function on_enabled(mutator) local config = mutator:get_internal_data("mutator_config") - dice_manager.addDice(config.dice) + reward_manager.addReward(config.reward) set_lobby_data() print("[MUTATORS] Enabled " .. mutator:get_name() .. " (" .. tostring(get_index(_mutators, mutator)) .. ")") @@ -61,9 +61,9 @@ end local function on_disabled(mutator, initial_call) local config = mutator:get_internal_data("mutator_config") - -- All mutators run on_disabled on initial call, so there's no need to remove dice and set lobby data + -- All mutators run on_disabled on initial call, so there's no need to remove rewards and set lobby data if not initial_call then - dice_manager.removeDice(config.dice) + reward_manager.removeReward(config.reward) set_lobby_data() end print("[MUTATORS] Disabled " .. mutator:get_name() .. " (" .. tostring(get_index(_mutators, mutator)) .. ")") @@ -74,9 +74,13 @@ end -- Checks if the player is server in a way that doesn't incorrectly return false during loading screens local function player_is_server() - local player = Managers.player - local state = Managers.state - return not player or player.is_server or not state or state.game_mode == nil + return Managers and Managers.state and Managers.state.game_session and Managers.state.game_session:is_server() + + --[[ -- Might not be necessary? + return Managers and Managers.state and ( + (Managers.state.game_session and Managers.state.game_session:is_server()) or + (Managers.state.game_mode and Managers.state.game_mode._game_mode == nil)) + --]] end @@ -154,7 +158,7 @@ local function mutator_can_be_enabled(mutator) end -- If conflicting difficulty is set (if no difficulty is set, all mutators are allowed) - local actual_difficulty = Managers.state and Managers.state.difficulty:get_difficulty() + local actual_difficulty = Managers.state and Managers.state.difficulty:get_difficulty() or 0 local compatible_difficulties = mutator_compatibility_config.compatible_difficulties return not actual_difficulty or compatible_difficulties[actual_difficulty] end @@ -302,14 +306,11 @@ local function update_compatibility(mutator) -- Compatibility with current difficulty (This part works only for VT1. Will see what to do with VT2 later.) compatibility.compatible_difficulties = { - easy = false, - normal = false, - hard = false, - harder = false, - hardest = false, - survival_hard = false, - survival_harder = false, - survival_hardest = false, + lowest = false, + low = false, + medium = false, + high = false, + highest = false, } local compatible_difficulties = compatibility.compatible_difficulties local compatible_difficulties_number = 0 @@ -342,7 +343,7 @@ local function initialize_mutator_config(mutator, _raw_config) local config = mutator:get_internal_data("mutator_config") - config.dice = raw_config.dice + config.reward = raw_config.reward config.short_title = raw_config.short_title config.title_placement = raw_config.title_placement @@ -493,13 +494,11 @@ end -- ##### Hooks ######################################################################################################### -- ##################################################################################################################### -vmf:hook_safe(DifficultyManager, "set_difficulty", function() - disable_impossible_mutators(true, "disabled_reason_difficulty_change") -end) +-- @TODO: Hook to disable impossible mutators when the difficulty changes -- ##################################################################################################################### -- ##### Script ######################################################################################################## -- ##################################################################################################################### -- Testing ---vmf:dofile("scripts/mods/vmf/modules/core/mutators/test/mutators_test") +--vmf:dofile("dmf/scripts/mods/vmf/modules/core/mutators/test/mutators_test") diff --git a/vmf/scripts/mods/vmf/modules/core/mutators/mutators_reward.lua b/vmf/scripts/mods/vmf/modules/core/mutators/mutators_reward.lua new file mode 100644 index 0000000..236bf22 --- /dev/null +++ b/vmf/scripts/mods/vmf/modules/core/mutators/mutators_reward.lua @@ -0,0 +1,48 @@ +--[[ + Add additional reward to end game results +--]] +local vmf = get_mod("VMF") + +-- Amounts of additional rewards to be added at level completion +local _num_reward = { + credits = 0, + plasteel = 0, + diamantine = 0 +} + +-- ##################################################################################################################### +-- ##### Local functions ############################################################################################### +-- ##################################################################################################################### + +-- Adds/removes reward modifiers +local function adjustReward(credits, plasteel, diamantine, multiplier) + if credits then + _num_reward.credits = _num_reward.credits + credits * multiplier + end + if plasteel then + _num_reward.plasteel = _num_reward.plasteel + plasteel * multiplier + end + if diamantine then + _num_reward.diamantine = _num_reward.diamantine + diamantine * multiplier + end +end + +-- ##################################################################################################################### +-- ##### Hooks ######################################################################################################### +-- ##################################################################################################################### + +-- @TODO: Hook to increase mission's reward according to enabled mutators + +-- ##################################################################################################################### +-- ##### Return ######################################################################################################## +-- ##################################################################################################################### + +return { + addReward = function(reward) + adjustReward(reward.credits, reward.plasteel, reward.diamantine, 1) + end, + + removeReward = function(reward) + adjustReward(reward.credits, reward.plasteel, reward.diamantine, -1) + end +} diff --git a/vmf/scripts/mods/vmf/modules/core/mutators/test/mutators_test.lua b/vmf/scripts/mods/vmf/modules/core/mutators/test/mutators_test.lua index ef31971..32617ed 100644 --- a/vmf/scripts/mods/vmf/modules/core/mutators/test/mutators_test.lua +++ b/vmf/scripts/mods/vmf/modules/core/mutators/test/mutators_test.lua @@ -50,8 +50,7 @@ create_test_mutator("test_deathwish", { is_mutator = true, mutator_settings = { difficulty_levels = { - "hardest", - "survival_hardest", + "highest", }, title_placement = "after", }, @@ -64,9 +63,9 @@ create_test_mutator("test_slayer", { is_mutator = true, mutator_settings = { difficulty_levels = { - "survival_hard", - "survival_harder", - "survival_hardest", + "medium", + "high", + "highest", }, }, }) @@ -98,7 +97,7 @@ create_test_mutator("test_one_hit_one_kill", { " non porta ante. Phasellus consequat facilisis quam quis dignissim", is_mutator = true, mutator_settings = { - difficulty_levels = {"hardest"}, + difficulty_levels = {"highest"}, enable_after_these = {"test_more_rats_weapons"}, }, }) @@ -116,10 +115,10 @@ create_test_mutator("lmao", { name = "lmao", is_mutator = true, mutator_settings = { - difficulty_levels = {"hardest"}, + difficulty_levels = {"highest"}, enable_after_these = {"ayyyy"}, - dice = { - bonus = 2, + reward = { + plasteel = 2, }, }, }) @@ -131,7 +130,7 @@ create_test_mutator("test_more_rats_weapons", { is_mutator = true, mutator_settings = { compatible_with_all = true, - difficulty_levels = {"hardest"}, + difficulty_levels = {"highest"}, }, }) diff --git a/vmf/scripts/mods/vmf/modules/core/network.lua b/vmf/scripts/mods/vmf/modules/core/network.lua index 1f813a7..14cc4a5 100644 --- a/vmf/scripts/mods/vmf/modules/core/network.lua +++ b/vmf/scripts/mods/vmf/modules/core/network.lua @@ -1,419 +1,29 @@ local vmf = get_mod("VMF") -local _vmf_users = {} -local _rpc_callbacks = {} - -local _local_mods_map = {} -local _local_rpcs_map = {} - -local _shared_mods_map = "" -local _shared_rpcs_map = "" - -local _network_module_is_initialized = false local _network_debug = false -local VT2_PORT_NUMBER = 0 - -local VERMINTIDE_CHANNEL_ID = 1 -local RPC_VMF_REQUEST_CHANNEL_ID = 3 -local RPC_VMF_RESPONCE_CHANNEL_ID = 4 -local RPC_VMF_UNKNOWN_CHANNEL_ID = 5 -- Note(Siku): No clue what 5 is supposed to mean. - -- #################################################################################################################### -- ##### Local functions ############################################################################################## -- #################################################################################################################### -local function is_rpc_registered(mod_name, rpc_name) - - local success = pcall(function() return _rpc_callbacks[mod_name][rpc_name] end) - return success -end - --- CONVERTING - -local function convert_names_to_numbers(peer_id, mod_name, rpc_name) - - local user_rpcs_dictionary = _vmf_users[peer_id] - if user_rpcs_dictionary then - - local mod_number = user_rpcs_dictionary[1][mod_name] - if mod_number then - - local rpc_number = user_rpcs_dictionary[2][mod_number][rpc_name] - if rpc_number then - - return mod_number, rpc_number - end - end - end - return nil -end - -local function convert_numbers_to_names(mod_number, rpc_number) - - local mod_name = _local_mods_map[mod_number] - if mod_name then - - local rpc_name = _local_rpcs_map[mod_number][rpc_number] - if rpc_name then - - return mod_name, rpc_name - - end - end - return nil -end - --- SERIALIZATION - -local function serialize_data(...) - - return cjson.encode({...}) -end - -local function deserialize_data(data) - - data = cjson.decode(data) - - local args_number = #data - - for i, _ in ipairs(data) do - if type(data[i]) == "userdata" then -- userdata [nullptr (deleted)] -> nil - data[i] = nil - end - end - - return unpack(data, 1, args_number) -end - --- DEBUG - -local function network_debug(rpc_type, action_type, peer_id, mod_name, rpc_name, data) - - if _network_debug then - - local debug_message - - if action_type == "local" then - debug_message = "[NETWORK][LOCAL]" - else - local msg_direction = (action_type == "sent" and "<-" or "->") - local player_string = tostring(Managers.player:player_from_peer_id(peer_id)) - --NOTE (Siku): Multiple concatenation requires the creation of multiple strings, look into it. - --debug_message = string.format("[NETWORK][%s (%s)] %s", peer_id, player_string, msg_direction) - debug_message = "[NETWORK][" .. peer_id .. " (" .. player_string .. ")]" .. msg_direction - end - - if rpc_type == "ping" then - - debug_message = debug_message .. "[PING]" - - elseif rpc_type == "pong" then - - debug_message = debug_message .. "[PONG]" - - elseif rpc_type == "data" then - - --debug_message = string.format("%s[DATA][%s][%s]: ", debug_message, mod_name, rpc_name) - debug_message = debug_message .. "[DATA][" .. mod_name .. "][" .. rpc_name .. "]: " - - if type(data) == "string" then - debug_message = debug_message .. data - else - local success, serialized_data = pcall(serialize_data, unpack(data)) - if success then - debug_message = debug_message .. serialized_data - end - end - end - - vmf:info(debug_message) - end -end - --- NETWORK - -local rpc_chat_message -if VT1 then - rpc_chat_message = function(member, channel_id, message_sender, message, localization_param, - is_system_message, pop_chat, is_dev) - RPC.rpc_chat_message(member, channel_id, message_sender, message, localization_param, - is_system_message, pop_chat, is_dev) - end -else - local _payload = {"","",""} - rpc_chat_message = function(member, channel_id, _, rpc_data1, rpc_data2) - _payload[1] = tostring(channel_id) - _payload[2] = rpc_data1 - _payload[3] = rpc_data2 - Managers.mod:network_send(member, VT2_PORT_NUMBER, _payload) - end -end - -local function send_rpc_vmf_ping(peer_id) - - network_debug("ping", "sent", peer_id) - rpc_chat_message(peer_id, 3, Network.peer_id(), "", "", false, true, false) -end - -local function send_rpc_vmf_pong(peer_id) - - network_debug("pong", "sent", peer_id) - rpc_chat_message(peer_id, 4, Network.peer_id(), _shared_mods_map, _shared_rpcs_map, false, true, false) -end - -local function send_rpc_vmf_data(peer_id, mod_name, rpc_name, ...) - - local mod_number, rpc_number = convert_names_to_numbers(peer_id, mod_name, rpc_name) - if mod_number then - - local rpc_info = cjson.encode({mod_number, rpc_number}) - local success, data = pcall(serialize_data, ...) - if success then - network_debug("data", "sent", peer_id, mod_name, rpc_name, data) - rpc_chat_message(peer_id, 5, Network.peer_id(), rpc_info, data, false, true, false) - end - end -end - -local function send_rpc_vmf_data_local(mod_name, rpc_name, ...) - - local mod = get_mod(mod_name) - - if mod:is_enabled() then - network_debug("data", "local", nil, mod_name, rpc_name, {...}) - - local error_prefix = "(local rpc) " .. tostring(rpc_name) - vmf.safe_call_nr(mod, error_prefix, _rpc_callbacks[mod_name][rpc_name], Network.peer_id(), ...) - end -end - -- #################################################################################################################### -- ##### VMFMod ####################################################################################################### -- #################################################################################################################### -VMFMod.network_register = function (self, rpc_name, rpc_function) - - if _network_module_is_initialized then - self:error("(network_register): you can't register new rpc after mod initialization") - return - end - - if vmf.check_wrong_argument_type(self, "network_register", "rpc_name", rpc_name, "string") or - vmf.check_wrong_argument_type(self, "network_register", "rpc_function", rpc_function, "function") then - return - end - - _rpc_callbacks[self:get_name()] = _rpc_callbacks[self:get_name()] or {} - - _rpc_callbacks[self:get_name()][rpc_name] = rpc_function -end - --- recipient = "all", "local", "others", peer_id -VMFMod.network_send = function (self, rpc_name, recipient, ...) - - if not is_rpc_registered(self:get_name(), rpc_name) then - - self:error("(network_send): attempt to send non-registered rpc") - return - end - - if recipient == "all" then - - for peer_id, _ in pairs(_vmf_users) do - send_rpc_vmf_data(peer_id, self:get_name(), rpc_name, ...) - end - - send_rpc_vmf_data_local(self:get_name(), rpc_name, ...) - - elseif recipient == "others" then - - for peer_id, _ in pairs(_vmf_users) do - send_rpc_vmf_data(peer_id, self:get_name(), rpc_name, ...) - end - - elseif recipient == "local" or recipient == Network.peer_id() then - - send_rpc_vmf_data_local(self:get_name(), rpc_name, ...) - - else -- recipient == peer_id - - send_rpc_vmf_data(recipient, self:get_name(), rpc_name, ...) - end -end - -- #################################################################################################################### -- ##### Hooks ######################################################################################################## -- #################################################################################################################### -local function vmf_network_recv(sender, channel_id, rpc_data1, rpc_data2) - if not _network_module_is_initialized then - return - end - - if channel_id == RPC_VMF_REQUEST_CHANNEL_ID then -- rpc_vmf_request - - network_debug("ping", "received", sender) - - send_rpc_vmf_pong(sender) - - elseif channel_id == RPC_VMF_RESPONCE_CHANNEL_ID then -- rpc_vmf_responce - -- @TODO: maybe I should protect it from sending by the player who's not in the game? - - network_debug("pong", "received", sender) - if _network_debug then - vmf:info("[RECEIVED MODS TABLE]: " .. rpc_data1) - vmf:info("[RECEIVED RPCS TABLE]: " .. rpc_data2) - end - - pcall(function() - - local user_rpcs_dictionary = {} - - user_rpcs_dictionary[1] = cjson.decode(rpc_data1) -- mods - user_rpcs_dictionary[2] = cjson.decode(rpc_data2) -- rpcs - - _vmf_users[sender] = user_rpcs_dictionary - - vmf:info("Added %s to the VMF users list.", sender) - - -- event - local player = Managers.player:player_from_peer_id(sender) - if player then - - for mod_name, _ in pairs(user_rpcs_dictionary[1]) do - local mod = get_mod(mod_name) - if mod then - vmf.mod_user_joined_the_game(mod, player) - end - end - end - end) - - elseif channel_id == RPC_VMF_UNKNOWN_CHANNEL_ID then - local mod_number, rpc_number = unpack(cjson.decode(rpc_data1)) - - local mod_name, rpc_name = convert_numbers_to_names(mod_number, rpc_number) - if mod_name and get_mod(mod_name):is_enabled() then - - network_debug("data", "received", sender, mod_name, rpc_name, rpc_data2) - - -- can be error in both callback_function() and deserialize_data() - local error_prefix = "(network) " .. tostring(rpc_name) - vmf.safe_call_nr( - get_mod(mod_name), - error_prefix, - function() _rpc_callbacks[mod_name][rpc_name](sender, deserialize_data(rpc_data2)) end - ) - end - end -end - -if VT1 then - vmf:hook("ChatManager", "rpc_chat_message", - function(func, self, sender, channel_id, message_sender, arg1, arg2, ...) - if channel_id == VERMINTIDE_CHANNEL_ID then - func(self, sender, channel_id, message_sender, arg1, arg2, ...) - else - vmf_network_recv(sender, channel_id, arg1, arg2) - end - end) -end --- VT2 uses the networking API provided by the ModManager. - -vmf:hook(PlayerManager, "add_remote_player", function (func, self, peer_id, player_controlled, ...) - - if player_controlled then - send_rpc_vmf_ping(peer_id) - end - - return func(self, peer_id, player_controlled, ...) -end) - -vmf:hook(PlayerManager, "remove_player", function (func, self, peer_id, ...) - - if _vmf_users[peer_id] then - - -- make sure it's not the bot - for _, player in pairs(Managers.player:human_players()) do - if player.peer_id == peer_id then - - vmf:info("Removed %s from the VMF users list.", peer_id) - - -- event - for mod_name, _ in pairs(_vmf_users[peer_id][1]) do - local mod = get_mod(mod_name) - if mod then - vmf.mod_user_left_the_game(mod, player) - end - end - - _vmf_users[peer_id] = nil - break - end - end - end - - func(self, peer_id, ...) -end) - -- #################################################################################################################### -- ##### VMF internal functions and variables ######################################################################### -- #################################################################################################################### vmf.create_network_dictionary = function() - - _shared_mods_map = {} - _shared_rpcs_map = {} - - local i = 0 - for mod_name, mod_rpcs in pairs(_rpc_callbacks) do - i = i + 1 - - _shared_mods_map[mod_name] = i - _local_mods_map[i] = mod_name - - _shared_rpcs_map[i] = {} - _local_rpcs_map[i] = {} - - local j = 0 - for rpc_name, _ in pairs(mod_rpcs) do - j = j + 1 - - _shared_rpcs_map[i][rpc_name] = j - _local_rpcs_map[i][j] = rpc_name - end - end - - _shared_mods_map = cjson.encode(_shared_mods_map) - _shared_rpcs_map = cjson.encode(_shared_rpcs_map) - - if not VT1 then - Managers.mod:network_bind(VT2_PORT_NUMBER, function(sender, payload) - vmf_network_recv(sender, tonumber(payload[1]), payload[2], payload[3]) - end) - end - - _network_module_is_initialized = true -end - -vmf.network_unload = function() - if not VT1 then - Managers.mod:network_unbind(VT2_PORT_NUMBER) - end + -- @TODO: Not implemented end vmf.ping_vmf_users = function() - - if Managers.player then - for _, player in pairs(Managers.player:human_players()) do - if player.peer_id ~= Network.peer_id() then - - send_rpc_vmf_ping(player.peer_id) - send_rpc_vmf_pong(player.peer_id) - end - end - end + -- @TODO: Not implemented end vmf.load_network_settings = function() diff --git a/vmf/scripts/mods/vmf/modules/core/options.lua b/vmf/scripts/mods/vmf/modules/core/options.lua index f2c7944..65a48a6 100644 --- a/vmf/scripts/mods/vmf/modules/core/options.lua +++ b/vmf/scripts/mods/vmf/modules/core/options.lua @@ -329,24 +329,6 @@ local function validate_keybind_data(data) vmf.throw_error("[widget \"%s\" (keybind)]: 'keybind_type' is set to \"view_toggle\" so 'view_name' " .. "field is required and must have 'string' type", data.setting_id) end - - local transition_data = data.transition_data - if type(transition_data) ~= "table" then - vmf.throw_error("[widget \"%s\" (keybind)]: 'keybind_type' is set to \"view_toggle\" so 'transition_data' " .. - "field is required and must have 'table' type", data.setting_id) - end - if transition_data.open_view_transition_name and type(transition_data.open_view_transition_name) ~= "string" then - vmf.throw_error("[widget \"%s\" (keybind)]: 'transition_data.open_view_transition_name' must have ".. - "'string' type", data.setting_id) - end - if transition_data.close_view_transition_name and type(transition_data.close_view_transition_name) ~= "string" then - vmf.throw_error("[widget \"%s\" (keybind)]: 'transition_data.close_view_transition_name' must have ".. - "'string' type", data.setting_id) - end - if transition_data.transition_fade and type(transition_data.transition_fade) ~= "boolean" then - vmf.throw_error("[widget \"%s\" (keybind)]: 'transition_data.transition_fade' must have ".. - "'boolean' type", data.setting_id) - end end local default_value = data.default_value @@ -388,7 +370,6 @@ local function initialize_keybind_data(mod, data, localize) new_data.keybind_type = data.keybind_type new_data.function_name = data.function_name -- required, if (keybind_type == "function_call") new_data.view_name = data.view_name -- required, if (keybind_type == "view_toggle") - new_data.transition_data = data.transition_data -- required, if (keybind_type == "view_toggle") validate_keybind_data(new_data) @@ -525,11 +506,12 @@ local function initialize_mod_options_widgets_data(mod, widgets_data, localize) end local initialized_data = {} + local base_depth = 0 -- Define widget data for header widget, because it's not up to modders to define it. local header_widget_data = {type = "header", sub_widgets = widgets_data} -- Put data of all widgets in one-dimensional array in order they will be displayed in mod options. - _unfolded_raw_widgets_data = unfold_table({header_widget_data}, widgets_data, 1, 1) + _unfolded_raw_widgets_data = unfold_table({header_widget_data}, widgets_data, 1, base_depth) -- Load info about widgets previously collapsed by user local collapsed_widgets = vmf:get("options_menu_collapsed_widgets")[mod:get_name()] or {} @@ -571,8 +553,7 @@ local function initialize_default_settings_and_keybinds(mod, initialized_widgets type = data.keybind_type, keys = mod:get(data.setting_id), function_name = data.function_name, - view_name = data.view_name, - transition_data = data.transition_data + view_name = data.view_name } ) end diff --git a/vmf/scripts/mods/vmf/modules/core/persistent_tables.lua b/vmf/scripts/mods/vmf/modules/core/persistent_tables.lua index 15cb300..47cafc1 100644 --- a/vmf/scripts/mods/vmf/modules/core/persistent_tables.lua +++ b/vmf/scripts/mods/vmf/modules/core/persistent_tables.lua @@ -1,6 +1,11 @@ local vmf = get_mod("VMF") -Managers.vmf = Managers.vmf or {} -- @TODO: move it to on_reload when it will be implemented in vt1 +-- @TODO: move it to on_reload when it will be implemented in vt1 +Managers.vmf = Managers.vmf or { + delete = function() + return + end +} Managers.vmf.persistent_tables = Managers.vmf.persistent_tables or {} local _persistent_tables = Managers.vmf.persistent_tables diff --git a/vmf/scripts/mods/vmf/modules/core/require.lua b/vmf/scripts/mods/vmf/modules/core/require.lua new file mode 100644 index 0000000..15ea6b9 --- /dev/null +++ b/vmf/scripts/mods/vmf/modules/core/require.lua @@ -0,0 +1,80 @@ +local vmf = get_mod("VMF") + +local _io_requires = {} + +-- Global store of objects created through require() +local _require_store = Mods.require_store + +-- Global backup of the require() function +local _original_require = Mods.original_require + +-- ##################################################################################################################### +-- ##### Local functions ############################################################################################### +-- ##################################################################################################################### + +local function add_io_require_path(path) + _io_requires[path] = true +end + + +local function remove_io_require_path(path) + _io_requires[path] = nil +end + + +local function get_require_store(path) + return _require_store[path] +end + + +local function original_require(path, ...) + return _original_require(path, ...) +end + +-- ##################################################################################################################### +-- ##### VMFMod ######################################################################################################## +-- ##################################################################################################################### + +-- Add a file path to be loaded through io instead of require() +function VMFMod:add_require_path(path) + add_io_require_path(path) +end + + +-- Remove a file path that was previously loaded through io instead of require() +function VMFMod:remove_require_path(path) + remove_io_require_path(path) +end + + +-- Get all instances of a file created through require() +function VMFMod:get_require_store(path) + return get_require_store(path) +end + + +-- Get a file through the original, unhooked require() function +function VMFMod:original_require(path, ...) + return original_require(path, ...) +end + +-- ##################################################################################################################### +-- ##### Hooks ######################################################################################################### +-- ##################################################################################################################### + +-- Handles the swap to io for registered files +vmf:hook(_G, "require", function (func, path, ...) + if _io_requires[path] then + return vmf:dofile(path) + else + return func(path, ...) + end +end) + +-- ##################################################################################################################### +-- ##### VMF internal functions and variables ########################################################################## +-- ##################################################################################################################### + +-- ##################################################################################################################### +-- ##### Script ######################################################################################################## +-- ##################################################################################################################### \ No newline at end of file diff --git a/vmf/scripts/mods/vmf/modules/core/safe_calls.lua b/vmf/scripts/mods/vmf/modules/core/safe_calls.lua index 547c7b8..95bc058 100644 --- a/vmf/scripts/mods/vmf/modules/core/safe_calls.lua +++ b/vmf/scripts/mods/vmf/modules/core/safe_calls.lua @@ -1,5 +1,8 @@ local vmf = get_mod("VMF") +-- Global method to load a file through io with a return +local mod_dofile = Mods.file.dofile + -- ##################################################################################################################### -- ##### Local functions ############################################################################################### -- ##################################################################################################################### @@ -84,7 +87,7 @@ function vmf.safe_call_dofile(mod, error_prefix_data, file_path) show_error(mod, error_prefix_data, "file path should be a string.") return false end - return vmf.safe_call(mod, error_prefix_data, dofile, file_path) + return vmf.safe_call(mod, error_prefix_data, mod_dofile, file_path) end diff --git a/vmf/scripts/mods/vmf/modules/core/toggling.lua b/vmf/scripts/mods/vmf/modules/core/toggling.lua index f8c7d1c..c247b18 100644 --- a/vmf/scripts/mods/vmf/modules/core/toggling.lua +++ b/vmf/scripts/mods/vmf/modules/core/toggling.lua @@ -12,11 +12,9 @@ vmf.set_mod_state = function (mod, is_enabled, initial_call) if is_enabled then mod:enable_all_hooks() - vmf.inject_hud_components(mod) vmf.mod_enabled_event(mod, initial_call) else mod:disable_all_hooks() - vmf.remove_injected_hud_components(mod) vmf.mod_disabled_event(mod, initial_call) end diff --git a/vmf/scripts/mods/vmf/modules/debug/dev_console.lua b/vmf/scripts/mods/vmf/modules/debug/dev_console.lua index 52801a2..db4a37d 100644 --- a/vmf/scripts/mods/vmf/modules/debug/dev_console.lua +++ b/vmf/scripts/mods/vmf/modules/debug/dev_console.lua @@ -4,6 +4,9 @@ local vmf = get_mod("VMF") -- It would requires hooks to be pushed higher in the loading order, but then we lose hooks printing to console -- Unless we find a way to store our logging messages in memory before the console is loaded. +-- Global backup of the ffi library +local _ffi = Mods.lua.ffi + local _console_data = vmf:persistent_table("dev_console_data") if not _console_data.enabled then _console_data.enabled = false end if not _console_data.original_print then _console_data.original_print = print end @@ -14,12 +17,6 @@ if not _console_data.original_print then _console_data.original_print = print en local function open_dev_console() - -- Forbid using dev console in official realm. Hopefully, temporarily restriction. So no localization. - if not VT1 and not script_data["eac-untrusted"] then - vmf:echo("You can't use developer console in official realm.") - return - end - if not _console_data.enabled then local print_hook_function = function(func, ...) @@ -50,14 +47,13 @@ local function close_dev_console() -- CommandWindow won't close by itself, so it have to be closed manually vmf:pcall(function() - local ffi = require("ffi") - ffi.cdef([[ + _ffi.cdef([[ void* FindWindowA(const char* lpClassName, const char* lpWindowName); int64_t SendMessageA(void* hWnd, unsigned int Msg, uint64_t wParam, int64_t lParam); ]]) local WM_CLOSE = 0x10; - local hwnd = ffi.C.FindWindowA("ConsoleWindowClass", "Developer console") - ffi.C.SendMessageA(hwnd, WM_CLOSE, 0, 0) + local hwnd = _ffi.C.FindWindowA("ConsoleWindowClass", "Developer console") + _ffi.C.SendMessageA(hwnd, WM_CLOSE, 0, 0) end) _console_data.enabled = false diff --git a/vmf/scripts/mods/vmf/modules/debug/table_dump.lua b/vmf/scripts/mods/vmf/modules/debug/table_dump.lua index 657c5a9..83fa7f8 100644 --- a/vmf/scripts/mods/vmf/modules/debug/table_dump.lua +++ b/vmf/scripts/mods/vmf/modules/debug/table_dump.lua @@ -1,6 +1,12 @@ local vmf = get_mod("VMF") -- @TODO: remove it? +-- Global backup of the io library +local _io = Mods.lua.io + +-- Global backup of the os library +local _os = Mods.lua.os + local function table_dump(key, value, depth, max_depth) if max_depth < depth then return @@ -222,8 +228,8 @@ local function table_dump_to_file(dumped_table, dumped_table_name, max_depth) -- ## Saving to file ## -- #################### - os.execute("mkdir dump 2>nul") - local file = assert(io.open("./dump/" .. dumped_table_name .. ".json", "w+")) + _os.execute("mkdir dump 2>nul") + local file = assert(_io.open("./dump/" .. dumped_table_name .. ".json", "w+")) local function dump_to_file(table_entry, table_name, depth) diff --git a/vmf/scripts/mods/vmf/modules/gui/custom_hud_components.lua b/vmf/scripts/mods/vmf/modules/gui/custom_hud_components.lua deleted file mode 100644 index 108f06f..0000000 --- a/vmf/scripts/mods/vmf/modules/gui/custom_hud_components.lua +++ /dev/null @@ -1,255 +0,0 @@ -local vmf = get_mod("VMF") - -local _ingame_hud -local _components_data = {} - -local COMPONENT_STATUS = table.enum("REGISTERED", "INJECTED") - -local ERRORS = { - THROWABLE = { - -- inject_hud_component: - component_already_exists = "hud component with class_name '%s' already exists.", - -- validate_component_data: - class_name_wrong_type = "'class_name' must be a string, not %s.", - visibility_groups_wrong_type = "'visibility_groups' must be a table, not %s.", - visibility_groups_key_wrong_type = "'visibility_groups' table keys must be a number, not %s.", - visibility_groups_value_wrong_type = "'visibility_groups' table values must be a string, not %s.", - use_hud_scale_wrong_type = "'use_hud_scale' must be a boolean or nil, not %s.", - validation_func_wrong_type = "'validation_function' must be a function or nil, not %s." - }, - PREFIX = { - component_validation = "[Custom HUD Components] (register_hud_component) Hud component data validation '%s'", - component_injection = "[Custom HUD Components] (inject_hud_component) Hud component injection '%s' ", - ingamehud_hook_injection = "[Custom HUD Components] Hud component injection '%s'" - } -} - --- ##################################################################################################################### --- ##### Local functions ############################################################################################### --- ##################################################################################################################### - -local function reset_component_status() - for _, component_data in pairs(_components_data) do - component_data.status = COMPONENT_STATUS.REGISTERED - end -end - -local function get_mod_hud_components(mod) - return table.filter(_components_data, function(component_data) - return component_data.mod == mod - end) -end - -local function remove_injected_hud_components(mod) - - local visibility_groups_lookup = _ingame_hud._definitions.visibility_groups_lookup - - local components_to_remove = mod and get_mod_hud_components(mod) or _components_data - for component_name, component_data in pairs(components_to_remove) do - if component_data.status == COMPONENT_STATUS.INJECTED and _ingame_hud._component_list[component_name] then - _ingame_hud:_remove_component(_ingame_hud._component_list, - _ingame_hud._components, - _ingame_hud._components_array, - _ingame_hud._components_array_id_lookup, - component_name) - - local component_settings = component_data.component_settings - for _, visibility_group in ipairs(component_settings.visibility_groups) do - visibility_groups_lookup[visibility_group].visible_components[component_name] = nil - end - _ingame_hud._components_hud_scale_lookup[component_name] = nil - _ingame_hud._component_list[component_name] = nil - - component_data.status = COMPONENT_STATUS.REGISTERED - end - end - -end - --- @ THROWS_ERRORS -local function inject_hud_component(component_name) - local component_data = _components_data[component_name] - local component_settings = component_data.component_settings - - if not component_data.mod:is_enabled() or component_data.status == COMPONENT_STATUS.INJECTED then - return - end - - -- Check for collisions. - if _ingame_hud._component_list[component_name] then - vmf.throw_error(ERRORS.THROWABLE.component_already_exists, component_name) - end - - if component_settings.use_hud_scale then - _ingame_hud._components_hud_scale_lookup[component_name] = true - end - - local visibility_groups_lookup = _ingame_hud._definitions.visibility_groups_lookup - for _, visibility_group in ipairs(component_settings.visibility_groups) do - visibility_groups_lookup[visibility_group].visible_components[component_name] = true - end - - if table.contains(component_settings.visibility_groups, _ingame_hud._current_group_name) then - _ingame_hud._currently_visible_components[component_name] = true - end - - _ingame_hud._component_list[component_name] = component_settings - _ingame_hud:_add_component(_ingame_hud._component_list, - _ingame_hud._components, - _ingame_hud._components_array, - _ingame_hud._components_array_id_lookup, - component_name) - - component_data.status = COMPONENT_STATUS.INJECTED - - return true -end - --- @ THROWS_ERRORS -local function validate_component_data(component_settings) - if type(component_settings.class_name) ~= "string" then - vmf.throw_error(ERRORS.THROWABLE.class_name_wrong_type, type(component_settings.class_name)) - end - if component_settings.use_hud_scale and type(component_settings.use_hud_scale) ~= "boolean" then - vmf.throw_error(ERRORS.THROWABLE.use_hud_scale_wrong_type, type(component_settings.use_hud_scale)) - end - if type(component_settings.visibility_groups) ~= "table" then - vmf.throw_error(ERRORS.THROWABLE.visibility_groups_wrong_type, type(component_settings.visibility_groups)) - end - if component_settings.validation_function and type(component_settings.validation_function) ~= "function" then - vmf.throw_error(ERRORS.THROWABLE.validation_func_wrong_type, type(component_settings.validation_function)) - end - - local visibility_groups = component_settings.visibility_groups - for key, visibility_group in pairs(visibility_groups) do - if type(key) ~= "number" then - vmf.throw_error(ERRORS.THROWABLE.visibility_groups_key_wrong_type, type(key)) - end - if type(visibility_group) ~= "string" then - vmf.throw_error(ERRORS.THROWABLE.visibility_groups_value_wrong_type, type(visibility_group)) - end - end - -end - --- ##################################################################################################################### --- ##### VMFMod ######################################################################################################## --- ##################################################################################################################### - ---[[ - Validates provided component settings, injects the component, and returns 'true' if everything is correct. - * component_settings [table] : Settings of the component to register - ** class_name [string] (required) : Name of the class containing the component logic. - ** visibility_groups [table nothing selected local _commands_list_gui_draw +local _commands_list_gui_destroy local _chat_history = {} local _chat_history_index = 0 @@ -21,14 +22,34 @@ local _chat_history_remove_dups_last = false local _chat_history_remove_dups_all = false local _chat_history_save_commands_only = false -local _queued_command -- is a workaround for VT2 where raycast is blocked during ui update +local _chat_message +local _previous_chat_message +local _queued_command -- #################################################################################################################### -- ##### Local functions ############################################################################################## -- #################################################################################################################### local function initialize_drawing_function() - _commands_list_gui_draw = dofile("scripts/mods/vmf/modules/ui/chat/commands_list_gui") + if not _commands_list_gui_draw then + local commands_list_gui = vmf:dofile("dmf/scripts/mods/vmf/modules/ui/chat/commands_list_gui") + _commands_list_gui_draw = commands_list_gui.draw + _commands_list_gui_destroy = commands_list_gui.destroy + end +end + +local function destroy_command_gui() + if _commands_list_gui_destroy then + _commands_list_gui_destroy() + _commands_list_gui_draw = nil + _commands_list_gui_destroy = nil + end +end + +local function clean_chat_notifications() + if Managers.event then + Managers.event:trigger("event_clear_notifications") + end end local function clean_chat_history() @@ -36,42 +57,42 @@ local function clean_chat_history() _chat_history_index = 0 end +local function get_chat_index(chat_gui) + return chat_gui._input_field_widget.content.caret_position +end + +local function get_chat_message(chat_gui) + return chat_gui._input_field_widget.content.input_text or "" +end + local function set_chat_message(chat_gui, message) - chat_gui.chat_message = message - chat_gui.chat_index = KeystrokeHelper.num_utf8chars(chat_gui.chat_message) + 1 - chat_gui.chat_input_widget.content.text_index = 1 + _chat_message = message + chat_gui._input_field_widget.content.input_text = message + chat_gui._input_field_widget.content.caret_position = Utf8.string_length(_chat_message) + 1 end -- #################################################################################################################### -- ##### Hooks ######################################################################################################## -- #################################################################################################################### -vmf:hook_safe(WorldManager, "create_world", function(self_, name) - if name == "top_ingame_view" then - initialize_drawing_function() - end -end) - - -vmf:hook_safe("ChatGui", "block_input", function() - _chat_opened = true -end) - - -vmf:hook("ChatGui", "_update_input", function(func, self, input_service, menu_input_service, dt, no_unblock, - chat_enabled, ...) +-- Handle chat actions when the chat window is active +vmf:hook("ConstantElementChat", "_handle_active_chat_input", function(func, self, input_service, ui_renderer, ...) + initialize_drawing_function() local command_executed = false - -- if ENTER was pressed - if Keyboard.pressed(Keyboard.button_index("enter")) or Keyboard.pressed(Keyboard.button_index("numpad enter")) then + _chat_message = get_chat_message(self) + _chat_opened = true + + -- if message is sending + if input_service:get("send_chat_message") then -- chat history if _chat_history_enabled - and self.chat_message ~= "" - and not (_chat_history_remove_dups_last and (self.chat_message == _chat_history[1])) + and _chat_message ~= "" + and not (_chat_history_remove_dups_last and (_chat_message == _chat_history[1])) and (not _chat_history_save_commands_only or (_command_index ~= 0)) then - table.insert(_chat_history, 1, self.chat_message) + table.insert(_chat_history, 1, _chat_message) if #_chat_history == _chat_history_max + 1 then table.remove(_chat_history, #_chat_history) @@ -80,7 +101,7 @@ vmf:hook("ChatGui", "_update_input", function(func, self, input_service, menu_in if _chat_history_remove_dups_all then for i = 2, #_chat_history do - if _chat_history[i] == self.chat_message then + if _chat_history[i] == _chat_message then table.remove(_chat_history, i) break end @@ -91,7 +112,7 @@ vmf:hook("ChatGui", "_update_input", function(func, self, input_service, menu_in -- command execution if _command_index ~= 0 then local args = {} - for arg in string.gmatch(self.chat_message, "%S+") do + for arg in string.gmatch(_chat_message, "%S+") do table.insert(args, arg) end table.remove(args, 1) @@ -107,13 +128,21 @@ vmf:hook("ChatGui", "_update_input", function(func, self, input_service, menu_in set_chat_message(self, "") command_executed = true + + elseif string.sub(_chat_message, 1, 1) == "/" then + vmf:notify(vmf:localize("chat_command_not_recognized") .. ": " .. _chat_message) + set_chat_message(self, "") + return end end + + local old_chat_message = _chat_message - local old_chat_message = self.chat_message + local result = func(self, input_service, ui_renderer, ...) - local chat_focused, chat_closed, chat_close_time = func(self, input_service, menu_input_service, - dt, no_unblock, chat_enabled, ...) + -- Get completion state + local input_widget = self._input_field_widget + local chat_closed = not input_widget.content.is_writing if chat_closed then set_chat_message(self, "") @@ -123,24 +152,19 @@ vmf:hook("ChatGui", "_update_input", function(func, self, input_service, menu_in _commands_list = {} _command_index = 0 _chat_history_index = 0 - - if command_executed then - chat_closed = false - chat_close_time = nil - end end if _chat_opened then - -- getting state of 'tab', 'arrow up' and 'arrow down' buttons - local tab_pressed = false + -- getting state of 'arrow right', 'arrow up' and 'arrow down' buttons + local arrow_right_pressed = false local arrow_up_pressed = false local arrow_down_pressed = false for _, stroke in ipairs(Keyboard.keystrokes()) do - if stroke == Keyboard.TAB then - tab_pressed = true -- game considers some "ctrl + [something]" combinations as arrow buttons, -- so I have to check for ctrl not pressed + if stroke == Keyboard.RIGHT and Keyboard.button(Keyboard.button_index("left ctrl")) == 0 then + arrow_right_pressed = true elseif stroke == Keyboard.UP and Keyboard.button(Keyboard.button_index("left ctrl")) == 0 then arrow_up_pressed = true elseif stroke == Keyboard.DOWN and Keyboard.button(Keyboard.button_index("left ctrl")) == 0 then @@ -151,14 +175,12 @@ vmf:hook("ChatGui", "_update_input", function(func, self, input_service, menu_in -- chat history if _chat_history_enabled then - -- reverse result of native chat history in VT2 - if not VT1 and input_service.get(input_service, "chat_next_old_message") or - input_service.get(input_service, "chat_previous_old_message") then + if arrow_up_pressed then set_chat_message(self, old_chat_message) end -- message was modified by player - if self.chat_message ~= self.previous_chat_message then + if _chat_message ~= _previous_chat_message then _chat_history_index = 0 end if arrow_up_pressed or arrow_down_pressed then @@ -171,7 +193,7 @@ vmf:hook("ChatGui", "_update_input", function(func, self, input_service, menu_in set_chat_message(self, _chat_history[new_index]) - self.previous_chat_message = self.chat_message + _previous_chat_message = _chat_message _chat_history_index = new_index else -- new_index == 0 @@ -181,41 +203,15 @@ vmf:hook("ChatGui", "_update_input", function(func, self, input_service, menu_in end end - -- ctrl + v - if VT1 and Keyboard.pressed(Keyboard.button_index("v")) and Keyboard.button(Keyboard.button_index("left ctrl")) == 1 then - local new_chat_message = self.chat_message - - -- remove carriage returns - local clipboard_data = tostring(Clipboard.get()):gsub("\r", "") - - -- remove invalid characters - if Utf8.valid(clipboard_data) then - new_chat_message = new_chat_message .. clipboard_data - else - local valid_data = "" - clipboard_data:gsub(".", function(c) - if Utf8.valid(c) then - valid_data = valid_data .. c - end - end) - new_chat_message = new_chat_message .. valid_data - end - - set_chat_message(self, new_chat_message) - end - - -- ctrl + c - if Keyboard.pressed(Keyboard.button_index("c")) and Keyboard.button(Keyboard.button_index("left ctrl")) == 1 then - Clipboard.put(self.chat_message) - end - -- entered chat message starts with "/" - if string.sub(self.chat_message, 1, 1) == "/" then + if string.sub(_chat_message, 1, 1) == "/" then - -- if there's no space after '/part_of_command_name' and if TAB was pressed - if not string.find(self.chat_message, " ") and tab_pressed and - -- if TAB was pressed with caret at the end of the string - (string.len(self.chat_message) + 1) == self.chat_index and + local autocompleting = false + + -- if there's no space after '/part_of_command_name' and if arrow_right was pressed + if not string.find(_chat_message, " ") and arrow_right_pressed and + -- if arrow_right was pressed with caret at the end of the string + (string.len(_chat_message) + 1) == get_chat_index(self) and -- if there are any commands matching entered '/part_of_command_name (#_commands_list > 0) then @@ -224,16 +220,15 @@ vmf:hook("ChatGui", "_update_input", function(func, self, input_service, menu_in set_chat_message(self, "/" .. _commands_list[_command_index].name) -- so the next block won't update the commands list - old_chat_message = self.chat_message + autocompleting = true end - if self.chat_message ~= old_chat_message then - + if not autocompleting or not vmf._commands_list_gui_draw then -- get '/part_of_command_name' without '/' - local command_name_contains = self.chat_message:match("%S+"):sub(2, -1) + local command_name_contains = _chat_message:match("%S+"):sub(2, -1) - if string.find(self.chat_message, " ") then + if string.find(_chat_message, " ") then _commands_list = vmf.get_commands_list(command_name_contains, true) else _commands_list = vmf.get_commands_list(command_name_contains) @@ -248,7 +243,7 @@ vmf:hook("ChatGui", "_update_input", function(func, self, input_service, menu_in -- chat message was modified and doesn't start with '/' - elseif self.chat_message ~= old_chat_message and #_commands_list > 0 then + elseif #_commands_list > 0 then _commands_list = {} _command_index = 0 end @@ -258,7 +253,7 @@ vmf:hook("ChatGui", "_update_input", function(func, self, input_service, menu_in end end - return chat_focused, chat_closed, chat_close_time + return result end) -- #################################################################################################################### @@ -303,16 +298,19 @@ vmf.execute_queued_chat_command = function() end end +vmf.destroy_command_gui = function() + destroy_command_gui() +end + -- #################################################################################################################### -- ##### Script ####################################################################################################### -- #################################################################################################################### vmf.load_chat_history_settings() +vmf:command("clean_chat_notifications", vmf:localize("clean_chat_notifications"), clean_chat_notifications) if _chat_history_save then _chat_history = vmf:get("chat_history") or _chat_history end -if Managers.world and Managers.world:has_world("top_ingame_view") then - initialize_drawing_function() -end +initialize_drawing_function() \ No newline at end of file diff --git a/vmf/scripts/mods/vmf/modules/ui/chat/commands_list_gui.lua b/vmf/scripts/mods/vmf/modules/ui/chat/commands_list_gui.lua index da9b1e9..8d0b72b 100644 --- a/vmf/scripts/mods/vmf/modules/ui/chat/commands_list_gui.lua +++ b/vmf/scripts/mods/vmf/modules/ui/chat/commands_list_gui.lua @@ -2,7 +2,10 @@ local vmf = get_mod("VMF") local MULTISTRING_INDICATOR_TEXT = "[...]" -local FONT_TYPE = "hell_shark_arial" +local DEFAULT_HUD_SCALE = 100 + +local FONT_TYPE = "arial" +local FONT_MATERIAL = "content/ui/fonts/arial" local FONT_SIZE = 22 local MAX_COMMANDS_VISIBLE = 5 @@ -11,41 +14,118 @@ local STRING_HEIGHT = 25 local STRING_Y_OFFSET = 7 local STRING_X_MARGIN = 10 -local OFFSET_X = 0 -local OFFSET_Y = 200 +local OFFSET_X = 10 +local OFFSET_Y = 300 local OFFSET_Z = 880 local WIDTH = 550 -local _gui = World.create_screen_gui(Managers.world:world("top_ingame_view"), "immediate", - "material", "materials/fonts/gw_fonts") +local BASE_COMMAND_TEXT_WIDTH = WIDTH - STRING_X_MARGIN * 2 + +local _gui -- ##################################################################################################################### -- ##### Local functions ############################################################################################### -- ##################################################################################################################### -local function get_text_width(text, font_material, font_size) - local text_extent_min, text_extent_max = Gui.text_extents(_gui, text, font_material, font_size) +local function create_gui() + if not _gui then + local world_manager = Managers.world + if world_manager and world_manager:has_world("level_world") then + _gui = World.create_screen_gui(world_manager:world("level_world"), "immediate") + end + end +end + + +local function destroy_gui() + if _gui then + local world_manager = Managers.world + if world_manager and world_manager:has_world("level_world") then + World.destroy_gui(world_manager:world("level_world"), _gui) + end + _gui = nil + end +end + + +local function get_hud_scale() + local save_data = Managers.save:account_data() + local interface_settings = save_data.interface_settings + local hud_scale = interface_settings.hud_scale or DEFAULT_HUD_SCALE + + return hud_scale +end + + +local function get_text_size(text, font_type, font_size) + local font_data = Managers.font:data_by_type(font_type) + local font = font_data.path + local additional_settings = { + flags = font_data.render_flags or 0 + } + + local min, max, caret = Gui2.slug_text_extents(_gui, text, font, font_size, additional_settings) + local min_x, min_y = Vector3.to_elements(min) + local max_x, max_y = Vector3.to_elements(max) + local width = max_x - min_x + local height = max_y - min_y + + return width, height, min, caret +end + + +local function get_text_width(text, font, font_size) + local text_extent_min, text_extent_max = Gui.slug_text_extents(_gui, text, font, font_size) local text_height = text_extent_max[1] - text_extent_min[1] return text_height end -local function word_wrap(text, font_material, font_size, max_width) - local whitespace = " " +local function get_scaled_font_size_by_width(text, font_type, font_size, max_width) + local scale = RESOLUTION_LOOKUP.scale + local min_font_size = 1 + local scaled_font_size = math.max(font_size * scale, 1) + local text_width = get_text_size(text, font_type, scaled_font_size) + + if max_width < text_width then + repeat + if font_size <= min_font_size then + break + end + + font_size = math.max(font_size - 1, min_font_size) + scaled_font_size = math.max(font_size * scale, 1) + text_width = math.floor(get_text_size(text, font_type, scaled_font_size)) + until text_width <= max_width + end + + return font_size +end + + +local function word_wrap(text, font, font_size, max_width) local soft_dividers = "-+&/*" local return_dividers = "\n" local reuse_global_table = true local scale = RESOLUTION_LOOKUP.scale - return Gui.word_wrap(_gui, text, font_material, font_size, max_width * scale, whitespace, - soft_dividers, return_dividers, reuse_global_table) + return Gui.slug_word_wrap(_gui, text, font, font_size, max_width * scale, return_dividers, + soft_dividers, reuse_global_table, 0) end local function draw(commands_list, selected_command_index) - -- VT2 requires applying additional HUD scaling - if not VT1 and UISettings.use_custom_hud_scale then - UPDATE_RESOLUTION_LOOKUP(true, UISettings.hud_scale * 0.01) + + create_gui() + if not _gui then + return + end + + -- Apply additional HUD scaling + local hud_scale = get_hud_scale() + local should_scale = hud_scale ~= DEFAULT_HUD_SCALE + if should_scale then + UPDATE_RESOLUTION_LOOKUP(true, hud_scale * 0.01) end local selected_command_new_index = 0 @@ -57,7 +137,7 @@ local function draw(commands_list, selected_command_index) for i = first_displayed_command, last_displayed_command do local new_entry = {} new_entry.name = "/" .. commands_list[i].name - new_entry.description = commands_list[i].description + new_entry.description = " " .. commands_list[i].description new_entry.full_text = new_entry.name .. " " .. new_entry.description if i == selected_command_index then new_entry.selected = true @@ -67,38 +147,36 @@ local function draw(commands_list, selected_command_index) end local scale = RESOLUTION_LOOKUP.scale - local selected_strings_number = 1 - local font, font_size = UIFontByResolution({font_type = FONT_TYPE, font_size = FONT_SIZE}) - local font_material = font[1] - local font_name = font[3] + local font_size = FONT_SIZE for i, command in ipairs(displayed_commands) do + font_size = get_scaled_font_size_by_width(command.name, FONT_TYPE, FONT_SIZE, BASE_COMMAND_TEXT_WIDTH) -- draw "/command_name" text local scaled_offet_x = (OFFSET_X + STRING_X_MARGIN) * scale local scaled_offset_y = (OFFSET_Y - STRING_HEIGHT * (i + selected_strings_number - 1) + STRING_Y_OFFSET) * scale local string_position = Vector3(scaled_offet_x, scaled_offset_y, OFFSET_Z + 2) - Gui.text(_gui, command.name, font_material, font_size, font_name, string_position, Color(255, 100, 255, 100)) + Gui.slug_text(_gui, command.name, FONT_MATERIAL, font_size, string_position, nil, Color(255, 100, 255, 100)) - local command_text_strings = word_wrap(command.full_text, font_material, font_size, WIDTH - STRING_X_MARGIN * 2) + local command_text_strings = word_wrap(command.full_text, FONT_MATERIAL, font_size, BASE_COMMAND_TEXT_WIDTH) local multistring = #command_text_strings > 1 local first_description_string if multistring then if command.selected then selected_strings_number = #command_text_strings else - local multistring_indicator_width = get_text_width(MULTISTRING_INDICATOR_TEXT, font_material, font_size) - local command_text_width = WIDTH - STRING_X_MARGIN * 2 - (multistring_indicator_width / scale) - command_text_strings = word_wrap(command.full_text, font_material, font_size, command_text_width) + local multistring_indicator_width = get_text_width(MULTISTRING_INDICATOR_TEXT, FONT_MATERIAL, font_size) + local command_text_width = BASE_COMMAND_TEXT_WIDTH - (multistring_indicator_width / scale) + command_text_strings = word_wrap(command.full_text, FONT_MATERIAL, font_size, command_text_width) -- draw that [...] thing local multistring_offset_x = (OFFSET_X + WIDTH) * scale - multistring_indicator_width local multistring_indicator_position = Vector3(multistring_offset_x, string_position.y, string_position.z) - Gui.text(_gui, MULTISTRING_INDICATOR_TEXT, font_material, font_size, font_name, - multistring_indicator_position, Color(255, 100, 100, 100)) + Gui.slug_text(_gui, MULTISTRING_INDICATOR_TEXT, FONT_MATERIAL, font_size, + multistring_indicator_position, nil, Color(255, 100, 100, 100)) end first_description_string = string.sub(command_text_strings[1], #command.name + 2) else @@ -106,19 +184,19 @@ local function draw(commands_list, selected_command_index) end -- draw command description text (1st string) - local first_description_string_width = get_text_width(command.name, font_material, font_size) + local first_description_string_width = get_text_width(command.name, FONT_MATERIAL, font_size) local first_description_pos_x = string_position.x + first_description_string_width local first_description_string_position = Vector3(first_description_pos_x, string_position.y, string_position.z) - Gui.text(_gui, first_description_string, font_material, font_size, font_name, - first_description_string_position, Color(255, 255, 255, 255)) + Gui.slug_text(_gui, first_description_string, FONT_MATERIAL, font_size, + first_description_string_position, nil, Color(255, 255, 255, 255)) -- draw command description text (2+ strings) if command.selected and multistring then for j = 2, selected_strings_number do string_position.y = string_position.y - STRING_HEIGHT * scale - Gui.text(_gui, command_text_strings[j], font_material, font_size, font_name, - string_position, Color(255, 255, 255, 255)) + Gui.slug_text(_gui, command_text_strings[j], FONT_MATERIAL, font_size, + string_position, nil, Color(255, 255, 255, 255)) end end end @@ -149,15 +227,15 @@ local function draw(commands_list, selected_command_index) if selected_command_index > 0 then total_number_indicator = selected_command_index .. "/" .. total_number_indicator end - local total_number_indicator_width = get_text_width(total_number_indicator, font_material, font_size) + local total_number_indicator_width = get_text_width(total_number_indicator, FONT_MATERIAL, font_size) local total_number_indicator_x = (WIDTH) * scale - total_number_indicator_width local total_number_indicator_y = (OFFSET_Y + STRING_Y_OFFSET) * scale local total_number_indicator_position = Vector3(total_number_indicator_x, total_number_indicator_y, OFFSET_Z + 2) - Gui.text(_gui, total_number_indicator, font_material, font_size, font_name, - total_number_indicator_position, Color(255, 100, 100, 100)) + Gui.slug_text(_gui, total_number_indicator, FONT_MATERIAL, font_size, + total_number_indicator_position, nil, Color(255, 100, 100, 100)) end - if not VT1 and UISettings.use_custom_hud_scale then + if should_scale then UPDATE_RESOLUTION_LOOKUP(true) end end @@ -186,4 +264,4 @@ end -- ##### Return ######################################################################################################## -- ##################################################################################################################### -return draw +return { draw = draw, destroy = destroy_gui } diff --git a/vmf/scripts/mods/vmf/modules/ui/mutators/mutators_gui.lua b/vmf/scripts/mods/vmf/modules/ui/mutators/mutators_gui.lua deleted file mode 100644 index 9847b8e..0000000 --- a/vmf/scripts/mods/vmf/modules/ui/mutators/mutators_gui.lua +++ /dev/null @@ -1,424 +0,0 @@ -local vmf = get_mod("VMF") - -local _MUTATORS = vmf.mutators - -local _SELECTED_DIFFICULTY_KEY -- Currently selected difficulty in the map view. - -local _DEFINITIONS = dofile("scripts/mods/vmf/modules/ui/mutators/mutators_gui_definitions") -local _UI_SCENEGRAPH -local _MUTATOR_LIST_WIDGETS = {} -local _PARTY_BUTTON_WIDGET -local _NO_MUTATORS_TEXT_WIDGET -local _OTHER_WIDGETS = {} - -local _IS_MUTATOR_LIST_VISIBLE -- 'true' if Mutator view is active, 'false' if Party view is active. -local _CURRENT_PAGE_NUMBER -local _TOTAL_PAGES_NUMBER - -local _IS_MUTATORS_GUI_INITIALIZED = false - --- #################################################################################################################### --- ##### Local functions ############################################################################################## --- #################################################################################################################### - -local function get_map_view() - local map_view_exists, map_view = pcall(function () return Managers.matchmaking.ingame_ui.views.map_view end) - if map_view_exists then - return map_view - end -end - - --- Toggles mutator list (switches between Party and Mutators views). -local function show_mutator_list(map_view, is_visible) - - _IS_MUTATOR_LIST_VISIBLE = is_visible - - if is_visible then - - -- Banner - local banner_widget = map_view.background_widgets[3] - banner_widget.style.text.localize = false - banner_widget.style.tooltip_text.localize = false - banner_widget.content.text = vmf:localize("mutators_title") - banner_widget.content.tooltip_text = vmf:localize("mutators_banner_tooltip") - - -- Players list - for _, widget in ipairs(map_view.player_list_widgets) do - widget.content.visible = false - end - - -- Players counter - map_view.player_list_conuter_text_widget.content.visible = false - else - - -- Banner - local banner_widget = map_view.background_widgets[3] - banner_widget.style.text.localize = true - banner_widget.style.tooltip_text.localize = true - banner_widget.content.text = "map_party_title" - banner_widget.content.tooltip_text = "map_party_setting_tooltip" - - -- Players list - for _, widget in ipairs(map_view.player_list_widgets) do - widget.content.visible = true - end - - -- Players counter - map_view.player_list_conuter_text_widget.content.visible = true - end -end - - -local function change_map_view_look(map_view, is_vmf_look) - - if is_vmf_look then - map_view.ui_scenegraph.settings_button.position[1] = -50 - map_view.ui_scenegraph.friends_button.position[1] = 50 - map_view.ui_scenegraph.lobby_button.position[1] = 150 - else - map_view.ui_scenegraph.settings_button.position[1] = -100 - map_view.ui_scenegraph.friends_button.position[1] = 0 - map_view.ui_scenegraph.lobby_button.position[1] = 100 - end -end - - --- Used in the next function to calculate tooltip offset, since Fatshark's solution doesn't support --- tooltips with cursor being in the left-bottom corner. -local function calculate_tooltip_offset (widget_content, widget_style, ui_renderer) - - local input_service = ui_renderer.input_service - if input_service then - - local cursor_position = UIInverseScaleVectorToResolution(input_service:get("cursor")) - if cursor_position then - - local text = widget_content.tooltip_text - local max_width = widget_style.max_width - - local font, font_size = UIFontByResolution(widget_style) - local font_name = font[3] - local font_material = font[1] - - local _, font_min, font_max = UIGetFontHeight(ui_renderer.gui, font_name, font_size) - - local texts = UIRenderer.word_wrap(ui_renderer, text, font_material, font_size, max_width) - local num_texts = #texts - local full_font_height = (font_max + math.abs(font_min)) * RESOLUTION_LOOKUP.inv_scale - - local tooltip_height = full_font_height * num_texts - - widget_style.cursor_offset[1] = widget_style.cursor_default_offset[1] - widget_style.cursor_offset[2] = widget_style.cursor_default_offset[2] - (tooltip_height * UIResolutionScale()) - end - end -end - - --- Callback function for mutator widgets. It's not defined in definitions file because it works with mutators array. --- And it's easier to work with it from there. -local function offset_function_callback(ui_scenegraph_, style, content, ui_renderer) - - local mutator = content.mutator - - -- Find out if mutator can be enabled. - local can_be_enabled = true - - local mutator_compatibility_config = mutator:get_internal_data("mutator_config").compatibility - local is_mostly_compatible = mutator_compatibility_config.is_mostly_compatible - local except = mutator_compatibility_config.except - - for _, other_mutator in ipairs(_MUTATORS) do - if other_mutator:is_enabled() and other_mutator ~= mutator then - can_be_enabled = can_be_enabled and (is_mostly_compatible and not except[other_mutator] or - not is_mostly_compatible and except[other_mutator]) - end - end - - can_be_enabled = can_be_enabled and mutator_compatibility_config.compatible_difficulties[_SELECTED_DIFFICULTY_KEY] - - content.can_be_enabled = can_be_enabled - - -- Enable/disable mutator. - if content.highlight_hotspot.on_release then - if mutator:is_enabled() then - vmf.mod_state_changed(mutator:get_name(), false) - elseif can_be_enabled then - vmf.mod_state_changed(mutator:get_name(), true) - end - end - - -- Build tooltip (only for currently selected mutator widget). - if content.highlight_hotspot.is_hover then - - -- DESCRIPTION - - local tooltip_text = content.description - - -- MUTATORS COMPATIBILITY - - local incompatible_mods = {} - if next(except) then - tooltip_text = tooltip_text .. (is_mostly_compatible and vmf:localize("tooltip_incompatible_mutators") or - vmf:localize("tooltip_compatible_mutators")) - - for other_mutator, _ in pairs(except) do - table.insert(incompatible_mods, " * " .. other_mutator:get_readable_name()) - end - - tooltip_text = tooltip_text .. table.concat(incompatible_mods, "\n") - else - if is_mostly_compatible then - tooltip_text = tooltip_text .. vmf:localize("tooltip_compatible_with_all_mutators") - else - tooltip_text = tooltip_text .. vmf:localize("tooltip_incompatible_with_all_mutators") - end - end - - -- DIFFICULTIES COMPATIBILITY - - local difficulties = {} - local compatible_difficulties_number = mutator_compatibility_config.compatible_difficulties_number - if compatible_difficulties_number < 8 then - tooltip_text = tooltip_text .. (compatible_difficulties_number > 4 and - vmf:localize("tooltip_incompatible_diffs") or - vmf:localize("tooltip_compatible_diffs")) - - for difficulty_key, is_compatible in pairs(mutator_compatibility_config.compatible_difficulties) do - if compatible_difficulties_number > 4 and not is_compatible - or not (compatible_difficulties_number > 4) and is_compatible then - table.insert(difficulties, " * " .. vmf:localize(difficulty_key)) - end - end - - tooltip_text = tooltip_text .. table.concat(difficulties, "\n") - else - tooltip_text = tooltip_text .. vmf:localize("tooltip_compatible_with_all_diffs") - end - - -- CONFLICTS - - if not can_be_enabled then - tooltip_text = tooltip_text .. vmf:localize("tooltip_conflicts") - - local conflicting_mods = {} - for _, other_mutator in ipairs(_MUTATORS) do - if other_mutator:is_enabled() and other_mutator ~= mutator then - if not (is_mostly_compatible and not except[other_mutator] or - not is_mostly_compatible and except[other_mutator]) then - - table.insert(conflicting_mods, " * " .. other_mutator:get_readable_name() .. - vmf:localize("tooltip_append_mutator")) - end - end - end - - if #conflicting_mods > 0 then - tooltip_text = tooltip_text .. table.concat(conflicting_mods, "\n") .. "\n" - end - - if not mutator_compatibility_config.compatible_difficulties[_SELECTED_DIFFICULTY_KEY] then - tooltip_text = tooltip_text .. " * " .. vmf:localize(_SELECTED_DIFFICULTY_KEY) .. - vmf:localize("tooltip_append_difficulty") - end - end - - -- Applying tooltip - - content.tooltip_text = tooltip_text - calculate_tooltip_offset(content, style.tooltip_text, ui_renderer) - end - - -- Visual changing (text color and checkboxes). - local is_enabled = content.mutator:is_enabled() - - style.text.text_color = content.can_be_enabled and (is_enabled and content.text_color_enabled or - content.text_color_disabled) or content.text_color_inactive - - content.checkbox_texture = is_enabled and content.checkbox_checked_texture or - content.checkbox_unchecked_texture -end - - -local function initialize_scrollbar() - - local scrollbar_widget_content = _OTHER_WIDGETS.scrollbar.content - - if _TOTAL_PAGES_NUMBER > 1 then - scrollbar_widget_content.visible = true - scrollbar_widget_content.scroll_bar_info.bar_height_percentage = 1 / _TOTAL_PAGES_NUMBER - else - scrollbar_widget_content.visible = false - end -end - - -local function initialize_mutators_ui(map_view) - - -- Scenegraph - _UI_SCENEGRAPH = UISceneGraph.init_scenegraph(_DEFINITIONS.scenegraph_definition) - - -- Creating mutator list widgets and calculating total pages number - for i, mutator in ipairs(_MUTATORS) do - local offset = ((i - 1) % 8) + 1 - _MUTATOR_LIST_WIDGETS[i] = UIWidget.init(_DEFINITIONS.create_mutator_widget(mutator, offset_function_callback)) - _MUTATOR_LIST_WIDGETS[i].offset = {0, -32 * offset, 0} - _MUTATOR_LIST_WIDGETS[i].content.mutator = mutator - end - _CURRENT_PAGE_NUMBER = 1 - _TOTAL_PAGES_NUMBER = math.floor(#_MUTATORS / 8) + ((#_MUTATORS % 8 > 0) and 1 or 0) - - -- Party button - _PARTY_BUTTON_WIDGET = UIWidget.init(_DEFINITIONS.party_button_widget_defenition) - - -- "No mutators installed" text - _NO_MUTATORS_TEXT_WIDGET = UIWidget.init(_DEFINITIONS.no_mutators_text_widget) - - -- Other widgets - for widget_name, widget in pairs(_DEFINITIONS.widgets_definition) do - _OTHER_WIDGETS[widget_name] = UIWidget.init(widget) - end - - -- Modify original map_view look - change_map_view_look(map_view, true) - show_mutator_list(map_view, true) - - -- Find out if scrollbar is needed, calculate scrollbar size - initialize_scrollbar() - - _IS_MUTATORS_GUI_INITIALIZED = true -end - - -local function draw(map_view, dt) - local input_service = map_view.input_manager:get_service("map_menu") - local ui_renderer = map_view.ui_renderer - local render_settings = map_view.render_settings - - UIRenderer.begin_pass(ui_renderer, _UI_SCENEGRAPH, input_service, dt, nil, render_settings) - - -- Party button - UIRenderer.draw_widget(ui_renderer, _PARTY_BUTTON_WIDGET) - - if _IS_MUTATOR_LIST_VISIBLE then - if #_MUTATORS > 0 then - -- Mutator list (render only 8 (or less) currently visible mutator widgets) - for i = ((_CURRENT_PAGE_NUMBER - 1) * 8 + 1), (_CURRENT_PAGE_NUMBER * 8) do - if not _MUTATOR_LIST_WIDGETS[i] then - break - end - UIRenderer.draw_widget(ui_renderer, _MUTATOR_LIST_WIDGETS[i]) - end - else - UIRenderer.draw_widget(ui_renderer, _NO_MUTATORS_TEXT_WIDGET) - end - - -- Other widgets - for _, widget in pairs(_OTHER_WIDGETS) do - UIRenderer.draw_widget(ui_renderer, widget) - end - end - - UIRenderer.end_pass(ui_renderer) -end - - --- Sets new scrollbar position (called when user changes the current page number with mouse scroll input) -local function update_scrollbar_position() - local scrollbar_widget_content = _OTHER_WIDGETS.scrollbar.content - local percentage = (1 / (_TOTAL_PAGES_NUMBER - 1)) * (_CURRENT_PAGE_NUMBER - 1) - scrollbar_widget_content.scroll_bar_info.value = percentage - scrollbar_widget_content.scroll_bar_info.old_value = percentage -end - - --- Reads scrollbar input and if it was changed, set current page according to the new scrollbar position -local function update_scrollbar_input() - local scrollbar_widget_content = _OTHER_WIDGETS.scrollbar.content - if scrollbar_widget_content.visible then - local scrollbar_info = scrollbar_widget_content.scroll_bar_info - local value = scrollbar_info.value - local old_value = scrollbar_info.old_value - if value ~= old_value then - _CURRENT_PAGE_NUMBER = math.clamp(math.ceil(value / (1 / _TOTAL_PAGES_NUMBER)), 1, _TOTAL_PAGES_NUMBER) - scrollbar_info.old_value = value - end - end -end - - --- Reads mousewheel scrolls from corresponding widget and changes current page number, if possible. -local function update_mousewheel_scroll_area_input() - local widget_content = _OTHER_WIDGETS.mousewheel_scroll_area.content - local mouse_scroll_value = widget_content.scroll_value - if mouse_scroll_value ~= 0 then - _CURRENT_PAGE_NUMBER = math.clamp(_CURRENT_PAGE_NUMBER + mouse_scroll_value, 1, _TOTAL_PAGES_NUMBER) - widget_content.scroll_value = 0 - update_scrollbar_position() - end -end - - -local function update_mutators_ui(map_view, dt) - - -- Show/hide mutator list if party button was pressed - local transitioning = map_view:transitioning() - local friends_menu_active = map_view.friends:is_active() - if not transitioning and not friends_menu_active then - local mutators_button_content = _PARTY_BUTTON_WIDGET.content - if mutators_button_content.button_hotspot.on_release then - map_view:play_sound("Play_hud_select") - show_mutator_list(map_view, mutators_button_content.toggled) - mutators_button_content.toggled = not mutators_button_content.toggled - end - end - - update_mousewheel_scroll_area_input() - update_scrollbar_input() - draw(map_view, dt) -end - --- #################################################################################################################### --- ##### Hooks ######################################################################################################## --- #################################################################################################################### - -vmf:hook_safe(MapView, "init", function (self) - initialize_mutators_ui(self) -end) - - -vmf:hook_safe(MapView, "update", function (self, dt) - - if self.menu_active and _IS_MUTATORS_GUI_INITIALIZED then - - -- Parse currently selected difficulty in the map_view - local difficulty_data = self.selected_level_index and self:get_difficulty_data(self.selected_level_index) - local difficulty_layout = difficulty_data and difficulty_data[self.selected_difficulty_stepper_index] - _SELECTED_DIFFICULTY_KEY = difficulty_layout and difficulty_layout.key - - update_mutators_ui(self, dt) - end -end) - --- #################################################################################################################### --- ##### VMF internal functions and variables ######################################################################### --- #################################################################################################################### - --- Changes map_view VMF way -function vmf.modify_map_view() - local map_view = get_map_view() - if map_view then - initialize_mutators_ui(map_view) - end -end - - --- Restores map_view to its defaults -function vmf.reset_map_view() - local map_view = get_map_view() - if map_view then - change_map_view_look(map_view, false) - show_mutator_list(map_view, false) - end -end diff --git a/vmf/scripts/mods/vmf/modules/ui/mutators/mutators_gui_definitions.lua b/vmf/scripts/mods/vmf/modules/ui/mutators/mutators_gui_definitions.lua deleted file mode 100644 index 371a2c8..0000000 --- a/vmf/scripts/mods/vmf/modules/ui/mutators/mutators_gui_definitions.lua +++ /dev/null @@ -1,326 +0,0 @@ -local vmf = get_mod("VMF") - - -local scenegraph_definition = { - - sg_root = { - size = {1920, 1080}, - position = {0, 0, UILayer.default}, - - is_root = true, - }, - - -- Fix for FullHD windowed (not fullscreen) mode (if everything else will inherit from sg_root, its children will - -- stick to the window border instead of the black gap) - sg_placeholder = { - size = {1920, 1080}, - position = {0, 0, 1}, - - parent = "sg_root", - - horizontal_alignment = "center", - vertical_alignment = "center" - }, - - sg_mutators_list_background = { - size = {547, 313}, - position = {-2, -2, 2}, -- @TODO: fix the actual image (-2 px plus image overlaping text) - - parent = "sg_placeholder", - - horizontal_alignment = "left", - vertical_alignment = "bottom" - }, - - sg_mutators_button = { - size = {64, 64}, - position = {87, 430.5, 2}, - - parent = "sg_placeholder", - - horizontal_alignment = "left", - vertical_alignment = "bottom" - }, - - sg_mutators_list = { - size = {370, 265}, - position = {80, 61, 3}, - - parent = "sg_placeholder", - - vertical_alignment = "bottom", - horizontal_alignment = "left" - }, - - sg_mutators_list_start = { - size = {1, 1}, - offset = {0, 0, 3}, - - parent = "sg_mutators_list", - - vertical_alignment = "top", - horizontal_alignment = "left" - }, - - sg_no_mutators_text = { - size = {310, 30}, - position = {0, 10, 1}, - - parent = "sg_mutators_list", - - vertical_alignment = "center", - horizontal_alignment = "center", - }, - - sg_scrollbar = { - size = {0, 290}, -- X size doesn't affect scrollbar width - position = {452, 52, 3}, - - parent = "sg_placeholder", - - vertical_alignment = "bottom", - horizontal_alignment = "left" - }, -} - - -local widgets_definition = { - - -- That photoshopped background texture which expands displayed list area - mutator_list_background = { - scenegraph_id = "sg_root", - element = { - passes = { - { - pass_type = "texture", - style_id = "mutators_list_background", - texture_id = "mutators_list_background_texture_id" - } - } - }, - content = { - mutators_list_background_texture_id = "map_view_mutators_area", - }, - style = { - mutators_list_background = { - scenegraph_id = "sg_mutators_list_background" - } - } - }, - - -- Widgets that detects mousewheel scrolls inside itself - mousewheel_scroll_area = { - scenegraph_id = "sg_mutators_list", - element = { - passes = { - { - pass_type = "scroll", - scroll_function = function (ui_scenegraph_, style_, content, input_service_, scroll_axis) - content.scroll_value = content.scroll_value - scroll_axis.y - end - } - } - }, - content = { - scroll_value = 0 - }, - style = {} - }, - - -- Scrollbar - scrollbar = UIWidgets.create_scrollbar(scenegraph_definition.sg_scrollbar.size[2], "sg_scrollbar") -} -widgets_definition.scrollbar.content.disable_frame = true -- Hide scrollbar frame - - --- The 4th button, which will toggle old "Party" view (which is replaced by "Mutators" view) -local party_button_widget_defenition = UIWidgets.create_octagon_button( - { - "map_view_party_button", - "map_view_party_button_lit" - }, - { - "map_party_title", - "map_party_title" - }, - "sg_mutators_button" -) - - --- Text displayed when user has 0 mutators -local no_mutators_text_widget = { - scenegraph_id = "sg_no_mutators_text", - element = { - passes = { - { - pass_type = "text", - - style_id = "text", - text_id = "text" - }, - { - pass_type = "hotspot", - - content_id = "tooltip_hotspot" - }, - { - pass_type = "tooltip_text", - - style_id = "tooltip_text", - text_id = "tooltip_text", - content_check_function = function (ui_content) - return ui_content.tooltip_hotspot.is_hover - end - } - } - }, - content = { - text = vmf:localize("no_mutators"), - tooltip_text = vmf:localize("no_mutators_tooltip"), - tooltip_hotspot = {}, - color = Colors.get_color_table_with_alpha("slate_gray", 255) - }, - style = { - - text = { - vertical_alignment = "center", - horizontal_alignment = "center", - font_size = 22, - localize = false, - word_wrap = true, - font_type = "hell_shark", - text_color = Colors.get_color_table_with_alpha("slate_gray", 255), - offset = {0, 2, 4} - }, - - tooltip_text = { - font_size = 24, - max_width = 500, - localize = false, - horizontal_alignment = "left", - vertical_alignment = "top", - font_type = "hell_shark", - text_color = Colors.get_color_table_with_alpha("white", 255), - line_colors = {}, - offset = {0, 0, 50} - } - } -} - - --- Creates a widget for every mutator (that string with checkbox) -local function create_mutator_widget(mutator, offset_function_callback) - return { - scenegraph_id = "sg_mutators_list_start", - element = { - passes = { - { - pass_type = "hotspot", - - content_id = "highlight_hotspot" - }, - { - pass_type = "local_offset", - - -- The function is executed inside of 'mutators_gui.lua', since it has to interact with mutator list a lot - offset_function = offset_function_callback - }, - { - pass_type = "texture", - - style_id = "hover_texture", - texture_id = "hover_texture", - content_check_function = function (content) - return content.can_be_enabled and content.highlight_hotspot.is_hover - end - }, - { - pass_type = "text", - - style_id = "text", - text_id = "text" - }, - { - pass_type = "texture", - - style_id = "checkbox_style", - texture_id = "checkbox_texture" - }, - { - pass_type = "tooltip_text", - - text_id = "tooltip_text", - style_id = "tooltip_text", - content_check_function = function (content) - return content.highlight_hotspot.is_hover - end - }, - } - }, - content = { - mutator = nil, -- is added after creation (i can't add mutator here now, becuase UIWidget.init() clones tables) - - text = mutator:get_readable_name(), - description = mutator:get_description() or vmf:localize("mutator_no_description_provided"), - - can_be_enabled = false, - - highlight_hotspot = {}, - - tooltip_text = "", -- always changes in local_offset pass - - hover_texture = "playerlist_hover", - - checkbox_texture = "checkbox_unchecked", -- always changes in local_offset pass - - -- Presets - checkbox_unchecked_texture = "checkbox_unchecked", - checkbox_checked_texture = "checkbox_checked", - - text_color_disabled = Colors.get_color_table_with_alpha("white", 255), - text_color_enabled = Colors.get_color_table_with_alpha("cheeseburger", 255), - text_color_inactive = Colors.get_color_table_with_alpha("slate_gray", 255), - }, - style = { - - text = { - offset = {10, -2, 2}, - font_size = 24, - font_type = "hell_shark", - dynamic_font = true, - text_color = Colors.get_color_table_with_alpha("white", 255) -- always changes in local_offset pass - }, - - hover_texture = { - size = {370, 32}, - offset = {0, 0, 1} - }, - - checkbox_style = { - size = {20, 20}, - offset = {340, 6, 2}, - color = {255, 255, 255, 255} - }, - - tooltip_text = { - font_type = "hell_shark", - font_size = 18, - cursor_side = "right", - max_width = 425, - cursor_offset = {0, 0}, -- always changes in local_offset pass - cursor_default_offset = {27, -27} - }, - - size = {370, 32} - } - } -end - - -return { - scenegraph_definition = scenegraph_definition, - widgets_definition = widgets_definition, - party_button_widget_defenition = party_button_widget_defenition, - no_mutators_text_widget = no_mutators_text_widget, - create_mutator_widget = create_mutator_widget -} diff --git a/vmf/scripts/mods/vmf/modules/ui/options/mod_options.lua b/vmf/scripts/mods/vmf/modules/ui/options/mod_options.lua index 868cb85..4fdd7b8 100644 --- a/vmf/scripts/mods/vmf/modules/ui/options/mod_options.lua +++ b/vmf/scripts/mods/vmf/modules/ui/options/mod_options.lua @@ -1,126 +1,389 @@ local vmf = get_mod("VMF") -local _button_injection_data = vmf:persistent_table("button_injection_data") +local OptionsUtilities = require("scripts/utilities/ui/options") +local InputUtils = require("scripts/managers/input/input_utils") + +local _type_template_map = {} + +-- #################################################################################################################### +-- ##### Local functions ############################################################################################## +-- #################################################################################################################### + +-- Create value slider template +local create_header_template = function (self, params) + + local template = { + category = params.category, + display_name = params.readable_mod_name or params.title, + group_name = params.mod_name, + tooltip_text = params.tooltip, + widget_type = "group_header", + } + return template +end +_type_template_map["header"] = create_header_template -if VT1 then +-- Create percentage slider template +local create_percent_slider_template = function (self, params) + + params.on_value_changed_function = function(new_value) + get_mod(params.mod_name):set(params.setting_id, new_value) + + return true + end + params.value_get_function = function() + return get_mod(params.mod_name):get(params.setting_id) + end + + params.display_name = params.title + params.apply_on_drag = true + params.default_value = params.default_value + params.normalized_step_size = 1 / 100 + + local template = OptionsUtilities.create_percent_slider_template(params) + + template.after = params.parent_index + template.category = params.category + template.indentation_level = params.depth + template.tooltip_text = params.tooltip + + return template +end +_type_template_map["percent_slider"] = create_percent_slider_template - -- Disable Mod Options button during mods reloading - vmf:hook_safe(IngameView, "update_menu_options", function (self) - for _, button_info in ipairs(self.active_button_data) do - if button_info.transition == "vmf_options_view_open" then - button_info.widget.content.disabled = _button_injection_data.mod_options_button_disabled - button_info.widget.content.button_hotspot.disabled = _button_injection_data.mod_options_button_disabled - end - end - end) +-- Create value slider template +local create_value_slider_template = function (self, params) + + params.on_value_changed_function = function(new_value) + get_mod(params.mod_name):set(params.setting_id, new_value) + + return true + end + params.value_get_function = function() + return get_mod(params.mod_name):get(params.setting_id) + end + + params.display_name = params.title + params.apply_on_drag = true + params.default_value = params.default_value + params.max_value = params.range[2] + params.min_value = params.range[1] + params.num_decimals = params.decimals_number + params.step_size_value = math.pow(10, params.decimals_number * -1) + params.type = "value_slider" + + local template = OptionsUtilities.create_value_slider_template(params) + + template.after = params.parent_index + template.category = params.category + template.indentation_level = params.depth + template.tooltip_text = params.tooltip + + return template +end +_type_template_map["value_slider"] = create_value_slider_template +_type_template_map["numeric"] = create_value_slider_template - -- Inject Mod Options button in current ESC-menu layout - -- Disable localization for button widget - vmf:hook(IngameView, "setup_button_layout", function (func, self, layout_data, ...) - local mods_options_button = { - display_name = vmf:localize("mods_options"), - transition = "vmf_options_view_open", - fade = false +-- Create checkbox template +local create_checkbox_template = function (self, params) + local template = { + after = params.parent_index, + category = params.category, + default_value = params.default_value, + display_name = params.title, + indentation_level = params.depth, + tooltip_text = params.tooltip, + value_type = "boolean", + } + template.on_activated = function(new_value) + get_mod(params.mod_name):set(params.setting_id, new_value) + + return true + end + template.get_function = function() + return get_mod(params.mod_name):get(params.setting_id) + end + + return template +end +_type_template_map["checkbox"] = create_checkbox_template + + +-- Create dropdown template +local create_dropdown_template = function (self, params) + + for i = 1, #params.options do + params.options[i].id = i - 1 + params.options[i].display_name = params.options[i].text + end + + local template = { + after = params.parent_index, + category = params.category, + default_value = params.default_value, + display_name = params.title, + indentation_level = params.depth, + options = params.options, + tooltip_text = params.tooltip, + widget_type = "dropdown", + } + template.on_activated = function(new_value) + get_mod(params.mod_name):set(params.setting_id, new_value) + + return true + end + template.get_function = function() + return get_mod(params.mod_name):get(params.setting_id) + end + + return template +end +_type_template_map["dropdown"] = create_dropdown_template + + +local set_new_keybind = function (self, keybind_widget_content) + vmf.add_mod_keybind( + get_mod(keybind_widget_content.mod_name), + keybind_widget_content.setting_id, + { + global = keybind_widget_content.keybind_global, + trigger = keybind_widget_content.keybind_trigger, + type = keybind_widget_content.keybind_type, + keys = keybind_widget_content.keys, + function_name = keybind_widget_content.function_name, + view_name = keybind_widget_content.view_name, } - for i = 1, #layout_data do - if layout_data[i].transition == "options_menu" and layout_data[i + 1].transition ~= "vmf_options_view_open" then - table.insert(layout_data, i + 1, mods_options_button) - break + ) +end + + +-- Create keybind template +local create_keybind_template = function (self, params) + local reserved_keys = {} + local cancel_keys = { + "keyboard_esc" + } + local devices = { + "keyboard", + "mouse", + "xbox_controller", + "ps4_controller" + } + + local template = { + widget_type = "keybind", + service_type = "Ingame", + tooltip_text = params.tooltip, + display_name = params.title, + group_name = params.category, + category = params.category, + after = params.parent_index, + devices = devices, + sort_order = params.sort_order, + cancel_keys = cancel_keys, + reserved_keys = reserved_keys, + indentation_level = params.depth, + mod_name = params.mod_name, + setting_id = params.setting_id, + + on_activated = function (new_value, old_value) + + for i = 1, #cancel_keys do + local cancel_key = cancel_keys[i] + if cancel_key == new_value.main then + + -- Prevent unbinding the mod options menu + if params.setting_id ~= "open_vmf_options" then + + params.keybind_text = "" + params.keys = {} + + set_new_keybind(self, params) + end + return true + end end - end - func(self, layout_data, ...) - - for _, button_info in ipairs(self.active_button_data) do - if button_info.transition == "vmf_options_view_open" then - button_info.widget.style.text.localize = false - button_info.widget.style.text_disabled.localize = false - button_info.widget.style.text_click.localize = false - button_info.widget.style.text_hover.localize = false - button_info.widget.style.text_selected.localize = false + for i = 1, #reserved_keys do + local reserved_key = reserved_keys[i] + if reserved_key == new_value.main then + return false + end end - end - end) + + local device_type = InputUtils.key_device_type(new_value.main) + local key_name = InputUtils.local_key_name(new_value.main, device_type) + + params.keybind_text = key_name + params.keys = {key_name} + + set_new_keybind(self, params) + return true + end, + + get_function = function (template) + + local setting = get_mod(template.mod_name):get(template.setting_id) + local local_name = setting and setting[1] + if not local_name then + return false + end + + local global_name = InputUtils.local_to_global_name(local_name, "keyboard") + return { + main = global_name, + disablers = {}, + enablers = {}, + } + end, + } + + return template +end +_type_template_map["keybind"] = create_keybind_template -else +local function widget_data_to_template(self, data) + if data and data.type and type(data.type) == "string" and _type_template_map[data.type] then + return _type_template_map[data.type](self, data) + else + vmf:dump(data, "widget", 1) + vmf.throw_error("[widget \"%s\"]: 'type' field must contain valid widget type name.", data.setting_id) + end +end - local function get_mod_options_button_index(layout_logic) - for button_index, button_data in ipairs(layout_logic.active_button_data) do - if button_data.transition == "vmf_options_view_open" then - return button_index +-- Add mod categories to options view +local create_mod_category = function (self, categories, widget_data) + local category = { + can_be_reset = widget_data.can_be_reset or true, + display_name = widget_data.readable_mod_name or widget_data.mod_name or "", + icon = widget_data.icon_material or "content/ui/materials/icons/system/settings/category_gameplay", + custom = true + } + categories[#categories + 1] = category + return category +end + +-- #################################################################################################################### +-- ##### Hooks ######################################################################################################## +-- #################################################################################################################### + +-- #################################################################################################################### +-- ##### VMF internal functions and variables ######################################################################### +-- #################################################################################################################### + + +-- Add mod settings to options view +vmf.create_mod_options_settings = function (self, options_templates) + local categories = options_templates.categories + local settings = options_templates.settings + + for _, mod_data in ipairs(vmf.options_widgets_data) do + local category = create_mod_category(self, categories, mod_data[1]) + + for _, widget_data in ipairs(mod_data) do + local template = widget_data_to_template(self, widget_data) + if template then + template.custom = true + template.category = category.display_name + + settings[#settings + 1] = template end end end + return options_templates - -- Disable localization for Mod Options button widget for pc version of ESC-menu - -- Widget definition: ingame_view_definitions.lua -> UIWidgets.create_default_button - vmf:hook_safe(IngameView, "on_enter", function (self) - self.layout_logic._ingame_view = self - end) - vmf:hook_safe(IngameViewLayoutLogic, "setup_button_layout", function (self) - if self._ingame_view then - local mod_options_button_index = get_mod_options_button_index(self) - local button_widget = self._ingame_view.stored_buttons[mod_options_button_index] - button_widget.style.title_text.localize = false - button_widget.style.title_text_shadow.localize = false - button_widget.style.title_text_disabled.localize = false - end - end) + --[[local settings = OptionsView._options_templates.settings + for name, this_mod in pairs(Mods) do + -- Custom settings + if type(this_mod) == "table" and this_mod.options then - -- Disable localization for Mod Options button widget for console version of ESC-menu - -- Widget definition: hero_window_ingame_view_definitions.lua -> create_title_button - vmf:hook_safe(HeroWindowIngameView, "on_enter", function (self) - local button_widget = self._title_button_widgets[get_mod_options_button_index(self.layout_logic)] - button_widget.style.text.localize = false - button_widget.style.text_hover.localize = false - button_widget.style.text_shadow.localize = false - button_widget.style.text_disabled.localize = false - end) + local text = this_mod.text or name + Mods.Localization.add("loc_settings_menu_group_mods_"..name, text) - - -- Disable Mod Options button during mods reloading - vmf:hook_safe(IngameViewLayoutLogic, "_update_menu_options_enabled_states", function (self) - local mod_options_button_index = get_mod_options_button_index(self) - local mod_options_button_data = self.active_button_data[mod_options_button_index] - mod_options_button_data.disabled = _button_injection_data.mod_options_button_disabled - end) - - - -- Inject Mod Options button in all possible ESC-menu layouts (except for developer's one, because it will increase - -- the number of buttons to 10, when the hard limit is 9, which will crash the game) - vmf:hook_safe(IngameViewLayoutLogic, "init", function (self) - local mod_options_button = { - display_name = vmf:localize("mods_options"), - transition = "vmf_options_view_open", - fade = false - } - for _, layout in pairs(self.layout_list) do - for i = 1, #layout do - if layout[i].transition == "options_menu" and layout[i + 1].transition ~= "vmf_options_view_open" then - table.insert(layout, i + 1, mod_options_button) - break + local options_no_after = 0 + for _, option in pairs(this_mod.options) do + if not option.after then + options_no_after = options_no_after + 1 end end + + if options_no_after > 0 then + settings[#settings+1] = { + widget_type = "group_header", + group_name = "mods_settings", + display_name = "loc_settings_menu_group_mods_"..name, + category = "loc_settings_menu_category_mods", + custom = true, + } + end + + for _, setting in pairs(this_mod.options) do + setting.custom = true + setting.category = setting.category or "loc_settings_menu_category_mods" + setting.indentation_level = setting.after and 1 or 0 + if setting.after then + local index = self:after_index(OptionsView, setting.after) + table.insert(settings, index, setting) + else + settings[#settings+1] = setting + end + end + end - end) - - + end]] end vmf.initialize_vmf_options_view = function () - vmf:dofile("scripts/mods/vmf/modules/ui/options/vmf_options_view") - _button_injection_data.mod_options_button_disabled = false + vmf:add_require_path("dmf/scripts/mods/vmf/modules/ui/options/vmf_options_view") + vmf:add_require_path("dmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_definitions") + vmf:add_require_path("dmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_settings") + vmf:add_require_path("dmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_content_blueprints") + + vmf:register_view({ + view_name = "vmf_options_view", + view_settings = { + init_view_function = function (ingame_ui_context) + return true + end, + class = "VMFOptionsView", + disable_game_world = false, + display_name = "loc_options_view_display_name", + game_world_blur = 1.1, + load_always = true, + load_in_hub = true, + package = "packages/ui/views/options_view/options_view", + path = "dmf/scripts/mods/vmf/modules/ui/options/vmf_options_view", + state_bound = true, + enter_sound_events = { + "wwise/events/ui/play_ui_enter_short" + }, + exit_sound_events = { + "wwise/events/ui/play_ui_back_short" + }, + wwise_states = { + options = "ingame_menu" + } + }, + view_transitions = {}, + view_options = { + close_all = false, + close_previous = false, + close_transition_time = nil, + transition_time = nil + } + }) + + vmf:dofile("dmf/scripts/mods/vmf/modules/ui/options/vmf_options_view") end - -vmf.disable_mods_options_button = function () - _button_injection_data.mod_options_button_disabled = true -end +-- #################################################################################################################### +-- ##### Script ####################################################################################################### +-- #################################################################################################################### 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 de31a69..0f0016a 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 @@ -1,4269 +1,15 @@ ---[[ - * If you're changing settings defined in widget via mod:set don't use values which aren't defined in widget - * Don't use tables in settings defined in widgets. The widgets are build to work with - basic datatypes (with exception of keybind widgets, but they are working differently) - * Using tables in mod:get and mod:set for the settings that are not defined in widgets is fine though, - but keep in mind, that every time you do it, this table will be cloned, so don't do it very frequently, - especially if the tables are big - * No external config files. Everything should be stored via mod:set - * Use mod:set only if you need setting to be saved in the config file - - - @TODO: [BUG] checkbox is checked at first tick after showing, since local_offset function is called after rect drawing - @TODO: [BUG] searchbar's input will stop working after using russian character - @TODO: [IMPROVEMENT] opened widgets are shown even behind the borders - @TODO: [IMPROVEMENT] dropdown widget goes up if there's not enough space at the bottom -]] local vmf = get_mod("VMF") ---vmf:custom_textures("header_fav_icon", "header_fav_icon_lit", "header_fav_arrow", "search_bar_icon") -vmf.custom_atlas(vmf, "materials/vmf/vmf_atlas", "vmf_atlas", "vmf_atlas_masked") - -vmf.inject_materials(vmf, "ingame_ui", "materials/vmf/vmf_atlas") +local _widgets_by_name -- #################################################################################################################### --- ##### MENU WIDGETS DEFINITIONS ##################################################################################### +-- ##### Local functions ############################################################################################## -- #################################################################################################################### --- Bandaid Fix for fancy ass ascii causing line checking errors. --- luacheck: no max_line_length --- Bandaid Fix for this file using lots of duplicated code and shadowed variables that could be refactored --- luacheck: ignore 4 - --- ███████╗ ██████╗███████╗███╗ ██╗███████╗ ██████╗ ██████╗ █████╗ ██████╗ ██╗ ██╗███████╗ --- ██╔════╝██╔════╝██╔════╝████╗ ██║██╔════╝██╔════╝ ██╔══██╗██╔══██╗██╔══██╗██║ ██║██╔════╝ --- ███████╗██║ █████╗ ██╔██╗ ██║█████╗ ██║ ███╗██████╔╝███████║██████╔╝███████║███████╗ --- ╚════██║██║ ██╔══╝ ██║╚██╗██║██╔══╝ ██║ ██║██╔══██╗██╔══██║██╔═══╝ ██╔══██║╚════██║ --- ███████║╚██████╗███████╗██║ ╚████║███████╗╚██████╔╝██║ ██║██║ ██║██║ ██║ ██║███████║ --- ╚══════╝ ╚═════╝╚══════╝╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝ - -local scenegraph_definition = { - - sg_root = { - size = {1920, 1080}, - position = {0, 0, UILayer.default + 10}, - is_root = true - }, - - sg_aligner = { - size = {1920, 1080}, - position = {0, 0, 0}, - - parent = "sg_root", - - horizontal_alignment = "center", - vertical_alignment = "center" - }, - - sg_background_border = { - size = {1206, 1056}, - position = {357, 12, 0}, - - parent = "sg_aligner" - }, - - sg_background_settings_list = { - size = {1200, 1000}, - position = {360, 65, 1}, - - parent = "sg_aligner" - }, - - sg_mousewheel_scroll_area = { - size = {1200, 1000}, - position = {0, 0, 0}, - - parent = "sg_background_settings_list" - }, - - sg_settings_list_mask = { - size = {1200, 1000}, - position = {0, 0, 2}, - - parent = "sg_background_settings_list" - }, - - sg_settings_list_mask_edge_fade_top = { - size = {1200, 15}, - position = {0, 985, 3}, - - parent = "sg_background_settings_list" - }, - - sg_settings_list_mask_edge_fade_bottom = { - size = {1200, 15}, - position = {0, 0, 3}, - - parent = "sg_background_settings_list" - }, - - sg_search_bar = { - size = {1200, 47}, - position = {360, 15, 1}, - - parent = "sg_aligner" - }, - - sg_scrollbar = { - size = {360, 1050}, - position = {1562, 40, 0}, - - parent = "sg_aligner" - }, - - sg_dead_space_filler = { - size = {1920, 1080}, - position = {0, 0, 0}, - scale = "fit" - } -} - - - - - - - - - - - - - - - - - - - -local function create_scrollbar(height, scenegraph_id) - return { - element = { - passes = { - { - pass_type = "texture", - style_id = "scroll_bar_bottom", - texture_id = "scroll_bar_bottom", - content_check_function = function (content) - return not content.disable_frame - end - }, - { - pass_type = "texture", - style_id = "scroll_bar_bottom_bg", - texture_id = "scroll_bar_bottom_bg", - content_check_function = function (content) - return not content.disable_frame - end - }, - { - pass_type = "tiled_texture", - style_id = "scroll_bar_middle", - texture_id = "scroll_bar_middle", - content_check_function = function (content) - return not content.disable_frame - end - }, - { - pass_type = "tiled_texture", - style_id = "scroll_bar_middle_bg", - texture_id = "scroll_bar_middle_bg", - content_check_function = function (content) - return not content.disable_frame - end - }, - { - pass_type = "texture", - style_id = "scroll_bar_top", - texture_id = "scroll_bar_top", - content_check_function = function (content) - return not content.disable_frame - end - }, - { - pass_type = "texture", - style_id = "scroll_bar_top_bg", - texture_id = "scroll_bar_top_bg", - content_check_function = function (content) - return not content.disable_frame - end - }, - { - style_id = "button_down", - pass_type = "hotspot", - content_id = "button_down_hotspot" - }, - { - style_id = "button_up", - pass_type = "hotspot", - content_id = "button_up_hotspot" - }, - { - pass_type = "local_offset", - offset_function = function (ui_scenegraph_, ui_style, ui_content, input_service_) - local scroll_bar_info = ui_content.scroll_bar_info - local scroll_bar_box = ui_style.scroll_bar_box - local scroll_size_y = scroll_bar_box.scroll_size_y - local percentage = math.max(scroll_bar_info.bar_height_percentage, 0.05) - scroll_bar_box.size[2] = scroll_size_y * percentage - local button_up_hotspot = ui_content.button_up_hotspot - - if button_up_hotspot.is_hover and button_up_hotspot.is_clicked == 0 then - ui_content.button_up = "scroll_bar_button_up_clicked" - else - ui_content.button_up = "scroll_bar_button_up" - end - - local button_down_hotspot = ui_content.button_down_hotspot - - if button_down_hotspot.is_hover and button_down_hotspot.is_clicked == 0 then - ui_content.button_down = "scroll_bar_button_down_clicked" - else - ui_content.button_down = "scroll_bar_button_down" - end - - local button_scroll_step = ui_content.button_scroll_step or 0.1 - - if button_up_hotspot.on_release then - local size_y = scroll_bar_box.size[2] - local scroll_size_y = scroll_bar_box.scroll_size_y - local start_y = scroll_bar_box.start_offset[2] - local end_y = (start_y + scroll_size_y) - size_y - local step_ = size_y / (start_y + end_y) - scroll_bar_info.value = math.max(scroll_bar_info.value - button_scroll_step, 0) - elseif button_down_hotspot.on_release then - local size_y = scroll_bar_box.size[2] - local scroll_size_y = scroll_bar_box.scroll_size_y - local start_y = scroll_bar_box.start_offset[2] - local end_y = (start_y + scroll_size_y) - size_y - local step_ = size_y / (start_y + end_y) - scroll_bar_info.value = math.min(scroll_bar_info.value + button_scroll_step, 1) - end - - return - end - }, - { - pass_type = "texture", - style_id = "button_down", - texture_id = "button_down" - }, - { - pass_type = "texture", - style_id = "button_up", - texture_id = "button_up" - }, - { - style_id = "scroll_bar_box", - pass_type = "hotspot", - content_id = "scroll_bar_info" - }, - { - style_id = "scroll_bar_box", - pass_type = "held", - content_id = "scroll_bar_info", - held_function = function (ui_scenegraph, ui_style, ui_content, input_service) - local cursor = UIInverseScaleVectorToResolution(input_service.get(input_service, "cursor")) - local cursor_y = cursor[2] - local world_pos = UISceneGraph.get_world_position(ui_scenegraph, ui_content.scenegraph_id) - local world_pos_y = world_pos[2] - local offset = ui_style.offset - local scroll_box_start = world_pos_y + offset[2] - local cursor_y_norm = cursor_y - scroll_box_start - - if not ui_content.click_pos_y then - ui_content.click_pos_y = cursor_y_norm - end - - local click_pos_y = ui_content.click_pos_y - local delta = cursor_y_norm - click_pos_y - local start_y = ui_style.start_offset[2] - local end_y = (start_y + ui_style.scroll_size_y) - ui_style.size[2] - local offset_y = math.clamp(offset[2] + delta, start_y, end_y) - local scroll_size = end_y - start_y - local scroll = end_y - offset_y - ui_content.value = (scroll ~= 0 and scroll / scroll_size) or 0 - - return - end, - release_function = function (ui_scenegraph_, ui_style_, ui_content, input_service_) - ui_content.click_pos_y = nil - - return - end - }, - { - pass_type = "local_offset", - content_id = "scroll_bar_info", - offset_function = function (ui_scenegraph_, ui_style, ui_content, input_service_) - local box_style = ui_style.scroll_bar_box - local box_size_y = box_style.size[2] - local start_y = box_style.start_offset[2] - local end_y = (start_y + box_style.scroll_size_y) - box_size_y - local scroll_size = end_y - start_y - local value = ui_content.value - local offset_y = start_y + scroll_size * (1 - value) - box_style.offset[2] = offset_y - local box_bottom = ui_style.scroll_bar_box_bottom - local box_middle = ui_style.scroll_bar_box_middle - local box_top = ui_style.scroll_bar_box_top - local box_bottom_size_y = box_bottom.size[2] - local box_top_size_y = box_top.size[2] - box_bottom.offset[2] = offset_y - box_top.offset[2] = (offset_y + box_size_y) - box_top_size_y - box_middle.offset[2] = offset_y + box_bottom_size_y - box_middle.size[2] = box_size_y - box_bottom_size_y - box_top_size_y - - return - end - }, - { - pass_type = "texture", - style_id = "scroll_bar_box_bottom", - texture_id = "scroll_bar_box_bottom" - }, - { - pass_type = "tiled_texture", - style_id = "scroll_bar_box_middle", - texture_id = "scroll_bar_box_middle" - }, - { - pass_type = "texture", - style_id = "scroll_bar_box_top", - texture_id = "scroll_bar_box_top" - } - } - }, - content = { - scroll_bar_bottom_bg = "scroll_bar_bottom_bg", - scroll_bar_top_bg = "scroll_bar_top_bg", - scroll_bar_middle = "scroll_bar_middle", - button_up = "scroll_bar_button_up", - scroll_bar_box_bottom = "scroll_bar_box_bottom", - scroll_bar_middle_bg = "scroll_bar_middle_bg", - scroll_bar_bottom = "scroll_bar_bottom", - disable_frame = false, - scroll_bar_box_middle = "scroll_bar_box_middle", - scroll_bar_box_top = "scroll_bar_box_top", - button_down = "scroll_bar_button_down", - scroll_bar_top = "scroll_bar_top", - scroll_bar_info = { - button_scroll_step = 0.1, - value = 0, - bar_height_percentage = 1, - scenegraph_id = scenegraph_id - }, - button_up_hotspot = {}, - button_down_hotspot = {} - }, - style = { - scroll_bar_bottom = { - size = { - 26, - 116 - } - }, - scroll_bar_bottom_bg = { - offset = { - 0, - 0, - -1 - }, - size = { - 26, - 116 - } - }, - scroll_bar_middle = { - offset = { - 0, - 116, - 0 - }, - size = { - 26, - height - 232 - }, - texture_tiling_size = { - 26, - 44 - } - }, - scroll_bar_middle_bg = { - offset = { - 0, - 116, - -1 - }, - size = { - 26, - height - 232 - }, - texture_tiling_size = { - 26, - 44 - } - }, - scroll_bar_top = { - offset = { - 0, - height - 116, - 0 - }, - size = { - 26, - 116 - } - }, - scroll_bar_top_bg = { - offset = { - 0, - height - 116, - -1 - }, - size = { - 26, - 116 - } - }, - button_down = { - offset = { - 5, - 4, - 0 - }, - size = { - 16, - 18 - } - }, - button_up = { - offset = { - 5, - height - 22, - 0 - }, - size = { - 16, - 18 - } - }, - scroll_bar_box = { - offset = { - 4, - 22, - 100 - }, - size = { - 18, - height - 44 - }, - color = { - 255, - 255, - 255, - 255 - }, - start_offset = { - 4, - 22, - 0 - }, - scroll_size_y = height - 44 - }, - scroll_bar_box_bottom = { - offset = { - 4, - 0, - 0 - }, - size = { - 18, - 8 - } - }, - scroll_bar_box_middle = { - offset = { - 4, - 0, - 0 - }, - size = { - 18, - 26 - }, - texture_tiling_size = { - 18, - 26 - } - }, - scroll_bar_box_top = { - offset = { - 4, - 0, - 0 - }, - size = { - 18, - 8 - } - } - }, - scenegraph_id = scenegraph_id - } +local function load_scrolling_speed_setting() + if vmf:get("vmf_options_scrolling_speed") and _widgets_by_name and _widgets_by_name["scrollbar"] then + _widgets_by_name["scrollbar"].content.scroll_speed = vmf:get("vmf_options_scrolling_speed") end ---███╗ ███╗███████╗███╗ ██╗██╗ ██╗ ██╗ ██╗██╗██████╗ ██████╗ ███████╗████████╗███████╗ ---████╗ ████║██╔════╝████╗ ██║██║ ██║ ██║ ██║██║██╔══██╗██╔════╝ ██╔════╝╚══██╔══╝██╔════╝ ---██╔████╔██║█████╗ ██╔██╗ ██║██║ ██║ ██║ █╗ ██║██║██║ ██║██║ ███╗█████╗ ██║ ███████╗ ---██║╚██╔╝██║██╔══╝ ██║╚██╗██║██║ ██║ ██║███╗██║██║██║ ██║██║ ██║██╔══╝ ██║ ╚════██║ ---██║ ╚═╝ ██║███████╗██║ ╚████║╚██████╔╝ ╚███╔███╔╝██║██████╔╝╚██████╔╝███████╗ ██║ ███████║ ---╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚══╝╚══╝ ╚═╝╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚══════╝ - - -local menu_widgets_definition = { - static_menu_elements = { - scenegraph_id = "sg_root", - element = { - passes = { - { - pass_type = "rect", - - style_id = "background_border" - }, - { - pass_type = "rect", - - style_id = "background_settings_list" - }, - { - pass_type = "texture", - - style_id = "settings_list_mask", - texture_id = "settings_list_mask_texture_id" - }, - { - pass_type = "texture_uv", - - style_id = "settings_list_mask_edge_fade_top", - content_id = "settings_list_mask_edge_fade_top" - }, - { - pass_type = "texture_uv", - - style_id = "settings_list_mask_edge_fade_bottom", - content_id = "settings_list_mask_edge_fade_bottom" - }, - { - pass_type = "rect", - - style_id = "dead_space_filler" - } - } - }, - content = { - settings_list_mask_texture_id = "mask_rect", - - settings_list_mask_edge_fade_top = { - texture_id = "mask_rect_edge_fade", - uvs = {{0, 0}, {1, 1}} - }, - - settings_list_mask_edge_fade_bottom = { - texture_id = "mask_rect_edge_fade", - uvs = {{0, 1}, {1, 0}} - } - }, - style = { - - background_border = { - scenegraph_id = "sg_background_border", - color = {255, 140, 100, 50} - }, - - background_settings_list = { - scenegraph_id = "sg_background_settings_list", - color = {255, 0, 0, 0} - }, - - settings_list_mask = { - scenegraph_id = "sg_settings_list_mask", - color = {255, 255, 255, 255} - }, - - settings_list_mask_edge_fade_top = { - scenegraph_id = "sg_settings_list_mask_edge_fade_top", - color = {255, 255, 255, 255} - }, - - settings_list_mask_edge_fade_bottom = { - scenegraph_id = "sg_settings_list_mask_edge_fade_bottom", - color = {255, 255, 255, 255} - }, - - dead_space_filler = { - scenegraph_id = "sg_dead_space_filler", - color = {150, 0, 0, 0} - } - } - }, - - search_bar = { - scenegraph_id = "sg_search_bar", - element = { - passes = { - { - pass_type = "hotspot", - - content_id = "hotspot" - }, - { - pass_type = "rect", - - style_id = "background", - - content_check_function = function (content, style) - - if content.is_active then - style.color[2] = 50 - style.color[3] = 50 - style.color[4] = 50 - else - if content.hotspot.is_hover then - style.color[2] = 25 - style.color[3] = 25 - style.color[4] = 25 - else - style.color[2] = 0 - style.color[3] = 0 - style.color[4] = 0 - end - end - return true - end - }, - { - pass_type = "texture", - - style_id = "search_icon", - texture_id = "search_icon_texture" - }, - { - pass_type = "text", - - style_id = "text", - text_id = "text" - } - } - }, - content = { - hotspot = {}, - text = "", - search_icon_texture = "search_bar_icon" - }, - style = { - text = { - offset = {46, 2, 3}, - font_size = 28, - font_type = "hell_shark", - dynamic_font = true, - text_color = Colors.get_color_table_with_alpha("white", 255) - }, - search_icon = { - size = {30, 30}, - offset = {8, 8, 3} - }, - background = { - color = {255, 0, 0, 0} - } - } - }, - - mousewheel_scroll_area = { - scenegraph_id = "sg_mousewheel_scroll_area", - element = { - passes = { - { - pass_type = "scroll", - -- the function is called only during scrolls - scroll_function = function (ui_scenegraph_, style_, content, input_service_, scroll_axis) - - content.internal_scroll_value = content.internal_scroll_value - scroll_axis.y - end - } - } - }, - content = { - internal_scroll_value = 0, - scroll_step = 0.01 - }, - style = { - } - }, - - scrollbar = create_scrollbar(scenegraph_definition.sg_scrollbar.size[2], "sg_scrollbar") -} - --- @TODO: make scrollbar full windowed o_O - -menu_widgets_definition.scrollbar.element.passes[15].pass_type = "rect" -menu_widgets_definition.scrollbar.style.scroll_bar_box_bottom.color = {200, 140, 100, 50} -menu_widgets_definition.scrollbar.style.scroll_bar_box_bottom.size[1] = 12 - -menu_widgets_definition.scrollbar.element.passes[16].pass_type = "rect" -menu_widgets_definition.scrollbar.style.scroll_bar_box_middle.color = {200, 140, 100, 50} -menu_widgets_definition.scrollbar.style.scroll_bar_box_middle.size[1] = 12 - -menu_widgets_definition.scrollbar.element.passes[17].pass_type = "rect" -menu_widgets_definition.scrollbar.style.scroll_bar_box_top.color = {200, 140, 100, 50} -menu_widgets_definition.scrollbar.style.scroll_bar_box_top.size[1] = 12 - - - -local original_scrollbar_function = menu_widgets_definition.scrollbar.element.passes[9].offset_function - -menu_widgets_definition.scrollbar.element.passes[9].offset_function = function (scenegraph, style, content, input_service) - original_scrollbar_function(scenegraph, style, content, input_service) - - style.scroll_bar_box_top.color = content.scroll_bar_info.is_hover and {255, 140, 100, 50} or {200, 140, 100, 50} - style.scroll_bar_box_middle.color = content.scroll_bar_info.is_hover and {255, 140, 100, 50} or {200, 140, 100, 50} - style.scroll_bar_box_bottom.color = content.scroll_bar_info.is_hover and {255, 140, 100, 50} or {200, 140, 100, 50} -end - -menu_widgets_definition.scrollbar.content.scroll_bar_info.bar_height_percentage = 0.5 -menu_widgets_definition.scrollbar.content.scroll_bar_info.old_value = 0 -menu_widgets_definition.scrollbar.content.disable_frame = true -menu_widgets_definition.scrollbar.style.scroll_bar_box.size[1] = 360 -- don't change visual scrollbox size - -menu_widgets_definition.scrollbar.content.button_up_hotspot.disable_button = true -menu_widgets_definition.scrollbar.content.button_down_hotspot.disable_button = true - --- removing up and down buttons -table.remove(menu_widgets_definition.scrollbar.element.passes, 7) -table.remove(menu_widgets_definition.scrollbar.element.passes, 7) -table.remove(menu_widgets_definition.scrollbar.element.passes, 8) -table.remove(menu_widgets_definition.scrollbar.element.passes, 8) - - - - - - --- #################################################################################################################### --- ##### SETTINGS LIST WIDGETS DEFINITIONS ############################################################################ --- #################################################################################################################### - -script_data.ui_debug_hover = false - -local DEBUG_WIDGETS = false - -local SETTINGS_LIST_HEADER_WIDGET_SIZE = {1194, 80} -local SETTINGS_LIST_REGULAR_WIDGET_SIZE = {1194, 50} - - -local function create_show_widget_condition(widget_definition) - local show_widget_condition = nil - if widget_definition.show_widget_condition then - show_widget_condition = {} - for _, i in ipairs(widget_definition.show_widget_condition) do - show_widget_condition[i] = true - end - end - return show_widget_condition -end - --- ██╗ ██╗███████╗ █████╗ ██████╗ ███████╗██████╗ --- ██║ ██║██╔════╝██╔══██╗██╔══██╗██╔════╝██╔══██╗ --- ███████║█████╗ ███████║██║ ██║█████╗ ██████╔╝ --- ██╔══██║██╔══╝ ██╔══██║██║ ██║██╔══╝ ██╔══██╗ --- ██║ ██║███████╗██║ ██║██████╔╝███████╗██║ ██║ --- ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝ ╚═╝ - - -local function create_header_widget(widget_definition, scenegraph_id) - - local widget_size = SETTINGS_LIST_HEADER_WIDGET_SIZE - local offset_y = -widget_size[2] - - local definition = { - element = { - passes = { - -- VISUALS - { - pass_type = "texture", - - style_id = "background", - texture_id = "rect_masked_texture" - }, - { - pass_type = "texture", - - style_id = "highlight_texture", - texture_id = "highlight_texture", - - content_check_function = function (content) - return content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - end - }, - { - pass_type = "texture", - - style_id = "fav_icon", - texture_id = "fav_icon_texture", - - content_check_function = function (content) - return content.is_favorited or content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - end - }, - { - pass_type = "texture", - - style_id = "fav_arrow_up", - texture_id = "fav_arrow_texture", - - content_check_function = function (content) - return content.is_favorited and content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - end - }, - { - pass_type = "rotated_texture", - - style_id = "fav_arrow_down", - texture_id = "fav_arrow_texture", - - content_check_function = function (content) - return content.is_favorited and content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - end - }, - { - pass_type = "text", - - style_id = "text", - text_id = "text" - }, - --[[ - { - pass_type = "texture", - - style_id = "checkbox", - texture_id = "checkbox_texture", - - content_check_function = function (content) - return content.is_checkbox_visible - end - }, - --]] - ---[[ - { - pass_type = "texture", - - style_id = "checkbox_border", - texture_id = "rect_masked_texture", - - content_check_function = function (content) - return content.is_checkbox_visible - end - }, - { - pass_type = "texture", - - style_id = "checkbox_background", - texture_id = "rect_masked_texture", - - content_check_function = function (content) - return content.is_checkbox_visible - end - }, - { - pass_type = "texture", - - style_id = "checkbox_fill", - texture_id = "rect_masked_texture", - - content_check_function = function (content) - return content.is_checkbox_visible - end - }, - --]] - -- HOTSPOTS - { - pass_type = "hotspot", - - style_id = "fav_icon_hotspot", - content_id = "fav_icon_hotspot" - }, - { - pass_type = "hotspot", - - style_id = "fav_arrow_up_hotspot", - content_id = "fav_arrow_up_hotspot", - - content_check_function = function (content) - return content.parent.is_favorited - end - }, - { - pass_type = "hotspot", - - style_id = "fav_arrow_down_hotspot", - content_id = "fav_arrow_down_hotspot", - - content_check_function = function (content) - return content.parent.is_favorited - end - }, - { - pass_type = "hotspot", - - style_id = "checkbox_hotspot", - content_id = "checkbox_hotspot", - - content_check_function = function (content) - return content.parent.is_checkbox_visible - end - }, - { - pass_type = "hotspot", - - content_id = "highlight_hotspot" - }, - -- PROCESSING - { - pass_type = "local_offset", - - offset_function = function (ui_scenegraph_, style, content, ui_renderer) - - local is_interactable = content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - - if is_interactable then - - if content.tooltip_text then - style.tooltip_text.cursor_offset = content.callback_fit_tooltip_to_the_screen(content, style.tooltip_text, ui_renderer) - end - - if content.highlight_hotspot.on_release and not content.checkbox_hotspot.on_release and not content.fav_icon_hotspot.on_release - and not content.fav_arrow_up_hotspot.on_release and not content.fav_arrow_down_hotspot.on_release then - - content.callback_hide_sub_widgets(content) - end - - if content.fav_icon_hotspot.on_release and not content.fav_arrow_up_hotspot.on_release and not content.fav_arrow_down_hotspot.on_release then - content.callback_favorite(content) - end - - if content.fav_arrow_up_hotspot.on_release then - content.callback_move_favorite(content, true) - end - - if content.fav_arrow_down_hotspot.on_release then - content.callback_move_favorite(content, false) - end - - if content.checkbox_hotspot.on_release then - - if content.is_widget_collapsed then - content.callback_hide_sub_widgets(content) - end - - local mod_name = content.mod_name - local is_mod_enabled = not content.is_checkbox_checked - - content.is_checkbox_checked = is_mod_enabled - - content.callback_mod_state_changed(mod_name, is_mod_enabled) - end - end - - content.fav_icon_texture = content.is_favorited and "header_fav_icon_lit" or "header_fav_icon" - --content.checkbox_texture = content.is_checkbox_checked and "checkbox_checked" or "checkbox_unchecked" - style.fav_arrow_up.color[1] = is_interactable and content.fav_arrow_up_hotspot.is_hover and 255 or 90 - style.fav_arrow_down.color[1] = is_interactable and content.fav_arrow_down_hotspot.is_hover and 255 or 90 - - style.background.color = content.is_widget_collapsed and {255, 110, 78, 39} or {255, 57, 39, 21} - if content.is_checkbox_checked then - style.checkbox_fill.color = is_interactable and content.checkbox_hotspot.is_hover and {255, 255, 255, 255} or {255, 255, 168, 0} - else - style.checkbox_fill.color = is_interactable and content.checkbox_hotspot.is_hover and {255, 100, 100, 100} or {255, 0, 0, 0} - end - if content.is_widget_collapsed then - style.checkbox_border.color = is_interactable and content.checkbox_hotspot.is_hover and {255, 166, 118, 61} or {255, 154, 109, 55} - else - style.checkbox_border.color = is_interactable and content.checkbox_hotspot.is_hover and {255, 103, 71, 38} or {255, 89, 61, 32} - end - end - }, - -- TOOLTIP - { - pass_type = "tooltip_text", - - text_id = "tooltip_text", - style_id = "tooltip_text", - content_check_function = function (content) - return content.tooltip_text and content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - end - }, - -- DEBUG - { - pass_type = "border", - - content_check_function = function (content_, style) - if DEBUG_WIDGETS then - style.thickness = 1 - end - - return DEBUG_WIDGETS - end - }, - { - pass_type = "rect", - - style_id = "debug_middle_line", - content_check_function = function () - return DEBUG_WIDGETS - end - } - } - }, - content = { - is_checkbox_checked = true, - is_checkbox_visible = false, - is_widget_visible = true, - is_widget_collapsed = widget_definition.is_collapsed, - is_favorited = widget_definition.is_favorited, - - rect_masked_texture = "rect_masked", - fav_icon_texture = "header_fav_icon", - --checkbox_texture = "checkbox_unchecked", - highlight_texture = "playerlist_hover", - background_texture = "header_background", - fav_arrow_texture = "header_fav_arrow", - - fav_icon_hotspot = {}, - fav_arrow_up_hotspot = {}, - fav_arrow_down_hotspot = {}, - checkbox_hotspot = {}, - highlight_hotspot = {}, - - text = widget_definition.readable_mod_name, - tooltip_text = widget_definition.tooltip, - - mod_name = widget_definition.mod_name, - widget_type = widget_definition.type - }, - style = { - - -- VISUALS - - background = { - size = {widget_size[1], widget_size[2] - 3}, - offset = {0, offset_y + 1, 0}, - color = {255, 57, 39, 21} - }, - - highlight_texture = { - size = {widget_size[1], widget_size[2] - 3}, - offset = {0, offset_y + 1, 2}, - color = {255, 255, 255, 255}, - masked = true - }, - - fav_icon = { - size = {30, 30}, - offset = {15, offset_y + 25, 3}, - masked = true - }, - - fav_arrow_up = { - size = {20, 20}, - offset = {20, offset_y + 57, 3}, - color = {90, 255, 255, 255}, - masked = true - }, - - fav_arrow_down = { - size = {20, 20}, - offset = {20, offset_y + 3, 3}, - angle = math.pi, - pivot = {10, 10}, - color = {90, 255, 255, 255}, - masked = true - }, - - text = { - offset = {60, offset_y + 18, 3}, - font_size = 28, - font_type = "hell_shark_masked", - dynamic_font = true, - text_color = Colors.get_color_table_with_alpha("white", 255) - }, ---[[ - checkbox = { - size = {30, 30}, - offset = {widget_size[1] - 180, offset_y + 25, 3}, - masked = true - }, -]] - checkbox_border = { - offset = {widget_size[1] - 184, offset_y + 21, 1}, - size = {38, 38}, - color = {255, 89, 61, 32} - }, - - checkbox_background = { - offset = {widget_size[1] - 176, offset_y + 29, 3}, - size = {22, 22}, - color = {255, 0, 0, 0} - }, - - checkbox_fill = { - offset = {widget_size[1] - 174, offset_y + 31, 4}, - size = {18, 18}, - color = {255, 255, 168, 0} - }, - -- HOTSPOTS - - fav_icon_hotspot = { - size = {60, widget_size[2]}, - offset = {0, offset_y, 3} - }, - - fav_arrow_up_hotspot = { - size = {60, 20}, - offset = {0, offset_y + 60, 3} - }, - - fav_arrow_down_hotspot = { - size = {60, 20}, - offset = {0, offset_y, 3} - }, - - checkbox_hotspot = { - size = {270, widget_size[2]}, - offset = {widget_size[1] - 300, offset_y, 0} - }, - - -- TOOLTIP - - tooltip_text = { - font_type = "hell_shark", - font_size = 18, - horizontal_alignment = "left", - vertical_alignment = "top", - cursor_side = "right", - max_width = 600, - cursor_offset = {27, 27}, - cursor_offset_bottom = {27, 27}, - cursor_offset_top = {27, -27} - }, - - -- DEBUG - - debug_middle_line = { - size = {widget_size[1], 1}, - offset = {0, (offset_y + widget_size[2]/2) - 1, 3}, - color = {200, 0, 255, 0} - }, - - offset = {0, offset_y, 0}, - size = {widget_size[1], widget_size[2]}, - color = {50, 255, 255, 255} - }, - scenegraph_id = scenegraph_id, - offset = {0, 0, 0} - } - - return UIWidget.init(definition) -end - --- ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗██████╗ ██████╗ ██╗ ██╗ --- ██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝██╔══██╗██╔═══██╗╚██╗██╔╝ --- ██║ ███████║█████╗ ██║ █████╔╝ ██████╔╝██║ ██║ ╚███╔╝ --- ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ██╔══██╗██║ ██║ ██╔██╗ --- ╚██████╗██║ ██║███████╗╚██████╗██║ ██╗██████╔╝╚██████╔╝██╔╝ ██╗ --- ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝ - -local function create_checkbox_widget(widget_definition, scenegraph_id) - - local widget_size = SETTINGS_LIST_REGULAR_WIDGET_SIZE - local offset_y = -widget_size[2] - - local show_widget_condition = create_show_widget_condition(widget_definition) - - local definition = { - element = { - passes = { - -- VISUALS - { - pass_type = "texture", - - style_id = "background", - texture_id = "rect_masked_texture", - - content_check_function = function (content) - return content.is_widget_collapsed - end - }, - { - pass_type = "texture", - - style_id = "highlight_texture", - texture_id = "highlight_texture", - content_check_function = function (content) - return content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - end - }, - { - pass_type = "text", - - style_id = "text", - text_id = "text" - }, - { - pass_type = "texture", - - style_id = "checkbox_border", - texture_id = "rect_masked_texture" - }, - { - pass_type = "texture", - - style_id = "checkbox_background", - texture_id = "rect_masked_texture" - }, - { - pass_type = "texture", - - style_id = "checkbox_fill", - texture_id = "rect_masked_texture" - }, - -- HOTSPOTS - { - pass_type = "hotspot", - - style_id = "checkbox_hotspot", - content_id = "checkbox_hotspot" - }, - { - pass_type = "hotspot", - - content_id = "highlight_hotspot" - }, - -- PROCESSING - { - pass_type = "local_offset", - - offset_function = function (ui_scenegraph_, style, content, ui_renderer) - - local is_interactable = content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - - if is_interactable then - - if content.tooltip_text then - style.tooltip_text.cursor_offset = content.callback_fit_tooltip_to_the_screen(content, style.tooltip_text, ui_renderer) - end - - if content.highlight_hotspot.on_release and not content.checkbox_hotspot.on_release then - content.callback_hide_sub_widgets(content) - end - - if content.checkbox_hotspot.on_release then - - if content.is_widget_collapsed then - content.callback_hide_sub_widgets(content) - end - - local mod_name = content.mod_name - local setting_id = content.setting_id - local old_value = content.is_checkbox_checked - local new_value = not old_value - - content.is_checkbox_checked = new_value - - content.callback_setting_changed(mod_name, setting_id, old_value, new_value) - end - end - - if content.is_checkbox_checked then - style.checkbox_fill.color = is_interactable and content.checkbox_hotspot.is_hover and {255, 255, 255, 255} or {255, 255, 168, 0} - else - style.checkbox_fill.color = is_interactable and content.checkbox_hotspot.is_hover and {255, 100, 100, 100} or {255, 0, 0, 0} - end - style.checkbox_border.color = is_interactable and content.checkbox_hotspot.is_hover and {255, 45, 45, 45} or {255, 30, 30, 30} - end - }, - -- TOOLTIP - { - pass_type = "tooltip_text", - - text_id = "tooltip_text", - style_id = "tooltip_text", - content_check_function = function (content) - return content.tooltip_text and content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - end - }, - -- DEBUG - { - pass_type = "rect", - - content_check_function = function () - return DEBUG_WIDGETS - end - }, - { - pass_type = "border", - - content_check_function = function (content_, style) - if DEBUG_WIDGETS then - style.thickness = 1 - end - - return DEBUG_WIDGETS - end - }, - { - pass_type = "rect", - - style_id = "debug_middle_line", - content_check_function = function () - return DEBUG_WIDGETS - end - } - } - }, - content = { - is_checkbox_checked = false, - is_widget_visible = true, - is_widget_collapsed = widget_definition.is_collapsed, - - rect_masked_texture = "rect_masked", - highlight_texture = "playerlist_hover", - - checkbox_hotspot = {}, - highlight_hotspot = {}, - - text = widget_definition.title, - tooltip_text = widget_definition.tooltip, - - mod_name = widget_definition.mod_name, - setting_id = widget_definition.setting_id, - widget_type = widget_definition.type, - default_value = widget_definition.default_value, - parent_widget_number = widget_definition.parent_index, - show_widget_condition = show_widget_condition - }, - style = { - - -- VISUALS - background = { - size = {widget_size[1], widget_size[2] - 3}, - offset = {0, offset_y + 1, 0}, - color = {255, 30, 23, 15} - }, - - highlight_texture = { - size = {widget_size[1], widget_size[2] - 3}, - offset = {0, offset_y + 1, 2}, - masked = true - }, - - text = { - offset = {60 + widget_definition.depth * 40, offset_y + 5, 3}, - font_size = 28, - font_type = "hell_shark_masked", - dynamic_font = true, - text_color = Colors.get_color_table_with_alpha("white", 255) - }, - - checkbox_border = { - offset = {widget_size[1] - 182, offset_y + 8, 1}, - size = {34, 34}, - color = {255, 30, 30, 30} - }, - - checkbox_background = { - offset = {widget_size[1] - 174, offset_y + 16, 3}, - size = {18, 18}, - color = {255, 0, 0, 0} - }, - - checkbox_fill = { - offset = {widget_size[1] - 172, offset_y + 18, 4}, - size = {14, 14}, - color = {255, 255, 168, 0} - }, - - -- HOTSPOTS - - checkbox_hotspot = { - size = {270, widget_size[2]}, - offset = {widget_size[1] - 300, offset_y, 0} - }, - - -- TOOLTIP - - tooltip_text = { - font_type = "hell_shark", - font_size = 18, - horizontal_alignment = "left", - vertical_alignment = "top", - cursor_side = "right", - max_width = 600, - cursor_offset = {27, 27}, - cursor_offset_bottom = {27, 27}, - cursor_offset_top = {27, -27}, - line_colors = { - Colors.get_color_table_with_alpha("cheeseburger", 255), - Colors.get_color_table_with_alpha("white", 255) - } - }, - - -- DEBUG - - debug_middle_line = { - size = {widget_size[1], 2}, - offset = {0, (offset_y + widget_size[2]/2) - 1, 10}, - color = {200, 0, 255, 0} - }, - - offset = {0, offset_y, 0}, - size = {widget_size[1], widget_size[2]}, - color = {50, 255, 255, 255} - }, - scenegraph_id = scenegraph_id, - offset = {0, 0, 0} - } - - return UIWidget.init(definition) -end - - --- ██████╗ ██████╗ ██████╗ ██╗ ██╗██████╗ --- ██╔════╝ ██╔══██╗██╔═══██╗██║ ██║██╔══██╗ --- ██║ ███╗██████╔╝██║ ██║██║ ██║██████╔╝ --- ██║ ██║██╔══██╗██║ ██║██║ ██║██╔═══╝ --- ╚██████╔╝██║ ██║╚██████╔╝╚██████╔╝██║ --- ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ - - -local function create_group_widget(widget_definition, scenegraph_id) - - local widget_size = SETTINGS_LIST_REGULAR_WIDGET_SIZE - local offset_y = -widget_size[2] - - local show_widget_condition = create_show_widget_condition(widget_definition) - - local definition = { - element = { - passes = { - -- VISUALS - { - pass_type = "texture", - - style_id = "background", - texture_id = "rect_masked_texture", - - content_check_function = function (content) - return content.is_widget_collapsed - end - }, - { - pass_type = "texture", - - style_id = "highlight_texture", - texture_id = "highlight_texture", - content_check_function = function (content) - return content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - end - }, - { - pass_type = "text", - - style_id = "text", - text_id = "text" - }, - -- HOTSPOTS - { - pass_type = "hotspot", - - content_id = "highlight_hotspot" - }, - -- PROCESSING - { - pass_type = "local_offset", - - offset_function = function (ui_scenegraph_, style, content, ui_renderer) - - local is_interactable = content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - - if is_interactable then - - if content.tooltip_text then - style.tooltip_text.cursor_offset = content.callback_fit_tooltip_to_the_screen(content, style.tooltip_text, ui_renderer) - end - - if content.highlight_hotspot.on_release then - content.callback_hide_sub_widgets(content) - end - end - end - }, - -- TOOLTIP - { - pass_type = "tooltip_text", - - text_id = "tooltip_text", - style_id = "tooltip_text", - content_check_function = function (content) - return content.tooltip_text and content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - end - }, - -- DEBUG - { - pass_type = "rect", - - content_check_function = function () - return DEBUG_WIDGETS - end - }, - { - pass_type = "border", - - content_check_function = function (content_, style) - if DEBUG_WIDGETS then - style.thickness = 1 - end - - return DEBUG_WIDGETS - end - }, - { - pass_type = "rect", - - style_id = "debug_middle_line", - content_check_function = function () - return DEBUG_WIDGETS - end - } - } - }, - content = { - is_widget_visible = true, - is_widget_collapsed = widget_definition.is_collapsed, - - highlight_texture = "playerlist_hover", - rect_masked_texture = "rect_masked", - - highlight_hotspot = {}, - - text = widget_definition.title, - tooltip_text = widget_definition.tooltip, - - - mod_name = widget_definition.mod_name, - setting_id = widget_definition.setting_id, - widget_type = widget_definition.type, - parent_widget_number = widget_definition.parent_index, - show_widget_condition = show_widget_condition - }, - style = { - - -- VISUALS - background = { - size = {widget_size[1], widget_size[2] - 3}, - offset = {0, offset_y + 1, 0}, - color = {255, 30, 23, 15} - }, - - highlight_texture = { - size = {widget_size[1], widget_size[2] - 3}, - offset = {0, offset_y + 1, 1}, - masked = true - }, - - text = { - offset = {60 + widget_definition.depth * 40, offset_y + 5, 2}, - font_size = 28, - font_type = "hell_shark_masked", - dynamic_font = true, - text_color = Colors.get_color_table_with_alpha("white", 255) - }, - - -- TOOLTIP - - tooltip_text = { - font_type = "hell_shark", - font_size = 18, - horizontal_alignment = "left", - vertical_alignment = "top", - cursor_side = "right", - max_width = 600, - cursor_offset = {27, 27}, - cursor_offset_bottom = {27, 27}, - cursor_offset_top = {27, -27}, - line_colors = { - Colors.get_color_table_with_alpha("cheeseburger", 255), - Colors.get_color_table_with_alpha("white", 255) - } - }, - - -- DEBUG - - debug_middle_line = { - size = {widget_size[1], 2}, - offset = {0, (offset_y + widget_size[2]/2) - 1, 10}, - color = {200, 0, 255, 0} - }, - - offset = {0, offset_y, 0}, - size = {widget_size[1], widget_size[2]}, - color = {50, 255, 255, 255} - }, - scenegraph_id = scenegraph_id, - offset = {0, 0, 0} - } - - return UIWidget.init(definition) -end - --- ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗███╗ ██╗ --- ██╔══██╗██╔══██╗██╔═══██╗██╔══██╗██╔══██╗██╔═══██╗██║ ██║████╗ ██║ --- ██║ ██║██████╔╝██║ ██║██████╔╝██║ ██║██║ ██║██║ █╗ ██║██╔██╗ ██║ --- ██║ ██║██╔══██╗██║ ██║██╔═══╝ ██║ ██║██║ ██║██║███╗██║██║╚██╗██║ --- ██████╔╝██║ ██║╚██████╔╝██║ ██████╔╝╚██████╔╝╚███╔███╔╝██║ ╚████║ --- ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══╝╚══╝ ╚═╝ ╚═══╝ - -local function create_dropdown_menu_widget(dropdown_definition, scenegraph_2nd_layer_id) - - local offset_x = dropdown_definition.style.border_bottom.offset[1] - local offset_y = dropdown_definition.style.border_bottom.offset[2] - --local offset_y = dropdown_definition.style.offset[2] - local size_x = dropdown_definition.style.border_bottom.size[1] - local options_texts = dropdown_definition.content.options_texts - local string_height = 35 - local size_y = #options_texts * string_height - - local definition = { - element = { - passes = { - { - pass_type = "texture", - - style_id = "background", - texture_id = "rect_masked_texture" - } - } - }, - content = { - rect_masked_texture = "rect_masked", - }, - style = { - background = { - size = {size_x, size_y}, - offset = {offset_x, offset_y - size_y, 20}, - color = {255, 10, 10, 10} - } - }, - scenegraph_id = scenegraph_2nd_layer_id, - offset = {0, 0, 0} - } - - for i, options_text in ipairs(options_texts) do - - -- HOTSPOT - - local lua_hotspot_name = "hotspot" .. tostring(i) - - -- pass - local pass = { - pass_type = "hotspot", - - style_id = lua_hotspot_name, - content_id = lua_hotspot_name - } - table.insert(definition.element.passes, pass) - - -- content - definition.content[lua_hotspot_name] = {} - definition.content[lua_hotspot_name].num = i - - -- style - definition.style[lua_hotspot_name] = { - offset = {offset_x, offset_y - string_height * i, 21}, - size = {size_x, string_height} - } - - -- OPTION TEXT - - local lua_text_name = "text" .. tostring(i) - - -- pass - pass = { - pass_type = "text", - - style_id = lua_text_name, - text_id = lua_text_name, - - content_check_function = function (content, style) - - style.text_color = content[lua_hotspot_name].is_hover and Colors.get_color_table_with_alpha("white", 255) or Colors.get_color_table_with_alpha("cheeseburger", 255) - return true - end - } - table.insert(definition.element.passes, pass) - - -- content - definition.content[lua_text_name] = options_text - - -- style - definition.style[lua_text_name] = { - offset = {offset_x + size_x / 2, offset_y - string_height * i, 21}, - horizontal_alignment = "center", - font_size = 24, - font_type = "hell_shark_masked", - dynamic_font = true, - text_color = Colors.get_color_table_with_alpha("cheeseburger", 255) - } - end - - return UIWidget.init(definition) -end - - -local function create_dropdown_widget(widget_definition, scenegraph_id, scenegraph_2nd_layer_id) - - local widget_size = SETTINGS_LIST_REGULAR_WIDGET_SIZE - local offset_y = -widget_size[2] - - local show_widget_condition = create_show_widget_condition(widget_definition) - - local options_texts = {} - local options_values = {} - local options_shown_widgets = {} - - 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 = { - element = { - passes = { - -- VISUALS - { - pass_type = "texture", - - style_id = "background", - texture_id = "rect_masked_texture", - - content_check_function = function (content) - return content.is_widget_collapsed - end - }, - { - pass_type = "texture", - - style_id = "highlight_texture", - texture_id = "highlight_texture", - content_check_function = function (content) - return content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - end - }, - { - pass_type = "text", - - style_id = "text", - text_id = "text" - }, - { - pass_type = "text", - - style_id = "current_option_text", - text_id = "current_option_text" - }, - { - pass_type = "texture", - - style_id = "border_top", - texture_id = "rect_masked_texture" - }, - { - pass_type = "texture", - - style_id = "border_left", - texture_id = "rect_masked_texture" - }, - { - pass_type = "texture", - - style_id = "border_right", - texture_id = "rect_masked_texture" - }, - { - pass_type = "texture", - - style_id = "border_bottom", - texture_id = "rect_masked_texture" - }, - - -- HOTSPOTS - { - pass_type = "hotspot", - - content_id = "highlight_hotspot" - }, - { - pass_type = "hotspot", - - style_id = "dropdown_hotspot", - content_id = "dropdown_hotspot" - }, - -- PROCESSING - { - pass_type = "local_offset", - - offset_function = function (ui_scenegraph_, style, content, ui_renderer) - - local is_interactable = content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - - if is_interactable then - - if content.tooltip_text then - style.tooltip_text.cursor_offset = content.callback_fit_tooltip_to_the_screen(content, style.tooltip_text, ui_renderer) - end - - if content.dropdown_hotspot.on_release then - content.callback_change_dropdown_menu_visibility(content) - end - - if content.highlight_hotspot.on_release and not content.dropdown_hotspot.on_release then - content.callback_hide_sub_widgets(content) - end - end - - if content.is_dropdown_menu_opened then - - local old_value = content.options_values[content.current_option_number] - - if content.callback_draw_dropdown_menu(content) then - - if content.is_widget_collapsed then - content.callback_hide_sub_widgets(content) - end - - local mod_name = content.mod_name - local setting_id = content.setting_id - local new_value = content.options_values[content.current_option_number] - - content.callback_setting_changed(mod_name, setting_id, old_value, new_value) - end - end - - style.current_option_text.text_color = (is_interactable and content.dropdown_hotspot.is_hover or content.is_dropdown_menu_opened) and Colors.get_color_table_with_alpha("white", 255) or Colors.get_color_table_with_alpha("cheeseburger", 255) - - local new_border_color = is_interactable and content.dropdown_hotspot.is_hover and {255, 45, 45, 45} or {255, 30, 30, 30} - style.border_top.color = new_border_color - style.border_left.color = new_border_color - style.border_right.color = new_border_color - style.border_bottom.color = new_border_color - end - }, - -- TOOLTIP - { - pass_type = "tooltip_text", - - text_id = "tooltip_text", - style_id = "tooltip_text", - content_check_function = function (content) - return content.tooltip_text and content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - end - }, - -- DEBUG - { - pass_type = "rect", - - content_check_function = function () - return DEBUG_WIDGETS - end - }, - { - pass_type = "border", - - content_check_function = function (content_, style) - if DEBUG_WIDGETS then - style.thickness = 1 - end - - return DEBUG_WIDGETS - end - }, - { - pass_type = "rect", - - style_id = "debug_middle_line", - content_check_function = function () - return DEBUG_WIDGETS - end - } - } - }, - content = { - is_widget_visible = true, - is_widget_collapsed = widget_definition.is_collapsed, - - highlight_texture = "playerlist_hover", - rect_masked_texture = "rect_masked", - --background_texture = "common_widgets_background_lit", - - highlight_hotspot = {}, - dropdown_hotspot = {}, - - text = widget_definition.title, - tooltip_text = widget_definition.tooltip, - - mod_name = widget_definition.mod_name, - setting_id = widget_definition.setting_id, - widget_type = widget_definition.type, - - 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_index, - show_widget_condition = show_widget_condition - }, - style = { - - -- VISUALS - - background = { - size = {widget_size[1], widget_size[2] - 3}, - offset = {0, offset_y + 1, 0}, - color = {255, 30, 23, 15} - }, - - highlight_texture = { - size = {widget_size[1], widget_size[2] - 3}, - offset = {0, offset_y + 1, 2}, - masked = true - }, - - text = { - offset = {60 + widget_definition.depth * 40, offset_y + 5, 3}, - font_size = 28, - font_type = "hell_shark_masked", - dynamic_font = true, - text_color = Colors.get_color_table_with_alpha("white", 255) - }, - - border_top = { - size = {270, 2}, - offset = {widget_size[1] - 300, offset_y + (widget_size[2] - 10), 1}, - color = {255, 30, 30, 30} - }, - - border_left = { - size = {2, widget_size[2] - 16}, - offset = {widget_size[1] - 300, offset_y + 8, 1}, - color = {255, 30, 30, 30} - }, - - border_right = { - size = {2, widget_size[2] - 16}, - offset = {widget_size[1] - 32, offset_y + 8, 1}, - color = {255, 30, 30, 30} - }, - - border_bottom = { - size = {270, 2}, - offset = {widget_size[1] - 300, offset_y + 8, 1}, - color = {255, 30, 30, 30} - }, - - current_option_text = { - offset = {widget_size[1] - 165, offset_y + 4, 3}, - horizontal_alignment = "center", - font_size = 28, - font_type = "hell_shark_masked", - dynamic_font = true, - text_color = Colors.get_color_table_with_alpha("cheeseburger", 255) - }, - - -- HOTSPOTS - - dropdown_hotspot = { - size = {270, widget_size[2]}, - offset = {widget_size[1] - 300, offset_y, 0} - }, - - -- TOOLTIP - - tooltip_text = { - font_type = "hell_shark", - font_size = 18, - horizontal_alignment = "left", - vertical_alignment = "top", - cursor_side = "right", - max_width = 600, - cursor_offset = {27, 27}, - cursor_offset_bottom = {27, 27}, - cursor_offset_top = {27, -27}, - line_colors = { - Colors.get_color_table_with_alpha("cheeseburger", 255), - Colors.get_color_table_with_alpha("white", 255) - } - }, - - -- DEBUG - - debug_middle_line = { - size = {widget_size[1], 2}, - offset = {0, (offset_y + widget_size[2]/2) - 1, 10}, - color = {200, 0, 255, 0} - }, - - offset = {0, offset_y, 0}, - size = {widget_size[1], widget_size[2]}, - color = {50, 255, 255, 255} - }, - scenegraph_id = scenegraph_id, - offset = {0, 0, 0} - } - - definition.content.popup_menu_widget = create_dropdown_menu_widget(definition, scenegraph_2nd_layer_id) - - return UIWidget.init(definition) -end - - --- ███╗ ██╗██╗ ██╗███╗ ███╗███████╗██████╗ ██╗ ██████╗ --- ████╗ ██║██║ ██║████╗ ████║██╔════╝██╔══██╗██║██╔════╝ --- ██╔██╗ ██║██║ ██║██╔████╔██║█████╗ ██████╔╝██║██║ --- ██║╚██╗██║██║ ██║██║╚██╔╝██║██╔══╝ ██╔══██╗██║██║ --- ██║ ╚████║╚██████╔╝██║ ╚═╝ ██║███████╗██║ ██║██║╚██████╗ --- ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝ - -local function create_numeric_menu_widget(dropdown_definition, scenegraph_2nd_layer_id) - - local offset_x = dropdown_definition.style.left_bracket.offset[1] - 3 - local offset_y = dropdown_definition.style.left_bracket.offset[2] + 80 - local size_x = 270 - local size_y = 100 - - local definition = { - element = { - passes = { - { - pass_type = "texture", - - style_id = "background", - texture_id = "rect_masked_texture" - }, - { - pass_type = "text", - - style_id = "range_text", - text_id = "range_text" - }, - { - pass_type = "text", - - style_id = "new_value_text", - text_id = "new_value_text" - }, - { - pass_type = "texture", - - style_id = "caret", - texture_id = "rect_masked_texture" - }, - { - pass_type = "texture", - - style_id = "slider_border", - texture_id = "rect_masked_texture" - }, - { - pass_type = "texture", - - style_id = "slider_background", - texture_id = "rect_masked_texture" - }, - { - pass_type = "texture", - - style_id = "slider_fill", - texture_id = "rect_masked_texture" - }, - { - pass_type = "texture", - - style_id = "slider_icon", - texture_id = "slider_icon_texture" - }, - { - pass_type = "hotspot", - - style_id = "slider_hotspot", - content_id = "slider_hotspot" - }, - { - pass_type = "held", - - style_id = "slider_hotspot", - content_check_hover = "slider_hotspot", - - -- fatshark solution copypasta - held_function = function (ui_scenegraph, ui_style, ui_content, input_service) - local cursor = UIInverseScaleVectorToResolution(input_service.get(input_service, "cursor")) - local scenegraph_id = ui_content.scenegraph_id - local world_position = UISceneGraph.get_world_position(ui_scenegraph, scenegraph_id) - local size_x_ = ui_style.size[1] - local cursor_x = cursor[1] - local pos_start = world_position[1] + ui_style.offset[1] - local old_value = ui_content.internal_value - local cursor_x_norm = cursor_x - pos_start - local value = math.clamp(cursor_x_norm/size_x_, 0, 1) - ui_content.internal_value = value - - if old_value ~= value then - ui_content.changed = true - end - end - } - } - }, - content = { - new_value_text = "", - range_text = "", - - rect_masked_texture = "rect_masked", - slider_icon_texture = "slider_skull_icon", - - caret_animation_timer = 0, - max_slider_size = 242, - slider_icon_offset = offset_x + 4, - - scenegraph_id = scenegraph_2nd_layer_id, - - slider_hotspot = {} - }, - style = { - background = { - size = {size_x, size_y}, - offset = {offset_x, offset_y - size_y, 20}, - color = {255, 20, 20, 20} - }, - range_text = { - offset = {offset_x + size_x / 2, offset_y - 30, 21}, - horizontal_alignment = "center", - font_size = 20, - font_type = "hell_shark_masked", - dynamic_font = true, - text_color = {255, 100, 100, 100} - }, - new_value_text = { - offset = { - dropdown_definition.style.current_value_text.offset[1], - dropdown_definition.style.current_value_text.offset[2], - 21 - }, - horizontal_alignment = "center", - font_size = 28, - font_type = "hell_shark_masked", - dynamic_font = true, - text_color = {255, 255, 255, 255} - }, - caret = { - size = {2, 25}, - offset = {offset_x, dropdown_definition.style.current_value_text.offset[2] + 10, 22}, - color = {255, 255, 255, 255} - }, - slider_border = { - offset = {offset_x + 10, offset_y - size_y + 10, 21}, - size = {250, 13}, - color = {255, 100, 100, 100} - }, - slider_background = { - offset = {offset_x + 12, offset_y - size_y + 12, 22}, - size = {246, 9}, - color = {255, 0, 0, 0} - }, - slider_fill = { - offset = {offset_x + 14, offset_y - size_y + 14, 23}, - size = {242, 5}, - color = {255, 255, 168, 0} - }, - slider_icon = { - offset = {offset_x + 4, offset_y - size_y + 7, 24}, - size = {20, 20}, - color = {255, 255, 255, 255}, - masked = true - }, - slider_hotspot = { - offset = {offset_x + 14, offset_y - size_y, 24}, - size = {242, 35} - } - }, - scenegraph_id = scenegraph_2nd_layer_id, - offset = {0, 0, 0} - } - - return UIWidget.init(definition) -end - -local function create_numeric_widget(widget_definition, scenegraph_id, scenegraph_2nd_layer_id) - - local widget_size = SETTINGS_LIST_REGULAR_WIDGET_SIZE - local offset_y = -widget_size[2] - - local show_widget_condition = create_show_widget_condition(widget_definition) - - local definition = { - element = { - passes = { - -- VISUALS - { - pass_type = "texture", - - style_id = "background", - texture_id = "rect_masked_texture", - - content_check_function = function (content) - return content.is_widget_collapsed - end - }, - { - pass_type = "texture", - - style_id = "highlight_texture", - texture_id = "highlight_texture", - content_check_function = function (content) - return content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - end - }, - { - pass_type = "text", - - style_id = "text", - text_id = "text" - }, - { - pass_type = "text", - - style_id = "left_bracket", - text_id = "left_bracket" - }, - { - pass_type = "text", - - style_id = "right_bracket", - text_id = "right_bracket" - }, - { - pass_type = "text", - - style_id = "current_value_text", - text_id = "current_value_text" - }, - -- HOTSPOTS - { - pass_type = "hotspot", - - content_id = "highlight_hotspot" - }, - { - pass_type = "hotspot", - - style_id = "dropdown_hotspot", - content_id = "dropdown_hotspot" - }, - -- PROCESSING - { - pass_type = "local_offset", - - offset_function = function (ui_scenegraph_, style, content, ui_renderer) - - local is_interactable = content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - - if is_interactable then - - if content.tooltip_text then - style.tooltip_text.cursor_offset = content.callback_fit_tooltip_to_the_screen(content, style.tooltip_text, ui_renderer) - end - - if content.dropdown_hotspot.on_release then - - content.callback_change_numeric_menu_visibility(content) - end - end - - if content.is_numeric_menu_opened then - - local old_value = content.current_value - - if content.callback_draw_numeric_menu(content) then - - local mod_name = content.mod_name - local setting_id = content.setting_id - local new_value = content.current_value - - content.callback_setting_changed(mod_name, setting_id, old_value, new_value) - end - end - - style.current_value_text.text_color = is_interactable and content.dropdown_hotspot.is_hover and Colors.get_color_table_with_alpha("white", 255) or Colors.get_color_table_with_alpha("cheeseburger", 255) - style.left_bracket.text_color = is_interactable and content.dropdown_hotspot.is_hover and {255, 45, 45, 45} or {255, 30, 30, 30} - style.right_bracket.text_color = is_interactable and content.dropdown_hotspot.is_hover and {255, 45, 45, 45} or {255, 30, 30, 30} - end - }, - -- TOOLTIP - { - pass_type = "tooltip_text", - - text_id = "tooltip_text", - style_id = "tooltip_text", - content_check_function = function (content) - return content.tooltip_text and content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - end - }, - -- DEBUG - { - pass_type = "rect", - - content_check_function = function () - return DEBUG_WIDGETS - end - }, - { - pass_type = "border", - - content_check_function = function (content_, style) - if DEBUG_WIDGETS then - style.thickness = 1 - end - - return DEBUG_WIDGETS - end - }, - { - pass_type = "rect", - - style_id = "debug_middle_line", - content_check_function = function () - return DEBUG_WIDGETS - end - } - } - }, - content = { - is_widget_visible = true, - is_widget_collapsed = widget_definition.is_collapsed, - - highlight_texture = "playerlist_hover", - rect_masked_texture = "rect_masked", - - highlight_hotspot = {}, - dropdown_hotspot = {}, - - text = widget_definition.title, - tooltip_text = widget_definition.tooltip, - unit_text = widget_definition.unit_text, - decimals_number = widget_definition.decimals_number, - range = widget_definition.range, - - left_bracket = "[", - right_bracket = "]", - - mod_name = widget_definition.mod_name, - setting_id = widget_definition.setting_id, - widget_type = widget_definition.type, - - current_value_text = "whatever", - default_value = widget_definition.default_value, - parent_widget_number = widget_definition.parent_index, - show_widget_condition = show_widget_condition - }, - style = { - - -- VISUALS - - background = { - size = {widget_size[1], widget_size[2] - 3}, - offset = {0, offset_y + 1, 0} - }, - - highlight_texture = { - size = {widget_size[1], widget_size[2] - 3}, - offset = {0, offset_y + 1, 2}, - masked = true - }, - - text = { - offset = {60 + widget_definition.depth * 40, offset_y + 5, 3}, - font_size = 28, - font_type = "hell_shark_masked", - dynamic_font = true, - text_color = Colors.get_color_table_with_alpha("white", 255) - }, - - left_bracket = { - offset = {widget_size[1] - 297, offset_y - 6, 1}, -- text positioning's living in its own world - horizontal_alignment = "center", - font_size = 39, - font_type = "hell_shark_masked", - dynamic_font = true, - text_color = {255, 30, 30, 30} - }, - - right_bracket = { - offset = {widget_size[1] - 33, offset_y - 6, 1}, - horizontal_alignment = "center", - font_size = 39, - font_type = "hell_shark_masked", - dynamic_font = true, - text_color = {255, 30, 30, 30} - }, - - current_value_text = { - offset = {widget_size[1] - 165, offset_y + 4, 3}, - horizontal_alignment = "center", - font_size = 28, - font_type = "hell_shark_masked", - dynamic_font = true, - text_color = Colors.get_color_table_with_alpha("cheeseburger", 255) - }, - - -- HOTSPOTS - - dropdown_hotspot = { - size = {270, widget_size[2]}, - offset = {widget_size[1] - 300, offset_y, 0} - }, - - -- TOOLTIP - - tooltip_text = { - font_type = "hell_shark", - font_size = 18, - horizontal_alignment = "left", - vertical_alignment = "top", - cursor_side = "right", - max_width = 600, - cursor_offset = {27, 27}, - cursor_offset_bottom = {27, 27}, - cursor_offset_top = {27, -27}, - line_colors = { - Colors.get_color_table_with_alpha("cheeseburger", 255), - Colors.get_color_table_with_alpha("white", 255) - } - }, - - -- DEBUG - - debug_middle_line = { - size = {widget_size[1], 2}, - offset = {0, (offset_y + widget_size[2]/2) - 1, 10}, - color = {200, 0, 255, 0} - }, - - offset = {0, offset_y, 0}, - size = {widget_size[1], widget_size[2]}, - color = {50, 255, 255, 255} - }, - scenegraph_id = scenegraph_id, - offset = {0, 0, 0} - } - - definition.content.popup_menu_widget = create_numeric_menu_widget(definition, scenegraph_2nd_layer_id) - - return UIWidget.init(definition) -end - - --- ██╗ ██╗███████╗██╗ ██╗██████╗ ██╗███╗ ██╗██████╗ --- ██║ ██╔╝██╔════╝╚██╗ ██╔╝██╔══██╗██║████╗ ██║██╔══██╗ --- █████╔╝ █████╗ ╚████╔╝ ██████╔╝██║██╔██╗ ██║██║ ██║ --- ██╔═██╗ ██╔══╝ ╚██╔╝ ██╔══██╗██║██║╚██╗██║██║ ██║ --- ██║ ██╗███████╗ ██║ ██████╔╝██║██║ ╚████║██████╔╝ --- ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝╚═╝ ╚═══╝╚═════╝ - - -local function create_keybind_widget(widget_definition, scenegraph_id) - - local widget_size = SETTINGS_LIST_REGULAR_WIDGET_SIZE - local offset_y = -widget_size[2] - - local show_widget_condition = create_show_widget_condition(widget_definition) - - local definition = { - element = { - passes = { - -- VISUALS - { - pass_type = "texture", - - style_id = "background", - texture_id = "background_texture", - - content_check_function = function (content) - return content.is_widget_collapsed - end - }, - { - pass_type = "texture", - - style_id = "highlight_texture", - texture_id = "highlight_texture", - content_check_function = function (content) - return content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - end - }, - { - pass_type = "text", - - style_id = "text", - text_id = "text" - }, - { - pass_type = "texture", - - style_id = "keybind_background", - texture_id = "rect_masked_texture" - }, - { - pass_type = "text", - - style_id = "keybind_text", - text_id = "keybind_text" - }, - -- HOTSPOTS - { - pass_type = "hotspot", - - content_id = "highlight_hotspot" - }, - { - pass_type = "hotspot", - - style_id = "keybind_text_hotspot", - content_id = "keybind_text_hotspot" - }, - -- PROCESSING - { - pass_type = "local_offset", - - offset_function = function (ui_scenegraph_, style, content, ui_renderer) - - local is_interactable = content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - - if is_interactable then - - if content.highlight_hotspot.is_hover and content.tooltip_text then - style.tooltip_text.cursor_offset = content.callback_fit_tooltip_to_the_screen(content, style.tooltip_text, ui_renderer) - end - - if content.highlight_hotspot.on_release and not content.keybind_text_hotspot.on_release then - content.callback_hide_sub_widgets(content) - end - - if content.highlight_hotspot.is_hover and content.tooltip_text then - style.tooltip_text.cursor_offset = content.callback_fit_tooltip_to_the_screen(content, style.tooltip_text, ui_renderer) - end - - if content.keybind_text_hotspot.on_release then - content.callback_change_setting_keybind_state(content) - return - end - end - - if content.is_setting_keybind then - if content.callback_setting_keybind(content) then - content.callback_setting_changed(content.mod_name, content.setting_id, nil, content.keys) - return - end - end - - style.keybind_text.text_color = is_interactable and content.keybind_text_hotspot.is_hover and Colors.get_color_table_with_alpha("white", 255) or content.is_setting_keybind and Colors.get_color_table_with_alpha("white", 100) or Colors.get_color_table_with_alpha("cheeseburger", 255) - style.keybind_background.color = is_interactable and content.keybind_text_hotspot.is_hover and {255, 45, 45, 45} or {255, 30, 30, 30} - end - }, - -- TOOLTIP - { - pass_type = "tooltip_text", - - text_id = "tooltip_text", - style_id = "tooltip_text", - content_check_function = function (content) - return content.tooltip_text and content.highlight_hotspot.is_hover and content.callback_is_cursor_inside_settings_list() - end - }, - -- DEBUG - { - pass_type = "rect", - - content_check_function = function () - return DEBUG_WIDGETS - end - }, - { - pass_type = "border", - - content_check_function = function (content_, style) - if DEBUG_WIDGETS then - style.thickness = 1 - end - - return DEBUG_WIDGETS - end - }, - { - pass_type = "rect", - - style_id = "debug_middle_line", - content_check_function = function () - return DEBUG_WIDGETS - end - } - } - }, - content = { - is_widget_visible = true, - is_widget_collapsed = widget_definition.is_collapsed, - - highlight_texture = "playerlist_hover", -- texture name - background_texture = "common_widgets_background_lit", - rect_masked_texture = "rect_masked", - - highlight_hotspot = {}, - keybind_text_hotspot = {}, - - text = widget_definition.title, - tooltip_text = widget_definition.tooltip, - - mod_name = widget_definition.mod_name, - setting_id = widget_definition.setting_id, - widget_type = widget_definition.type, - - keybind_global = widget_definition.keybind_global, - keybind_trigger = widget_definition.keybind_trigger, - keybind_type = widget_definition.keybind_type, - function_name = widget_definition.function_name, - view_name = widget_definition.view_name, - transition_data = widget_definition.transition_data, - - keybind_text = widget_definition.keybind_text, - default_value = widget_definition.default_value, - parent_widget_number = widget_definition.parent_index, - show_widget_condition = show_widget_condition - }, - style = { - - -- VISUALS - - background = { - size = {widget_size[1], widget_size[2] - 3}, - offset = {0, offset_y + 1, 0} - }, - - highlight_texture = { - size = {widget_size[1], widget_size[2] - 3}, - offset = {0, offset_y + 1, 1}, - masked = true - }, - - text = { - offset = {60 + widget_definition.depth * 40, offset_y + 5, 2}, - font_size = 28, - font_type = "hell_shark_masked", - dynamic_font = true, - text_color = Colors.get_color_table_with_alpha("white", 255) - }, - - keybind_background = { - size = {270, 34}, - offset = {widget_size[1] - 300, offset_y + 8, 0}, - color = {255, 30, 30, 30} - }, - - keybind_text = { - offset = {widget_size[1] - 165, offset_y + 6, 3}, - horizontal_alignment = "center", - font_size = 24, - font_type = "hell_shark_masked", - dynamic_font = true, - text_color = Colors.get_color_table_with_alpha("cheeseburger", 255) - }, - - -- HOTSPOTS - - keybind_text_hotspot = { - size = {270, widget_size[2]}, - offset = {widget_size[1] - 300, offset_y, 0} - }, - - -- TOOLTIP - - tooltip_text = { - font_type = "hell_shark", - font_size = 18, - horizontal_alignment = "left", - vertical_alignment = "top", - cursor_side = "right", - max_width = 600, - cursor_offset = {27, 27}, - cursor_offset_bottom = {27, 27}, - cursor_offset_top = {27, -27}, - line_colors = { - Colors.get_color_table_with_alpha("cheeseburger", 255), - Colors.get_color_table_with_alpha("white", 255) - } - }, - - -- DEBUG - - debug_middle_line = { - size = {widget_size[1], 2}, - offset = {0, (offset_y + widget_size[2]/2) - 1, 10}, - color = {200, 0, 255, 0} - }, - - offset = {0, offset_y, 0}, - size = {widget_size[1], widget_size[2]}, - color = {50, 255, 255, 255} - }, - scenegraph_id = scenegraph_id, - offset = {0, 0, 0} - } - - return UIWidget.init(definition) -end - - - - - - - - - - - - --- ██████╗██╗ █████╗ ███████╗███████╗ ---██╔════╝██║ ██╔══██╗██╔════╝██╔════╝ ---██║ ██║ ███████║███████╗███████╗ ---██║ ██║ ██╔══██║╚════██║╚════██║ ---╚██████╗███████╗██║ ██║███████║███████║ --- ╚═════╝╚══════╝╚═╝ ╚═╝╚══════╝╚══════╝ - -local _DEFAULT_SCROLL_STEP = 40 -local _SCROLL_STEP - - --- copypasted 'math.point_is_inside_2d_box' from VT2 source code, since VT1 and VT2 have different implementations -local function is_point_inside_2d_box(pos, lower_left_corner, size) - if lower_left_corner[1] < pos[1] and pos[1] < lower_left_corner[1] + size[1] and lower_left_corner[2] < pos[2] and pos[2] < lower_left_corner[2] + size[2] then - return true - else - return false - end -end - --- #################################################################################################################### --- ##### INITIALIZATION ############################################################################################### --- #################################################################################################################### - - -VMFOptionsView = class(VMFOptionsView) -VMFOptionsView.init = function (self, ingame_ui_context) - - self.current_setting_list_offset_y = 0 - - self.is_setting_changes_applied_immidiately = true - - self.definitions = {} - self.definitions.scenegraph = scenegraph_definition - self.definitions.scenegraph_2nd_layer = {} - self.definitions.menu_widgets = menu_widgets_definition - self.definitions.settings_list_widgets = vmf.options_widgets_data - - -- get necessary things for the rendering - self.ui_renderer = ingame_ui_context.ui_renderer - self.render_settings = {snap_pixel_positions = true} - self.ingame_ui = ingame_ui_context.ingame_ui - - -- create the input service - local input_manager = ingame_ui_context.input_manager - input_manager:create_input_service("vmf_options_menu", "IngameMenuKeymaps", "IngameMenuFilters") - input_manager:map_device_to_service("vmf_options_menu", "keyboard") - input_manager:map_device_to_service("vmf_options_menu", "mouse") - input_manager:map_device_to_service("vmf_options_menu", "gamepad") - - input_manager:create_input_service("changing_setting", "IngameMenuKeymaps") - input_manager:map_device_to_service("changing_setting", "keyboard") - input_manager:map_device_to_service("changing_setting", "mouse") - input_manager:map_device_to_service("changing_setting", "gamepad") - self.input_manager = input_manager - - -- wwise_world is used for making sounds (for opening menu, closing menu, etc.) - local world - if VT1 then - world = ingame_ui_context.world_manager:world("music_world") - else - world = ingame_ui_context.world_manager:world("level_world") - end - self.wwise_world = Managers.world:wwise_world(world) - - self:create_ui_elements() -end - - --- #################################################################################################################### --- ##### INITIALIZATION: UI ELEMENTS ################################################################################## --- #################################################################################################################### - - -VMFOptionsView.create_ui_elements = function (self) - - self.menu_widgets = {} - - for name, definition in pairs(self.definitions.menu_widgets) do - self.menu_widgets[name] = UIWidget.init(definition) - end - - self.settings_list_widgets = self:initialize_settings_list_widgets() - - self.ui_scenegraph = UISceneGraph.init_scenegraph(self.definitions.scenegraph) - self.ui_scenegraph_2nd_layer = UISceneGraph.init_scenegraph(self.definitions.scenegraph_2nd_layer) - - self.setting_list_mask_size_y = self.ui_scenegraph.sg_settings_list_mask.size[2] - - if self.is_scrolling_enabled then - self:calculate_scrollbar_size() - end -end - - -VMFOptionsView.initialize_settings_list_widgets = function (self) - - local scenegraph_id = "sg_settings_list" - local scenegraph_id_start = "sg_settings_list_start" - local scenegraph_id_start_2nd_layer = "sg_settings_list_start_2nd_layer" - local list_size_y = 0 - - local all_widgets = {} - local mod_widgets - - for _, mod_settings_list_definitions in ipairs(self.definitions.settings_list_widgets) do - - mod_widgets = {} - - for _, definition in ipairs(mod_settings_list_definitions) do - - local widget = nil - local widget_type = definition.type - - if widget_type == "checkbox" then - widget = self:initialize_checkbox_widget(definition, scenegraph_id_start) - elseif widget_type == "dropdown" then - widget = self:initialize_dropdown_widget(definition, scenegraph_id_start, scenegraph_id_start_2nd_layer) - elseif widget_type == "numeric" then - widget = self:initialize_numeric_widget(definition, scenegraph_id_start, scenegraph_id_start_2nd_layer) - elseif widget_type == "keybind" then - widget = self:initialize_keybind_widget(definition, scenegraph_id_start) - elseif widget_type == "header" then - widget = self:initialize_header_widget(definition, scenegraph_id_start) - elseif widget_type == "group" then - widget = self:initialize_group_widget(definition, scenegraph_id_start) - end - - if widget then - list_size_y = list_size_y + widget.style.size[2] - - table.insert(mod_widgets, widget) - end - end - - table.insert(all_widgets, mod_widgets) - end - - local mask_size = self.definitions.scenegraph.sg_settings_list_mask.size - local mask_size_x = mask_size[1] - local mask_size_y = mask_size[2] - - self.definitions.scenegraph[scenegraph_id] = { - size = {mask_size_x, list_size_y}, - position = {0, 0, 0}, - offset = {0, 0, 0}, - - vertical_alignment = "top", - horizontal_alignment = "center", - - parent = "sg_settings_list_mask" - } - - self.definitions.scenegraph[scenegraph_id_start] = { - size = {1, 1}, - position = {3, 0, 10}, - - vertical_alignment = "top", - horizontal_alignment = "left", - - parent = scenegraph_id - } - - self.definitions.scenegraph_2nd_layer[scenegraph_id_start_2nd_layer] = { - size = {0, 0}, - position = {0, 0, 510}, - - vertical_alignment = "bottom", - horizontal_alignment = "left" - } - - local is_scrolling_enabled = false - local max_offset_y = 0 - - if mask_size_y < list_size_y then - is_scrolling_enabled = true - max_offset_y = list_size_y - mask_size_y - end - - self.menu_widgets["scrollbar"].content.visible = is_scrolling_enabled - self.menu_widgets["mousewheel_scroll_area"].content.visible = is_scrolling_enabled - - self.max_setting_list_offset_y = max_offset_y - self.settings_list_size_y = list_size_y - self.original_settings_list_size_y = list_size_y - self.settings_list_scenegraph_id = scenegraph_id - self.settings_list_scenegraph_id_start = scenegraph_id_start - self.is_scrolling_enabled = is_scrolling_enabled - - return all_widgets -end - - -VMFOptionsView.initialize_header_widget = function (self, definition, scenegraph_id) - - local widget = create_header_widget(definition, scenegraph_id) - local content = widget.content - content.is_checkbox_checked = definition.is_togglable - content.is_checkbox_visible = definition.is_togglable - - content.callback_favorite = callback(self, "callback_favorite") - content.callback_move_favorite = callback(self, "callback_move_favorite") - content.callback_mod_state_changed = callback(self, "callback_mod_state_changed") - content.callback_hide_sub_widgets = callback(self, "callback_hide_sub_widgets") - content.callback_fit_tooltip_to_the_screen = callback(self, "callback_fit_tooltip_to_the_screen") - content.callback_is_cursor_inside_settings_list = callback(self, "callback_is_cursor_inside_settings_list") - - return widget -end - - -VMFOptionsView.initialize_checkbox_widget = function (self, definition, scenegraph_id) - - local widget = create_checkbox_widget(definition, scenegraph_id) - local content = widget.content - - content.callback_setting_changed = callback(self, "callback_setting_changed") - content.callback_hide_sub_widgets = callback(self, "callback_hide_sub_widgets") - content.callback_fit_tooltip_to_the_screen = callback(self, "callback_fit_tooltip_to_the_screen") - content.callback_is_cursor_inside_settings_list = callback(self, "callback_is_cursor_inside_settings_list") - - return widget -end - -VMFOptionsView.initialize_group_widget = function (self, definition, scenegraph_id) - - local widget = create_group_widget(definition, scenegraph_id) - local content = widget.content - - --content.callback_setting_changed = callback(self, "callback_setting_changed") - content.callback_hide_sub_widgets = callback(self, "callback_hide_sub_widgets") - content.callback_fit_tooltip_to_the_screen = callback(self, "callback_fit_tooltip_to_the_screen") - content.callback_is_cursor_inside_settings_list = callback(self, "callback_is_cursor_inside_settings_list") - - return widget -end - - -VMFOptionsView.initialize_dropdown_widget = function (self, definition, scenegraph_id, scenegraph_2nd_layer_id) - - local widget = create_dropdown_widget(definition, scenegraph_id, scenegraph_2nd_layer_id) - local content = widget.content - - content.callback_setting_changed = callback(self, "callback_setting_changed") - content.callback_hide_sub_widgets = callback(self, "callback_hide_sub_widgets") - content.callback_fit_tooltip_to_the_screen = callback(self, "callback_fit_tooltip_to_the_screen") - content.callback_is_cursor_inside_settings_list = callback(self, "callback_is_cursor_inside_settings_list") - content.callback_change_dropdown_menu_visibility = callback(self, "callback_change_dropdown_menu_visibility") - content.callback_draw_dropdown_menu = callback(self, "callback_draw_dropdown_menu") - - return widget -end - - -VMFOptionsView.initialize_numeric_widget = function (self, definition, scenegraph_id, scenegraph_2nd_layer_id) - - local widget = create_numeric_widget(definition, scenegraph_id, scenegraph_2nd_layer_id) - local content = widget.content - - content.callback_setting_changed = callback(self, "callback_setting_changed") - content.callback_fit_tooltip_to_the_screen = callback(self, "callback_fit_tooltip_to_the_screen") - content.callback_is_cursor_inside_settings_list = callback(self, "callback_is_cursor_inside_settings_list") - content.callback_change_numeric_menu_visibility = callback(self, "callback_change_numeric_menu_visibility") - content.callback_draw_numeric_menu = callback(self, "callback_draw_numeric_menu") - - return widget -end - - -VMFOptionsView.initialize_keybind_widget = function (self, definition, scenegraph_id) - - local widget = create_keybind_widget(definition, scenegraph_id) - local content = widget.content - - content.callback_setting_changed = callback(self, "callback_setting_changed") - content.callback_hide_sub_widgets = callback(self, "callback_hide_sub_widgets") - content.callback_fit_tooltip_to_the_screen = callback(self, "callback_fit_tooltip_to_the_screen") - content.callback_change_setting_keybind_state = callback(self, "callback_change_setting_keybind_state") - content.callback_setting_keybind = callback(self, "callback_setting_keybind") - content.callback_is_cursor_inside_settings_list = callback(self, "callback_is_cursor_inside_settings_list") - - return widget -end - - --- #################################################################################################################### --- ##### CALLBACKS #################################################################################################### --- #################################################################################################################### - - -VMFOptionsView.callback_setting_changed = function (self, mod_name, setting_id, old_value, new_value) - - if self.is_setting_changes_applied_immidiately and old_value ~= new_value then - get_mod(mod_name):set(setting_id, new_value, true) - end - - WwiseWorld.trigger_event(self.wwise_world, "Play_hud_select") - - self:update_settings_list_widgets_visibility(mod_name) - self:readjust_visible_settings_list_widgets_position() -end - - -VMFOptionsView.callback_mod_state_changed = function (self, mod_name, is_mod_enabled) - - vmf.mod_state_changed(mod_name, is_mod_enabled) - - WwiseWorld.trigger_event(self.wwise_world, "Play_hud_select") - - self:update_settings_list_widgets_visibility(mod_name) - self:readjust_visible_settings_list_widgets_position() -end - - -VMFOptionsView.callback_is_cursor_inside_settings_list = function (self) - - local input_service = self:input_service() - - local cursor = input_service:get("cursor") - local mask_pos = Vector3.deprecated_copy(UISceneGraph.get_world_position(self.ui_scenegraph, "sg_settings_list_mask")) - local mask_size = UISceneGraph.get_size(self.ui_scenegraph, "sg_settings_list_mask") - - local cursor_position = UIInverseScaleVectorToResolution(cursor) - - local is_hover = math.point_is_inside_2d_box(cursor_position, mask_pos, mask_size) - if is_hover then - return true - end -end - --- [VT1] [VT2] -local UIResolutionScale = UIResolutionScale or UIResolutionScale_pow2 --- @TODO: replace with `VT1` later -local USE_LEGACY_FONT_NAMES = VT1 or (tonumber(script_data.settings.content_revision) < 1296) -VMFOptionsView.callback_fit_tooltip_to_the_screen = function (self, widget_content, widget_style, ui_renderer) - - local cursor_offset_bottom = widget_style.cursor_offset_bottom - - if ui_renderer.input_service then - - local cursor_position = UIInverseScaleVectorToResolution(ui_renderer.input_service.get(ui_renderer.input_service, "cursor")) - if cursor_position then - - local text = widget_content.tooltip_text - local max_width = widget_style.max_width - - local font, font_size = UIFontByResolution(widget_style) - local font_name = USE_LEGACY_FONT_NAMES and font[3] or widget_style.font_type - local font_material = font[1] - - local _, font_min, font_max = UIGetFontHeight(ui_renderer.gui, font_name, font_size) - - local texts = UIRenderer.word_wrap(ui_renderer, text, font_material, font_size, max_width) - local num_texts = #texts - local full_font_height = (font_max + math.abs(font_min)) * RESOLUTION_LOOKUP.inv_scale - - local tooltip_height = full_font_height * num_texts - - if((cursor_offset_bottom[2] / UIResolutionScale() + tooltip_height) > cursor_position[2]) then - - local cursor_offset_top = {} - cursor_offset_top[1] = widget_style.cursor_offset_top[1] - cursor_offset_top[2] = widget_style.cursor_offset_top[2] - (tooltip_height * UIResolutionScale()) - - return cursor_offset_top - else - return cursor_offset_bottom - end - end - end - - return cursor_offset_bottom -end - - -VMFOptionsView.callback_favorite = function (self, widget_content) - - local mod_name = widget_content.mod_name - local is_favorited = not widget_content.is_favorited - - local favorite_mods_list = vmf:get("options_menu_favorite_mods") - - if is_favorited then - table.insert(favorite_mods_list, mod_name) - else - for i, current_mod_name in ipairs(favorite_mods_list) do - if current_mod_name == mod_name then - table.remove(favorite_mods_list, i) - break - end - end - end - - vmf:set("options_menu_favorite_mods", favorite_mods_list) - - widget_content.is_favorited = is_favorited - - WwiseWorld.trigger_event(self.wwise_world, "Play_hud_select") - - self:sort_settings_list_widgets() - self:readjust_visible_settings_list_widgets_position() -end - - -VMFOptionsView.callback_move_favorite = function (self, widget_content, is_moved_up) - - local mod_name = widget_content.mod_name - - local new_index - - local favorite_mods_list = vmf:get("options_menu_favorite_mods") - - for current_index, current_mod_name in ipairs(favorite_mods_list) do - if current_mod_name == mod_name then - - new_index = is_moved_up and (current_index - 1) or (current_index + 1) - new_index = math.clamp(new_index, 1, #favorite_mods_list) - - if current_index ~= new_index then - table.insert(favorite_mods_list, new_index, table.remove(favorite_mods_list, current_index)) - - vmf:set("options_menu_favorite_mods", favorite_mods_list) - - WwiseWorld.trigger_event(self.wwise_world, "Play_hud_select") - - self:sort_settings_list_widgets() - self:readjust_visible_settings_list_widgets_position() - - return - end - end - end -end - - -VMFOptionsView.callback_hide_sub_widgets = function (self, widget_content) - - local mod_name = widget_content.mod_name - local setting_id = widget_content.setting_id - local is_widget_collapsed = widget_content.is_widget_collapsed - - local widget_number = not setting_id and 1 -- if (setting_id == nil) -> it's header -> #1 - - local are_there_visible_sub_widgets = false - - if not is_widget_collapsed then - - for _, mod_widgets in ipairs(self.settings_list_widgets) do - - if mod_widgets[1].content.mod_name == mod_name then - - for i, widget in ipairs(mod_widgets) do - - if widget_number then - if widget.content.parent_widget_number == widget_number then - are_there_visible_sub_widgets = are_there_visible_sub_widgets or widget.content.is_widget_visible - end - else - if widget.content.setting_id == setting_id then - widget_number = i - end - end - end - end - end - end - - local is_widget_collapsed_new = not is_widget_collapsed and are_there_visible_sub_widgets - - if is_widget_collapsed_new and not is_widget_collapsed then - WwiseWorld.trigger_event(self.wwise_world, "Play_hud_map_close") - elseif not is_widget_collapsed_new and is_widget_collapsed then - WwiseWorld.trigger_event(self.wwise_world, "Play_hud_map_open") - end - - widget_content.is_widget_collapsed = is_widget_collapsed_new - - - if setting_id then - - local all_collapsed_widgets = vmf:get("options_menu_collapsed_widgets") - - local mod_collapsed_widgets = all_collapsed_widgets[mod_name] - - if widget_content.is_widget_collapsed then - - mod_collapsed_widgets = mod_collapsed_widgets or {} - mod_collapsed_widgets[setting_id] = true - - all_collapsed_widgets[mod_name] = mod_collapsed_widgets - else - if mod_collapsed_widgets then - mod_collapsed_widgets[setting_id] = nil - - local is_collapsed_widgets_list_empty = true - - 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 - 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 - self:update_settings_list_widgets_visibility(mod_name) - self:readjust_visible_settings_list_widgets_position() -end - - -VMFOptionsView.callback_change_setting_keybind_state = function (self, widget_content) - - if not widget_content.is_setting_keybind then - self.input_manager:device_unblock_all_services("keyboard", 1) - self.input_manager:device_unblock_all_services("mouse", 1) - self.input_manager:device_unblock_all_services("gamepad", 1) - - self.input_manager:block_device_except_service("changing_setting", "keyboard", 1, "keybind") - self.input_manager:block_device_except_service("changing_setting", "mouse", 1, "keybind") - self.input_manager:block_device_except_service("changing_setting", "gamepad", 1, "keybind") - - WwiseWorld.trigger_event(self.wwise_world, "Play_hud_select") - - widget_content.is_setting_keybind = true - else - - self.input_manager:device_unblock_all_services("keyboard", 1) - self.input_manager:device_unblock_all_services("mouse", 1) - self.input_manager:device_unblock_all_services("gamepad", 1) - - self.input_manager:block_device_except_service("vmf_options_menu", "keyboard", 1) - self.input_manager:block_device_except_service("vmf_options_menu", "mouse", 1) - self.input_manager:block_device_except_service("vmf_options_menu", "gamepad", 1) - - widget_content.is_setting_keybind = false - end -end - -local function set_new_keybind(keybind_widget_content) - vmf.add_mod_keybind( - get_mod(keybind_widget_content.mod_name), - keybind_widget_content.setting_id, - { - global = keybind_widget_content.keybind_global, - trigger = keybind_widget_content.keybind_trigger, - type = keybind_widget_content.keybind_type, - keys = keybind_widget_content.keys, - function_name = keybind_widget_content.function_name, - view_name = keybind_widget_content.view_name, - transition_data = keybind_widget_content.transition_data - } - ) -end - -VMFOptionsView.callback_setting_keybind = function (self, widget_content) - if not widget_content.first_pressed_button_id then - if Keyboard.any_pressed() then - widget_content.first_pressed_button_id = vmf.get_key_id("KEYBOARD", Keyboard.any_pressed()) - widget_content.first_pressed_button_index = Keyboard.any_pressed() - widget_content.first_pressed_button_type = "keyboard" - elseif Mouse.any_pressed() then - widget_content.first_pressed_button_id = vmf.get_key_id("MOUSE", Mouse.any_pressed()) - widget_content.first_pressed_button_index = Mouse.any_pressed() - widget_content.first_pressed_button_type = "mouse" - end - end - - local pressed_buttons = {} - if widget_content.first_pressed_button_id then - table.insert(pressed_buttons, widget_content.first_pressed_button_id) - else - table.insert(pressed_buttons, "no_button") - end - if Keyboard.button(Keyboard.button_index("left ctrl")) + Keyboard.button(Keyboard.button_index("right ctrl")) > 0 then - table.insert(pressed_buttons, "ctrl") - end - if Keyboard.button(Keyboard.button_index("left alt")) + Keyboard.button(Keyboard.button_index("right alt")) > 0 then - table.insert(pressed_buttons, "alt") - end - if Keyboard.button(Keyboard.button_index("left shift")) + Keyboard.button(Keyboard.button_index("right shift")) > 0 then - table.insert(pressed_buttons, "shift") - end - - local preview_string = vmf.build_keybind_string(pressed_buttons) - - widget_content.keybind_text = preview_string ~= "" and preview_string or "_" - widget_content.keys = pressed_buttons - - if widget_content.first_pressed_button_id then - if widget_content.first_pressed_button_type == "keyboard" and Keyboard.released(widget_content.first_pressed_button_index) or - widget_content.first_pressed_button_type == "mouse" and Mouse.released(widget_content.first_pressed_button_index) - then - widget_content.first_pressed_button_id = nil - widget_content.first_pressed_button_index = nil - widget_content.first_pressed_button_type = nil - - -- Fix accidental LMB binding for Mod Options view toggling. - if widget_content.setting_id == "open_vmf_options" and #widget_content.keys == 1 and widget_content.keys[1] == "mouse left" then - widget_content.keys = {} - end - - set_new_keybind(widget_content) - - self:callback_change_setting_keybind_state(widget_content) - return true - end - elseif Keyboard.released(Keyboard.button_index("esc")) then - widget_content.keybind_text = "" - widget_content.keys = {} - - set_new_keybind(widget_content) - - self:callback_change_setting_keybind_state(widget_content) - return true - end -end - - -VMFOptionsView.callback_change_dropdown_menu_visibility = function (self, widget_content) - - if not widget_content.is_dropdown_menu_opened then - self.input_manager:device_unblock_all_services("keyboard", 1) - self.input_manager:device_unblock_all_services("mouse", 1) - self.input_manager:device_unblock_all_services("gamepad", 1) - - self.input_manager:block_device_except_service("changing_setting", "keyboard", 1, "keybind") - self.input_manager:block_device_except_service("changing_setting", "mouse", 1, "keybind") - self.input_manager:block_device_except_service("changing_setting", "gamepad", 1, "keybind") - - WwiseWorld.trigger_event(self.wwise_world, "Play_hud_select") - - widget_content.is_dropdown_menu_opened = true - - -- if not check for this, dropdown menu will close right after opening - widget_content.wrong_mouse_on_release = true - else - - self.input_manager:device_unblock_all_services("keyboard", 1) - self.input_manager:device_unblock_all_services("mouse", 1) - self.input_manager:device_unblock_all_services("gamepad", 1) - - self.input_manager:block_device_except_service("vmf_options_menu", "keyboard", 1) - self.input_manager:block_device_except_service("vmf_options_menu", "mouse", 1) - self.input_manager:block_device_except_service("vmf_options_menu", "gamepad", 1) - - widget_content.is_dropdown_menu_opened = false - end -end - - -VMFOptionsView.callback_draw_dropdown_menu = function (self, widget_content) - local ui_renderer = self.ui_renderer - local scenegraph = self.ui_scenegraph_2nd_layer - local parent_scenegraph_id = self.settings_list_scenegraph_id_start - local input_manager = self.input_manager - local input_service = input_manager:get_service("changing_setting") - - UIRenderer.begin_pass(ui_renderer, scenegraph, input_service, self.dt, parent_scenegraph_id, self.render_settings) - - UIRenderer.draw_widget(ui_renderer, widget_content.popup_menu_widget) - - UIRenderer.end_pass(ui_renderer) - - ui_renderer.input_service = input_manager:get_service("vmf_options_menu") - - - for _, hotspot_content in pairs(widget_content.popup_menu_widget.content) do - if type(hotspot_content) == "table" and hotspot_content.on_release then - self:callback_change_dropdown_menu_visibility(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 - end - - --if Left Mouse Button or Esc pressed - if Mouse.released(0) and not widget_content.wrong_mouse_on_release or Keyboard.released(27) then - self:callback_change_dropdown_menu_visibility(widget_content) - end - - widget_content.wrong_mouse_on_release = nil -end - - -VMFOptionsView.callback_change_numeric_menu_visibility = function (self, widget_content) - - if not widget_content.is_numeric_menu_opened then - self.input_manager:device_unblock_all_services("keyboard", 1) - self.input_manager:device_unblock_all_services("mouse", 1) - self.input_manager:device_unblock_all_services("gamepad", 1) - - self.input_manager:block_device_except_service("changing_setting", "keyboard", 1, "keybind") - self.input_manager:block_device_except_service("changing_setting", "mouse", 1, "keybind") - self.input_manager:block_device_except_service("changing_setting", "gamepad", 1, "keybind") - - WwiseWorld.trigger_event(self.wwise_world, "Play_hud_select") - - widget_content.is_numeric_menu_opened = true - - -- current value text - - widget_content.popup_menu_widget.content.new_value_text = widget_content.current_value_text - - -- new value - - widget_content.popup_menu_widget.content.new_value = widget_content.current_value .. "" - - -- decimals number - - local decimals_number = widget_content.decimals_number and widget_content.decimals_number or 0 - widget_content.popup_menu_widget.content.decimals_number = decimals_number - - -- range text @TODO: maybe improve it - - local min_text = widget_content.range[1] .. "" - local max_text = widget_content.range[2] .. "" - - local min_text_has_dot = string.find(min_text, "%.") - local max_text_has_dot = string.find(max_text, "%.") - - if decimals_number > 0 then - if not min_text_has_dot then - min_text = min_text .. "." - - for _ = 1, decimals_number do - min_text = min_text .. "0" - end - end - if not max_text_has_dot then - max_text = max_text .. "." - - for _ = 1, decimals_number do - max_text = max_text .. "0" - end - end - end - - widget_content.popup_menu_widget.content.range_text = string.format("[min: %s] [max: %s]", min_text, max_text) - - -- if not check for this, numeric menu will close right after opening - widget_content.wrong_mouse_on_release = true - else - - self.input_manager:device_unblock_all_services("keyboard", 1) - self.input_manager:device_unblock_all_services("mouse", 1) - self.input_manager:device_unblock_all_services("gamepad", 1) - - self.input_manager:block_device_except_service("vmf_options_menu", "keyboard", 1) - self.input_manager:block_device_except_service("vmf_options_menu", "mouse", 1) - self.input_manager:block_device_except_service("vmf_options_menu", "gamepad", 1) - - widget_content.is_numeric_menu_opened = false - end -end - - -VMFOptionsView.callback_draw_numeric_menu = function (self, widget_content) - - local numeric_menu_content = widget_content.popup_menu_widget.content - local numeric_menu_text_style = widget_content.popup_menu_widget.style.new_value_text - local numeric_menu_caret_style = widget_content.popup_menu_widget.style.caret - - -- calculate caret offset --------------------------- - - local font, font_size = UIFontByResolution(numeric_menu_text_style, nil) - local font_name = font[3] - local font_material = font[1] - - local new_value_text = numeric_menu_content.new_value_text - local new_value_text_just_numbers = numeric_menu_content.new_value - - local new_value_text_width = UIRenderer.text_size(self.ui_renderer, new_value_text, font_material, font_size, font_name) - local new_value_text_offset = numeric_menu_text_style.offset[1] - new_value_text_width / 2 - - local caret_offset = UIRenderer.text_size(self.ui_renderer, new_value_text_just_numbers, font_material, font_size, font_name) - - numeric_menu_caret_style.offset[1] = new_value_text_offset + caret_offset - 3 - - -- blink caret --------------------------------------- - - numeric_menu_content.caret_animation_timer = numeric_menu_content.caret_animation_timer + self.dt - - numeric_menu_caret_style.color[1] = math.sirp(0, 0.7, numeric_menu_content.caret_animation_timer * 1.5) * 255 - - - -- PROCESS KEYSTROKES ################################ - - local new_value = numeric_menu_content.new_value - - local can_add_more_characters = string.len(new_value) < 16 - - local keystrokes = Keyboard.keystrokes() - - for _, stroke in ipairs(keystrokes) do - if type(stroke) == "string" then - - if can_add_more_characters then - - if tonumber(stroke) then -- number - - local dot_position = string.find(new_value, "%.") - - if not dot_position or (dot_position + numeric_menu_content.decimals_number) > string.len(new_value) then - - new_value = new_value .. stroke - end - - elseif stroke == "-" then -- minus - - if string.find(new_value, "%-") then - new_value = string.gsub(new_value, "%-", "") - else - new_value = "-" .. new_value - end - - elseif stroke == "." then -- dot - - if numeric_menu_content.decimals_number > 0 and not string.find(new_value, "%.") then - new_value = new_value .. "." - end - end - end - - elseif stroke == Keyboard.BACKSPACE then -- backspace - - if string.len(new_value) > 0 then - new_value = string.sub(new_value, 1, -2) - end - end - end - - local new_value_number = tonumber(new_value) - - if new_value_number and new_value_number >= widget_content.range[1] and new_value_number <= widget_content.range[2] then - numeric_menu_text_style.text_color = {255, 255, 255, 255} - - -- clamp entered value according to defined range (if dot isn't the last character in the string) - local dot_position = string.find(new_value, "%.") - local string_length = string.len(new_value) - - if not (dot_position and dot_position == string_length) then - - new_value_number = math.clamp(new_value_number, widget_content.range[1], widget_content.range[2]) -- @TODO: remove? - - -- Lua, pls! Sometimes "tostring" returns something like "1337.5999999999999" instead of "1337.6", - -- so I have to convert number to string this way - -- UPD(bi) 01.01.19 I commented out following string. Rething this whole block later. Seems like it can be just removed. - -- new_value = new_value_number .. "" - end - else - -- if entered string is not convertable, change its color to red - numeric_menu_text_style.text_color = {255, 255, 70, 70} - end - - - -- SLIDER ############################################ - - if numeric_menu_content.changed then - - local full_range = widget_content.range[2] - widget_content.range[1] - - new_value_number = full_range * numeric_menu_content.internal_value + widget_content.range[1] - new_value_number = math.round_with_precision(new_value_number, widget_content.decimals_number) - - new_value = new_value_number .. "" - - numeric_menu_content.changed = false - end - - if new_value_number then - - local clamped_new_value_number = math.clamp(new_value_number, widget_content.range[1], widget_content.range[2]) - - local full_range = widget_content.range[2] - widget_content.range[1] - local slider_fill_percent = (clamped_new_value_number - widget_content.range[1]) / full_range - - local new_slider_fill_size = numeric_menu_content.max_slider_size * slider_fill_percent - - widget_content.popup_menu_widget.style.slider_fill.size[1] = new_slider_fill_size - widget_content.popup_menu_widget.style.slider_icon.offset[1] = numeric_menu_content.slider_icon_offset + new_slider_fill_size - end - - -- ASSIGNING VALUES ################################## - - numeric_menu_content.new_value = new_value - numeric_menu_content.new_value_text = new_value - - if widget_content.unit_text then - numeric_menu_content.new_value_text = numeric_menu_content.new_value_text .. widget_content.unit_text - end - - -- DRAWING WIDGET #################################### - local ui_renderer = self.ui_renderer - local scenegraph = self.ui_scenegraph_2nd_layer - local parent_scenegraph_id = self.settings_list_scenegraph_id_start - local input_manager = self.input_manager - local input_service = input_manager:get_service("changing_setting") - - - UIRenderer.begin_pass(ui_renderer, scenegraph, input_service, self.dt, parent_scenegraph_id, self.render_settings) - - UIRenderer.draw_widget(ui_renderer, widget_content.popup_menu_widget) - - UIRenderer.end_pass(ui_renderer) - - ui_renderer.input_service = input_manager:get_service("vmf_options_menu") - - - -- CLOSE WITH PRESSED BUTTONS ######################## - - -- Left Mouse Button or Enter pressed ---------------- - - if Mouse.released(0) and not widget_content.wrong_mouse_on_release and not numeric_menu_content.slider_is_held or Keyboard.released(13) then - self:callback_change_numeric_menu_visibility(widget_content) - - if new_value_number and new_value_number >= widget_content.range[1] and new_value_number <= widget_content.range[2] then - widget_content.current_value = new_value_number - widget_content.current_value_text = widget_content.current_value .. "" -- so "1337." -> "1337" - - if widget_content.unit_text then - widget_content.current_value_text = widget_content.current_value_text .. widget_content.unit_text - end - - return true - end - end - - -- Esc pressed --------------------------------------- - - if Keyboard.released(27) then - self:callback_change_numeric_menu_visibility(widget_content) - end - - -- Fix for closing menu when releasing LMB outside the hotspot - - if numeric_menu_content.slider_hotspot.is_held then - numeric_menu_content.slider_is_held = true - end - - if Mouse.released(0) and numeric_menu_content.slider_is_held then - numeric_menu_content.slider_is_held = false - end - - - widget_content.wrong_mouse_on_release = nil -end - - --- #################################################################################################################### --- ##### MISCELLANEOUS: SETTINGS LIST WIDGETS ######################################################################### --- #################################################################################################################### - - -VMFOptionsView.sort_settings_list_widgets = function (self) - - local sorted_settings_list_widgets = {} - - local favorited_mods_widgets = {} - local favorited_mods_names = {} - - local regular_mods_widgets = {} - local regular_mods_names = {} - - for _, mod_widgets in ipairs(self.settings_list_widgets) do - - if mod_widgets[1].content.is_favorited then - favorited_mods_widgets[mod_widgets[1].content.mod_name] = mod_widgets - table.insert(favorited_mods_names, mod_widgets[1].content.mod_name) - else - - -- if there are 2 (or more) mods with the same (readable) name - if regular_mods_widgets[mod_widgets[1].content.text] then - local random_number = tostring(math.random(10000)) - - regular_mods_widgets[mod_widgets[1].content.text .. random_number] = mod_widgets - table.insert(regular_mods_names, mod_widgets[1].content.text .. random_number) - else - regular_mods_widgets[mod_widgets[1].content.text] = mod_widgets - table.insert(regular_mods_names, mod_widgets[1].content.text) - end - end - end - - -- favorite mods sorting + cleaning up the favs list setting - - local favorite_mods_list = vmf:get("options_menu_favorite_mods") - if favorite_mods_list then - - local new_favorite_mods_list = {} - - for _, mod_name in ipairs(favorite_mods_list) do - if favorited_mods_widgets[mod_name] then - table.insert(sorted_settings_list_widgets, favorited_mods_widgets[mod_name]) - table.insert(new_favorite_mods_list, mod_name) - end - end - - vmf:set("options_menu_favorite_mods", new_favorite_mods_list) - end - - -- regular mods sorting (ABC order) - - table.sort(regular_mods_names, function(a, b) return a:upper() < b:upper() end) - - for _, mod_name in ipairs(regular_mods_names) do - table.insert(sorted_settings_list_widgets, regular_mods_widgets[mod_name]) - end - - self.settings_list_widgets = sorted_settings_list_widgets -end - - -VMFOptionsView.update_picked_option_for_settings_list_widgets = function (self) - - local widget_content - local widget_type - local loaded_setting_value - - for _, mod_widgets in ipairs(self.settings_list_widgets) do - for _, widget in ipairs(mod_widgets) do - - widget_content = widget.content - widget_type = widget_content.widget_type - - if widget_type == "checkbox" then - - loaded_setting_value = get_mod(widget_content.mod_name):get(widget_content.setting_id) - - if type(loaded_setting_value) == "boolean" then - widget_content.is_checkbox_checked = loaded_setting_value - else - --if type(loaded_setting_value) ~= "nil" then - -- @TODO: warning: variable of wrong type in config - --end - - widget_content.is_checkbox_checked = widget_content.default_value - get_mod(widget_content.mod_name):set(widget_content.setting_id, widget_content.default_value) - end - - elseif widget_type == "dropdown" then - - loaded_setting_value = get_mod(widget_content.mod_name):get(widget_content.setting_id) - - local setting_not_found = true - for i, option_value in ipairs(widget_content.options_values) do - - 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 - end - end - - if setting_not_found then - --if type(loaded_setting_value) ~= "nil" then - -- @TODO: warning: variable which is not in the dropdown options list in config - --end - - for i, option_value in ipairs(widget_content.options_values) do - - 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_id, widget_content.default_value) - end - end - end - - elseif widget_type == "header" then - - widget_content.is_checkbox_checked = get_mod(widget_content.mod_name):is_enabled() or - get_mod(widget_content.mod_name):get_internal_data("is_mutator") - - elseif widget_type == "keybind" then - - loaded_setting_value = get_mod(widget_content.mod_name):get(widget_content.setting_id) - - if type(loaded_setting_value) == "table" then - widget_content.keys = loaded_setting_value - else - -- @TODO: warning: - widget_content.keys = widget_content.default_value - end - - widget_content.keybind_text = vmf.build_keybind_string(widget_content.keys) - - elseif widget_type == "numeric" then - - loaded_setting_value = get_mod(widget_content.mod_name):get(widget_content.setting_id) - - if type(loaded_setting_value) == "number" then - - -- the fload numbers is some kind of magic in lua - local decimals_number = widget_content.decimals_number and widget_content.decimals_number or 0 - loaded_setting_value = math.round_with_precision(loaded_setting_value, decimals_number) - - widget_content.current_value_text = loaded_setting_value .. "" - widget_content.current_value = loaded_setting_value - else - -- @TODO: warning: - widget_content.current_value_text = widget_content.default_value .. "" - widget_content.current_value = widget_content.default_value - end - - if widget_content.unit_text then - widget_content.current_value_text = widget_content.current_value_text .. widget_content.unit_text - end - end - end - end -end - - -VMFOptionsView.update_settings_list_widgets_visibility = function (self, mod_name) - - for _, mod_widgets in ipairs(self.settings_list_widgets) do - - if not mod_name or mod_widgets[1].content.mod_name == mod_name then - - 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] - local widget_type = parent_widget.content.widget_type - - -- if 'header' or 'checkbox' - if widget_type == "header" or widget_type == "checkbox" then - - widget.content.is_widget_visible = parent_widget.content.is_checkbox_checked and parent_widget.content.is_widget_visible and not parent_widget.content.is_widget_collapsed - - -- 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] 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 - 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_id) - end - -- if 'group' - else - widget.content.is_widget_visible = parent_widget.content.is_widget_visible and not parent_widget.content.is_widget_collapsed - end - end - end - end - end -end - - -VMFOptionsView.readjust_visible_settings_list_widgets_position = function (self) - - local offset_y = 0 - - for _, mod_widgets in ipairs(self.settings_list_widgets) do - for _, widget in ipairs(mod_widgets) do - if widget.content.is_widget_visible then - - widget.offset[2] = -offset_y - - if widget.content.popup_menu_widget then - widget.content.popup_menu_widget.offset[2] = -offset_y - end - - offset_y = offset_y + ((widget.content.widget_type == "header") and SETTINGS_LIST_HEADER_WIDGET_SIZE[2] or SETTINGS_LIST_REGULAR_WIDGET_SIZE[2]) - end - end - end - - local list_size_y = offset_y - local mask_size_y = self.setting_list_mask_size_y - local is_scrolling_enabled = false - local max_offset_y = 0 - - if mask_size_y < list_size_y then - is_scrolling_enabled = true - max_offset_y = list_size_y - mask_size_y - end - - self.ui_scenegraph[self.settings_list_scenegraph_id].size[2] = list_size_y - - self.max_setting_list_offset_y = max_offset_y - self.settings_list_size_y = list_size_y - self.current_setting_list_offset_y = math.clamp(self.current_setting_list_offset_y, 0, max_offset_y) - - - self.menu_widgets["scrollbar"].content.visible = is_scrolling_enabled - self.menu_widgets["mousewheel_scroll_area"].content.visible = is_scrolling_enabled - - if is_scrolling_enabled then - self:calculate_scrollbar_size() - self:update_scrollbar_position() - end -end - - --- #################################################################################################################### --- ##### MISCELLANEOUS: SCROLLING'N'STUFF ############################################################################# --- #################################################################################################################### - - -VMFOptionsView.calculate_scrollbar_size = function (self) - - local widget_content = self.menu_widgets["scrollbar"].content - - local percentage = self.setting_list_mask_size_y / self.settings_list_size_y - - widget_content.scroll_bar_info.bar_height_percentage = percentage -end - - -VMFOptionsView.update_mousewheel_scroll_area_input = function (self) - local widget_content = self.menu_widgets["mousewheel_scroll_area"].content - - local mouse_scroll_value = widget_content.internal_scroll_value - - if mouse_scroll_value ~= 0 then - - local new_offset = self.current_setting_list_offset_y + mouse_scroll_value * _SCROLL_STEP - - self.current_setting_list_offset_y = math.clamp(new_offset, 0, self.max_setting_list_offset_y) - - widget_content.internal_scroll_value = 0 - - self:update_scrollbar_position() - end -end - - -VMFOptionsView.update_scrollbar_input = function (self) - local scrollbar_info = self.menu_widgets["scrollbar"].content.scroll_bar_info - local value = scrollbar_info.value - local old_value = scrollbar_info.old_value - - if value ~= old_value then - self.current_setting_list_offset_y = self.max_setting_list_offset_y * value - scrollbar_info.old_value = value - end -end - - -VMFOptionsView.update_scrollbar_position = function (self) - - local widget_content = self.menu_widgets["scrollbar"].content - - local percentage = self.current_setting_list_offset_y / self.max_setting_list_offset_y - - widget_content.scroll_bar_info.value = percentage - widget_content.scroll_bar_info.old_value = percentage -end - --- #################################################################################################################### --- ##### SEARCH BAR ################################################################################################### --- #################################################################################################################### - - -VMFOptionsView.update_search_bar = function (self) - - local widget_content = self.menu_widgets["search_bar"].content - - if self.search_bar_selected then - - if Mouse.any_pressed() == 0 and not widget_content.hotspot.is_hover or -- Left Mouse Button - Keyboard.any_pressed() == 27 or -- Esc - Keyboard.any_released() == 13 then -- Enter - - self:deactivate_search_bar() - - return - end - - local keystrokes = Keyboard.keystrokes() - - local old_search_text = widget_content.text - local text_index = string.len(old_search_text) + 1 - - local new_search_text = KeystrokeHelper.parse_strokes(old_search_text, text_index, "insert", keystrokes) - - new_search_text = string.gsub(new_search_text, "%%", "") - - if new_search_text ~= old_search_text then - self:filter_mods_settings_by_name(new_search_text) - end - - widget_content.text = new_search_text - end - - if widget_content.hotspot.on_release then - - self:activate_search_bar() - self:filter_mods_settings_by_name("") - end -end - - -VMFOptionsView.activate_search_bar = function (self) - - self.menu_widgets["search_bar"].content.text = "" - self.menu_widgets["search_bar"].content.is_active = true - - self.search_bar_selected = true - - self.input_manager:device_unblock_all_services("keyboard", 1) - self.input_manager:block_device_except_service("changing_setting", "keyboard", 1, "keybind") -end - - -VMFOptionsView.deactivate_search_bar = function (self) - - self.menu_widgets["search_bar"].content.is_active = false - - self.search_bar_selected = false - - self.input_manager:device_unblock_all_services("keyboard", 1) - self.input_manager:block_device_except_service("vmf_options_menu", "keyboard", 1, "keybind") -end - - -VMFOptionsView.filter_mods_settings_by_name = function (self, pattern) - - pattern = string.upper(pattern) - - if pattern == "" then - - for _, mod_widgets in ipairs(self.settings_list_widgets) do - - local content = mod_widgets[1].content - - content.is_widget_visible = true - end - else - - for _, mod_widgets in ipairs(self.settings_list_widgets) do - - local content = mod_widgets[1].content - - if string.find(string.upper(content.text), pattern) then - content.is_widget_visible = true - else - content.is_widget_visible = false - end - end - end - - self:update_settings_list_widgets_visibility() - self:readjust_visible_settings_list_widgets_position() -end - - --- #################################################################################################################### --- ##### UPDATE ####################################################################################################### --- #################################################################################################################### - - -VMFOptionsView.update = function (self, dt) - if self.suspended then - return - end - - if self.is_scrolling_enabled then - self:update_scrollbar_input() - self:update_mousewheel_scroll_area_input() - end - - self.dt = dt - self.draw_widgets(self, dt) - - local input_service = self:input_service() - if input_service.get(input_service, "toggle_menu") then - self.ingame_ui:handle_transition("exit_menu") - end - - self:update_search_bar() - -end - - -VMFOptionsView.draw_widgets = function (self, dt) - local ui_renderer = self.ui_renderer - local ui_scenegraph = self.ui_scenegraph - local input_manager = self.input_manager - local input_service = input_manager.get_service(input_manager, "vmf_options_menu") - - UIRenderer.begin_pass(ui_renderer, ui_scenegraph, input_service, dt, nil, self.render_settings) - - local menu_widgets = self.menu_widgets - - for _, widget in pairs(menu_widgets) do - UIRenderer.draw_widget(ui_renderer, widget) - end - - self:update_settings_list(self.settings_list_widgets, ui_renderer, ui_scenegraph) - - - UIRenderer.end_pass(ui_renderer) -end - --- update settings list widgets position, and draw widget which are inside the visible area -VMFOptionsView.update_settings_list = function (self, settings_list_widgets, ui_renderer, ui_scenegraph) - - local scenegraph = ui_scenegraph[self.settings_list_scenegraph_id] - scenegraph.offset[2] = self.current_setting_list_offset_y - - local scenegraph_id_start = self.settings_list_scenegraph_id_start - local list_position = UISceneGraph.get_world_position(ui_scenegraph, scenegraph_id_start) - local mask_pos = Vector3.deprecated_copy(UISceneGraph.get_world_position(ui_scenegraph, "sg_settings_list_mask")) - local mask_size = UISceneGraph.get_size(ui_scenegraph, "sg_settings_list_mask") - local temp_pos_table = {} - - for _, mod_widgets in ipairs(settings_list_widgets) do - for _, widget in ipairs(mod_widgets) do - if widget.content.is_widget_visible then - local style = widget.style - local size = style.size - local offset = style.offset - - temp_pos_table[1] = list_position[1] + offset[1] - temp_pos_table[2] = list_position[2] + offset[2] + widget.offset[2] - - local lower_visible = is_point_inside_2d_box(temp_pos_table, mask_pos, mask_size) - temp_pos_table[2] = temp_pos_table[2] + size[2] - local top_visible = is_point_inside_2d_box(temp_pos_table, mask_pos, mask_size) - - local visible = lower_visible or top_visible - if visible then - UIRenderer.draw_widget(ui_renderer, widget) - end - end - end - end -end - - --- #################################################################################################################### --- ##### SOME OTHER STUFF ############################################################################################# --- #################################################################################################################### - - -VMFOptionsView.on_enter = function (self) - - if ShowCursorStack.stack_depth == 0 or not VT1 then ShowCursorStack.push() end - - local input_manager = self.input_manager - input_manager.block_device_except_service(input_manager, "vmf_options_menu", "keyboard", 1) - input_manager.block_device_except_service(input_manager, "vmf_options_menu", "mouse", 1) - input_manager.block_device_except_service(input_manager, "vmf_options_menu", "gamepad", 1) - - WwiseWorld.trigger_event(self.wwise_world, "Play_hud_button_open") - - self:sort_settings_list_widgets() - self:update_picked_option_for_settings_list_widgets() - self:update_settings_list_widgets_visibility() - self:readjust_visible_settings_list_widgets_position() -end - - -VMFOptionsView.on_exit = function (self) - WwiseWorld.trigger_event(self.wwise_world, "Play_hud_button_close") - - -- in VT1 cursor will be romover automatically - if not VT1 then ShowCursorStack.pop() end - - vmf.save_unsaved_settings_to_file() -end - - --- default event, is used by IngameUI -VMFOptionsView.input_service = function (self) - return self.input_manager:get_service("vmf_options_menu") -end - - --- I'm not really sure if suspend and unsuspend are needed. --- --- StateInGameRunning.gm_event_end_conditions_met -> --- IngameUI.suspend_active_view -> --- XXXXXXX.suspend -VMFOptionsView.suspend = function (self) - self.suspended = true - - self.input_manager:device_unblock_all_services("keyboard", 1) - self.input_manager:device_unblock_all_services("mouse", 1) - self.input_manager:device_unblock_all_services("gamepad", 1) -end -VMFOptionsView.unsuspend = function (self) - self.suspended = nil - - self.input_manager:block_device_except_service("vmf_options_menu", "keyboard", 1) - self.input_manager:block_device_except_service("vmf_options_menu", "mouse", 1) - self.input_manager:block_device_except_service("vmf_options_menu", "gamepad", 1) end -- #################################################################################################################### @@ -4271,36 +17,1233 @@ end -- #################################################################################################################### vmf.load_vmf_options_view_settings = function() - if vmf:get("vmf_options_scrolling_speed") then - _SCROLL_STEP = _DEFAULT_SCROLL_STEP / 100 * vmf:get("vmf_options_scrolling_speed") - end + load_scrolling_speed_setting() end -- #################################################################################################################### --- ##### Script ####################################################################################################### +-- ##### VMF Options View Class ####################################################################################### -- #################################################################################################################### -vmf.load_vmf_options_view_settings() +local _content_blueprints = vmf:dofile("dmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_content_blueprints") +local _view_settings = vmf:dofile("dmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_settings") +local InputUtils = require("scripts/managers/input/input_utils") +local ScriptWorld = require("scripts/foundation/utilities/script_world") +local UIFonts = require("scripts/managers/ui/ui_fonts") +local UIRenderer = require("scripts/managers/ui/ui_renderer") +local UIWidget = require("scripts/managers/ui/ui_widget") +local UIWidgetGrid = require("scripts/ui/widget_logic/ui_widget_grid") +local ViewElementInputLegend = require("scripts/ui/view_elements/view_element_input_legend/view_element_input_legend") +local ViewElementKeybindPopup = require("scripts/ui/view_elements/view_element_keybind_popup/view_element_keybind_popup") -vmf:register_view({ - view_name = "vmf_options_view", - view_settings = { - init_view_function = function (ingame_ui_context) - return VMFOptionsView:new(ingame_ui_context) - end, - active = { - inn = true, - ingame = true +local CATEGORIES_GRID = 1 +local SETTINGS_GRID = 2 + +local VMFOptionsView = class("VMFOptionsView", "BaseView") + +VMFOptionsView.init = function (self, settings) + local definitions = vmf:dofile("dmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_definitions") + + VMFOptionsView.super.init(self, definitions, settings) + + self._pass_draw = false + + self:_setup_offscreen_gui() +end + +VMFOptionsView.on_enter = function (self) + _widgets_by_name = self._widgets_by_name + + if not self._options_templates then + self._options_templates = { + settings = {}, + categories = {} } - }, - view_transitions = { - vmf_options_view_open = function (self) - self.current_view = "vmf_options_view" - self.menu_active = true - end, - vmf_options_view_close = function (self) - require("scripts/ui/views/ingame_ui_settings").transitions.exit_menu(self) + vmf:create_mod_options_settings(self._options_templates) + end + VMFOptionsView.super.on_enter(self) + + self._default_category = nil + self._using_cursor_navigation = Managers.ui:using_cursor_navigation() + self._validation_mapping = self:_map_validations(self._options_templates) + + self:_setup_settings_config(self._options_templates) + self:_setup_category_config(self._options_templates) + self:_setup_input_legend() + self:_enable_settings_overlay(false) + self:_update_grid_navigation_selection() +end + +VMFOptionsView._map_validations = function (self, config) + local config_categories = config.categories + local categories = {} + + for i = 1, #config_categories do + local category_config = config_categories[i] + local validation_result = category_config.validation_function and category_config.validation_function() + + if validation_result == nil then + validation_result = true end + + categories[category_config.display_name] = { + validation_function = category_config.validation_function, + validation_result = validation_result, + settings = {} + } + end + + local config_settings = config.settings + + for _, setting in ipairs(config_settings) do + local validation_result = setting.validation_function and setting.validation_function() + + if validation_result == nil then + validation_result = true + end + + categories[setting.category].settings[setting.display_name] = { + validation_function = setting.validation_function, + validation_result = validation_result + } + end + + return categories +end + +VMFOptionsView.on_exit = function (self) + Managers.event:trigger("event_on_input_settings_changed") + + if self._input_legend_element then + self._input_legend_element = nil + + self:_remove_element("input_legend") + end + + if self._popup_id then + Managers.event:trigger("event_remove_ui_popup", self._popup_id) + end + + if self._ui_offscreen_renderer then + self._ui_offscreen_renderer = nil + + Managers.ui:destroy_renderer(self.__class_name .. "_ui_offscreen_renderer") + + local offscreen_world = self._offscreen_world + local offscreen_viewport_name = self._offscreen_viewport_name + + ScriptWorld.destroy_viewport(offscreen_world, offscreen_viewport_name) + Managers.ui:destroy_world(offscreen_world) + + self._offscreen_viewport = nil + self._offscreen_viewport_name = nil + self._offscreen_world = nil + end + + VMFOptionsView.super.on_exit(self) +end + +VMFOptionsView.cb_on_back_pressed = function (self) + local selected_navigation_column = self._selected_navigation_column_index + local selected_settings_widget = self._selected_settings_widget + + if selected_settings_widget then + self._close_selected_setting = true + elseif selected_navigation_column == SETTINGS_GRID then + self:_change_navigation_column(selected_navigation_column - 1) + else + local view_name = "vmf_options_view" + Managers.ui:close_view(view_name) + end +end + +VMFOptionsView.cb_reset_category_to_default = function (self) + local selected_category = self._selected_category + local reset_functions_by_category = self._reset_functions_by_category + local reset_function = reset_functions_by_category[selected_category] + local context = { + title_text = "loc_popup_header_settings_reset_default", + description_text = "loc_popup_description_settings_reset_default", + type = "warning", + options = { + { + text = "loc_popup_button_settings_reset_default", + close_on_pressed = true, + callback = callback(function () + if reset_function then + reset_function() + else + local settings_category_default_values = self._settings_category_default_values + local settings_default_values = selected_category and settings_category_default_values[selected_category] + + if settings_default_values then + for setting, default_value in pairs(settings_default_values) do + local on_activated = setting.on_activated + + if on_activated then + on_activated(default_value, setting) + end + end + end + end + + self._popup_id = nil + end) + }, + { + text = "loc_popup_button_cancel_settings_reset_default", + template_type = "terminal_button_small", + close_on_pressed = true, + hotkey = "back", + callback = function () + self._popup_id = nil + end + } + } } -}) + + Managers.event:trigger("event_show_ui_popup", context, function (id) + self._popup_id = id + end) +end + +VMFOptionsView._setup_input_legend = function (self) + self._input_legend_element = self:_add_element(ViewElementInputLegend, "input_legend", 10) + local legend_inputs = self._definitions.legend_inputs + + for i = 1, #legend_inputs do + local legend_input = legend_inputs[i] + local on_pressed_callback = legend_input.on_pressed_callback and callback(self, legend_input.on_pressed_callback) + + self._input_legend_element:add_entry(legend_input.display_name, legend_input.input_action, legend_input.visibility_function, on_pressed_callback, legend_input.alignment) + end +end + +VMFOptionsView._setup_content_grid_scrollbar = function (self, grid, widget_id, grid_scenegraph_id, grid_pivot_scenegraph_id) + local widgets_by_name = self._widgets_by_name + local scrollbar_widget = widgets_by_name[widget_id] + + load_scrolling_speed_setting() + + grid:assign_scrollbar(scrollbar_widget, grid_pivot_scenegraph_id, grid_scenegraph_id) + grid:set_scrollbar_progress(0) +end + +VMFOptionsView._setup_offscreen_gui = function (self) + local ui_manager = Managers.ui + local class_name = self.__class_name + local timer_name = "ui" + local world_layer = 10 + local world_name = class_name .. "_ui_offscreen_world" + local view_name = self.view_name + self._offscreen_world = ui_manager:create_world(world_name, world_layer, timer_name, view_name) + local shading_environment = _view_settings.shading_environment + local viewport_name = class_name .. "_ui_offscreen_world_viewport" + local viewport_type = "overlay_offscreen" + local viewport_layer = 1 + self._offscreen_viewport = ui_manager:create_viewport(self._offscreen_world, viewport_name, viewport_type, viewport_layer, shading_environment) + self._offscreen_viewport_name = viewport_name + self._ui_offscreen_renderer = ui_manager:create_renderer(class_name .. "_ui_offscreen_renderer", self._offscreen_world) +end + +VMFOptionsView._setup_content_widgets = function (self, content, scenegraph_id, callback_name) + local definitions = self._definitions + local widget_definitions = {} + local widgets = {} + local alignment_list = {} + local amount = #content + + for i = 1, amount do + local entry = content[i] + local verified = true + + if verified then + local widget_type = entry.widget_type + local widget = nil + local template = _content_blueprints[widget_type] + local size = template.size + local pass_template = template.pass_template + + if pass_template and not widget_definitions[widget_type] then + local scenegraph_definition = definitions.scenegraph_definition + widget_definitions[widget_type] = UIWidget.create_definition(pass_template, scenegraph_id, nil, size) + end + + local widget_definition = widget_definitions[widget_type] + + if widget_definition then + local name = scenegraph_id .. "_widget_" .. i + widget = self:_create_widget(name, widget_definition) + local init = template.init + + if init then + init(self, widget, entry, callback_name) + end + + local focus_group = entry.focus_group + + if focus_group then + widget.content.focus_group = focus_group + end + + widgets[#widgets + 1] = widget + end + + alignment_list[#alignment_list + 1] = widget or { + size = size + } + end + end + + return widgets, alignment_list +end + +VMFOptionsView._draw_widgets = function (self, dt, t, input_service, ui_renderer) + local widgets_by_name = self._widgets_by_name + local scrollbar_widget = widgets_by_name.scrollbar + scrollbar_widget.content.visible = self._category_content_grid:can_scroll() + + if self._selected_settings_widget then + UIWidget.draw(self._selected_settings_widget, ui_renderer) + + input_service = input_service:null_service() + ui_renderer.input_service = input_service + end + + VMFOptionsView.super._draw_widgets(self, dt, t, input_service, ui_renderer) +end + +VMFOptionsView._draw_elements = function (self, dt, t, ui_renderer, render_settings, input_service) + if self:_handling_keybinding() or self._selected_settings_widget then + input_service = input_service:null_service() + ui_renderer.input_service = input_service + end + + VMFOptionsView.super._draw_elements(self, dt, t, ui_renderer, render_settings, input_service) +end + +VMFOptionsView.draw = function (self, dt, t, input_service, layer) + if self:_handling_keybinding() then + input_service = input_service:null_service() + end + + local widgets_by_name = self._widgets_by_name + local grid_interaction_widget = widgets_by_name.grid_interaction + + self:_draw_grid(self._category_content_grid, self._category_content_widgets, grid_interaction_widget, dt, t, input_service) + + if self._settings_content_grid then + local grid_interaction_widget = widgets_by_name.settings_grid_interaction + + self:_draw_grid(self._settings_content_grid, self._settings_content_widgets, grid_interaction_widget, dt, t, input_service) + end + + VMFOptionsView.super.draw(self, dt, t, input_service, layer) +end + +VMFOptionsView._draw_grid = function (self, grid, widgets, interaction_widget, dt, t, input_service) + local is_grid_hovered = not self._using_cursor_navigation or interaction_widget.content.hotspot.is_hover or false + local null_input_service = input_service:null_service() + local render_settings = self._render_settings + local ui_renderer = self._ui_offscreen_renderer + local ui_scenegraph = self._ui_scenegraph + + UIRenderer.begin_pass(ui_renderer, ui_scenegraph, input_service, dt, render_settings) + + for j = 1, #widgets do + local widget = widgets[j] + local draw = widget ~= self._selected_settings_widget + + if draw then + if self._selected_settings_widget then + ui_renderer.input_service = null_input_service + end + + if grid:is_widget_visible(widget) then + local hotspot = widget.content.hotspot + + if hotspot then + hotspot.force_disabled = not is_grid_hovered + local is_active = hotspot.is_focused or hotspot.is_hover + + if is_active and widget.content.entry and (widget.content.entry.tooltip_text or widget.content.entry.disabled_by and not table.is_empty(widget.content.entry.disabled_by)) then + self:_set_tooltip_data(widget) + end + end + + UIWidget.draw(widget, ui_renderer) + end + end + end + + UIRenderer.end_pass(ui_renderer) +end + +VMFOptionsView._setup_grid = function (self, widgets, alignment_list, grid_scenegraph_id, spacing, use_is_focused) + local ui_scenegraph = self._ui_scenegraph + local direction = "down" + local grid = UIWidgetGrid:new(widgets, alignment_list, ui_scenegraph, grid_scenegraph_id, direction, spacing, nil, use_is_focused) + local render_scale = self._render_scale + + grid:set_render_scale(render_scale) + + return grid +end + +VMFOptionsView.set_render_scale = function (self, scale) + VMFOptionsView.super.set_render_scale(self, scale) + self._category_content_grid:set_render_scale(self._render_scale) + + if self._settings_content_grid then + self._settings_content_grid:set_render_scale(self._render_scale) + end +end + +VMFOptionsView.update = function (self, dt, t, input_service, view_data) + local drawing_view = view_data and view_data.drawing_view + local using_cursor_navigation = Managers.ui:using_cursor_navigation() + + if self:_handling_keybinding() then + if not drawing_view or not using_cursor_navigation then + self:close_keybind_popup(true) + end + + input_service = input_service:null_service() + end + + self:_handle_keybind_rebind(dt, t, input_service) + + local close_keybind_popup_duration = self._close_keybind_popup_duration + + if close_keybind_popup_duration then + if close_keybind_popup_duration < 0 then + self._close_keybind_popup_duration = nil + + self:close_keybind_popup(true) + else + self._close_keybind_popup_duration = close_keybind_popup_duration - dt + end + end + + local grid_length = self._category_content_grid:length() + + if grid_length ~= self._grid_length then + self._grid_length = grid_length + end + + local category_grid_is_focused = self._selected_navigation_column_index == CATEGORIES_GRID + local category_grid_input_service = category_grid_is_focused and input_service or input_service:null_service() + + self._category_content_grid:update(dt, t, category_grid_input_service) + self:_update_category_content_widgets(dt, t) + + local settings_content_grid = self._settings_content_grid + + if settings_content_grid then + local settings_grid_input_service = not category_grid_is_focused and not self._selected_settings_widget and input_service or input_service:null_service() + + settings_content_grid:update(dt, t, settings_grid_input_service) + self:_update_settings_content_widgets(dt, t, input_service) + end + + if self._validation_mapping then + local needs_reset = false + local reset_all = false + + for category_name, category_data in pairs(self._validation_mapping) do + local valid = category_data.validation_function and category_data.validation_function() + + if valid ~= nil and valid ~= category_data.validation_result then + category_data.validation_result = valid + needs_reset = true + + if reset_all == false then + reset_all = valid == false and self._selected_category == category_name + end + end + + if category_data.settings then + for _, settings_data in pairs(category_data.settings) do + valid = settings_data.validation_function and settings_data.validation_function() + + if valid ~= nil and valid ~= settings_data.validation_result then + settings_data.validation_result = valid + needs_reset = true + end + end + end + end + + if needs_reset then + self:_reset_options_view(reset_all) + end + end + + if self._tooltip_data and self._tooltip_data.widget and not self._tooltip_data.widget.content.hotspot.is_hover then + self._tooltip_data = {} + self._widgets_by_name.tooltip.content.visible = false + end + + return VMFOptionsView.super.update(self, dt, t, input_service) +end + +VMFOptionsView.on_resolution_modified = function (self) + VMFOptionsView.super.on_resolution_modified(self) + + local scale = self._render_scale + + self._category_content_grid:on_resolution_modified(scale) + + if self._settings_content_grid then + self._settings_content_grid:on_resolution_modified(scale) + end + + self._grid_length = nil +end + +VMFOptionsView._on_navigation_input_changed = function (self) + VMFOptionsView.super._on_navigation_input_changed(self) + + if self._settings_content_widgets then + self:_update_grid_navigation_selection() + end +end + +VMFOptionsView._reset_options_view = function (self, reset_all) + if reset_all then + self._selected_category = nil + self._selected_settings_widget = nil + self._selected_navigation_row_index = nil + self._selected_navigation_column_index = nil + end + + self._selected_category_widget = nil + + self:_setup_settings_config(self._options_templates) + self:_setup_category_config(self._options_templates) + + if self._category_content_widgets then + self._selected_category = self._selected_category or self._options_templates.categories[1].display_name + + for i = 1, #self._category_content_widgets do + local widget = self._category_content_widgets[i] + widget.content.hotspot.is_focused = widget.content.entry.display_name == self._selected_category + widget.content.hotspot.is_selected = widget.content.entry.display_name == self._selected_category + end + end + + self:_update_grid_navigation_selection() +end + +VMFOptionsView.settings_grid_length = function (self) + local grid = self._settings_content_grid + + if grid then + local scroll_length = grid:scroll_length() + local total_length = grid:length() + local area_length = grid:area_length() + + return math.max(total_length - scroll_length, area_length) + end + + return 0 +end + +VMFOptionsView.settings_scroll_amount = function (self) + local grid = self._settings_content_grid + + if grid then + local scroll_progress = grid:scrollbar_progress() + local scroll_length = grid:scroll_length() + + return scroll_length * scroll_progress + end + + return 0 +end + +VMFOptionsView.set_exclusive_focus_on_grid_widget = function (self, widget_name) + self:_set_exclusive_focus_on_grid_widget(widget_name) +end + +VMFOptionsView._handle_input = function (self, input_service) + local selected_settings_widget = self._selected_settings_widget + + if selected_settings_widget then + local close_selected_setting = false + + if input_service:get("left_pressed") or input_service:get("confirm_pressed") or input_service:get("back") then + close_selected_setting = true + else + self._navigation_column_changed_this_frame = false + end + + self._close_selected_setting = close_selected_setting + else + local selected_navigation_row = self._selected_navigation_row_index + local selected_navigation_column = self._selected_navigation_column_index + + if selected_navigation_row and selected_navigation_column then + if input_service:get("navigate_left_continuous") then + self:_change_navigation_column(selected_navigation_column - 1) + elseif input_service:get("navigate_right_continuous") then + self:_change_navigation_column(selected_navigation_column + 1) + elseif not input_service:get("confirm_pressed") and not input_service:get("back") then + self._navigation_column_changed_this_frame = false + end + elseif not input_service:get("confirm_pressed") and not input_service:get("back") then + self._navigation_column_changed_this_frame = false + end + end +end + +VMFOptionsView._update_grid_navigation_selection = function (self) + local selected_column_index = self._selected_navigation_column_index + local selected_row_index = self._selected_navigation_row_index + + if self._using_cursor_navigation then + if selected_row_index or selected_column_index then + self:_set_selected_navigation_widget(nil) + end + else + local navigation_widgets = self._navigation_widgets[selected_column_index] + local selected_widget = navigation_widgets and navigation_widgets[selected_row_index] or self._selected_settings_widget + + if selected_widget then + local selected_grid = self._navigation_grids[selected_column_index] + + if not selected_grid or not selected_grid:selected_grid_index() then + self:_set_selected_navigation_widget(selected_widget) + end + elseif navigation_widgets or self._settings_content_widgets then + self:_set_default_navigation_widget() + elseif self._default_category then + self:present_category_widgets(self._default_category) + end + end +end + +VMFOptionsView.present_category_widgets = function (self, category) + self._selected_category = category + local settings_category_widgets = self._settings_category_widgets + local grid_data = settings_category_widgets[category] + + if grid_data then + local widgets = {} + local alignment_widgets = {} + + for i = 1, #grid_data do + local widget = grid_data[i].widget + local alignment_widget = grid_data[i].alignment_widget + widgets[#widgets + 1] = widget + alignment_widgets[#alignment_widgets + 1] = alignment_widget + end + + self._settings_content_widgets = widgets + self._settings_alignment_list = alignment_widgets + local scrollbar_widget_id = "settings_scrollbar" + local grid_scenegraph_id = "settings_grid_background" + local grid_pivot_scenegraph_id = "settings_grid_content_pivot" + local grid_spacing = _view_settings.grid_spacing + self._settings_content_grid = self:_setup_grid(self._settings_content_widgets, self._settings_alignment_list, grid_scenegraph_id, grid_spacing, false) + + self:_setup_content_grid_scrollbar(self._settings_content_grid, scrollbar_widget_id, grid_scenegraph_id, grid_pivot_scenegraph_id) + + self._navigation_widgets[SETTINGS_GRID] = widgets + self._navigation_grids[SETTINGS_GRID] = self._settings_content_grid + + self:_update_grid_navigation_selection() + end +end + +VMFOptionsView._setup_category_config = function (self, config) + if self._category_content_widgets then + for i = 1, #self._category_content_widgets do + local widget = self._category_content_widgets[i] + + self:_unregister_widget_name(widget.name) + end + + self._category_content_widgets = {} + end + + local config_categories = config.categories + local entries = {} + local reset_functions_by_category = {} + local categories_by_display_name = {} + + for i = 1, #config_categories do + local category_config = config_categories[i] + local category_display_name = category_config.display_name + local category_icon = category_config.icon + local category_reset_function = category_config.reset_function + local valid = self._validation_mapping[category_display_name].validation_result + + if valid then + local entry = { + widget_type = "settings_button", + display_name = category_display_name, + icon = category_icon, + can_be_reset = category_config.can_be_reset, + pressed_function = function (parent, widget, entry) + self._category_content_grid:select_widget(widget) + + local widget_name = widget.name + + self:present_category_widgets(category_display_name) + + local selected_navigation_column = self._selected_navigation_column_index + + if selected_navigation_column then + self:_change_navigation_column(selected_navigation_column + 1) + end + end, + select_function = function (parent, widget, entry) + self:present_category_widgets(category_display_name) + end + } + entries[#entries + 1] = entry + categories_by_display_name[category_display_name] = entry + reset_functions_by_category[category_display_name] = category_reset_function + end + end + + self._default_category = config_categories[1].display_name + local scenegraph_id = "grid_content_pivot" + local callback_name = "cb_on_category_pressed" + self._category_content_widgets, self._category_alignment_list = self:_setup_content_widgets(entries, scenegraph_id, callback_name) + local scrollbar_widget_id = "scrollbar" + local grid_scenegraph_id = "background" + local grid_pivot_scenegraph_id = "grid_content_pivot" + local grid_spacing = _view_settings.grid_spacing + self._category_content_grid = self:_setup_grid(self._category_content_widgets, self._category_alignment_list, grid_scenegraph_id, grid_spacing, true) + + self:_setup_content_grid_scrollbar(self._category_content_grid, scrollbar_widget_id, grid_scenegraph_id, grid_pivot_scenegraph_id) + + self._reset_functions_by_category = reset_functions_by_category + self._categories_by_display_name = categories_by_display_name + self._navigation_widgets = { + self._category_content_widgets + } + self._navigation_grids = { + self._category_content_grid + } +end + +VMFOptionsView._setup_settings_config = function (self, config) + if self._settings_category_widgets then + for _, settings_data in pairs(self._settings_category_widgets) do + for i = 1, #settings_data do + local widget = settings_data[i].widget + + self:_unregister_widget_name(widget.name) + end + end + + self._settings_category_widgets = {} + end + + local config_settings = config.settings + local category_widgets = {} + local settings_default_values = {} + local aligment_list = {} + local callback_name = "cb_on_settings_pressed" + + for setting_index, setting in ipairs(config_settings) do + local valid = self._validation_mapping[setting.category].settings[setting.display_name].validation_result + + if valid and not setting.hidden then + local category = setting.category or "Uncategorized" + local widgets = category_widgets[category] + + if not category_widgets[category] then + category_widgets[category] = {} + widgets = category_widgets[category] + end + + if not settings_default_values[category] then + settings_default_values[category] = {} + end + + if setting.get_function then + settings_default_values[category][setting] = setting.default_value + end + + local widget_suffix = "setting_" .. tostring(setting_index) + local widget, alignment_widget = self:_create_settings_widget_from_config(setting, category, widget_suffix, callback_name) + category_widgets[category][#widgets + 1] = { + widget = widget, + alignment_widget = alignment_widget + } + end + end + + self._settings_category_default_values = settings_default_values + self._settings_category_widgets = category_widgets +end + +VMFOptionsView._update_category_content_widgets = function (self, dt, t) + local category_content_widgets = self._category_content_widgets + + if category_content_widgets then + local is_focused_grid = self._selected_navigation_column_index == CATEGORIES_GRID + local selected_category_widget = self._selected_category_widget + + for i = 1, #category_content_widgets do + local widget = category_content_widgets[i] + local hotspot = widget.content.hotspot + + if hotspot.is_focused then + hotspot.is_selected = true + + if widget ~= selected_category_widget then + self._selected_category_widget = widget + local entry = widget.content.entry + + if entry and entry.select_function then + entry.select_function(self, widget, entry) + end + end + elseif is_focused_grid then + hotspot.is_selected = false + end + end + end +end + +VMFOptionsView._set_tooltip_data = function (self, widget) + local current_widget = self._tooltip_data and self._tooltip_data.widget + local display_text = nil + local tooltip_text = widget.content.entry.tooltip_text + local disabled_by_list = widget.content.entry.disabled_by + + if tooltip_text then + display_text = tooltip_text + end + + if disabled_by_list then + display_text = display_text and string.format("%s\n", display_text) + + for _, text in pairs(disabled_by_list) do + display_text = display_text and string.format("%s\n%s", display_text, text) or text + end + end + + local starting_point = self:_scenegraph_world_position("settings_grid_start") + local current_y = self._widgets_by_name.tooltip.offset[2] + local scroll_addition = self._settings_content_grid:length_scrolled() + local new_y = starting_point[2] + widget.offset[2] - scroll_addition + + if current_widget ~= widget or current_widget == widget and new_y ~= current_y then + self._tooltip_data = { + widget = widget, + text = display_text + } + self._widgets_by_name.tooltip.content.text = display_text + local text_style = self._widgets_by_name.tooltip.style.text + local x_pos = starting_point[1] + widget.offset[1] + local width = widget.content.size[1] * 0.5 + local text_options = UIFonts.get_font_options_by_style(text_style) + local _, text_height = self:_text_size(display_text, text_style.font_type, text_style.font_size, { + width, + 0 + }, text_options) + local height = text_height + self._widgets_by_name.tooltip.content.size = { + width, + height + } + self._widgets_by_name.tooltip.offset[1] = x_pos - width * 0.8 + self._widgets_by_name.tooltip.offset[2] = new_y - height + self._widgets_by_name.tooltip.content.visible = true + end +end + +VMFOptionsView._update_settings_content_widgets = function (self, dt, t, input_service) + local settings_content_widgets = self._settings_content_widgets + + if settings_content_widgets then + local focused_widget_index = self._settings_content_grid:selected_grid_index() + local focused_widget = focused_widget_index and settings_content_widgets[focused_widget_index] + + if focused_widget then + self:_set_selected_navigation_widget(focused_widget) + end + + local handle_input = false + local selected_settings_widget = self._selected_settings_widget + + for i = 1, #settings_content_widgets do + local widget = settings_content_widgets[i] + local widget_type = widget.type + local template = _content_blueprints[widget_type] + local update = template and template.update + + if update then + update(self, widget, input_service, dt, t) + end + end + + if selected_settings_widget and self._close_selected_setting then + self:_set_exclusive_focus_on_grid_widget(nil) + self:_update_grid_navigation_selection() + + self._close_selected_setting = nil + end + end +end + +VMFOptionsView._create_settings_widget_from_config = function (self, config, category, suffix, callback_name) + local scenegraph_id = "settings_grid_content_pivot" + local default_value = config.default_value + local default_value_type = type(default_value) + local options = config.options or config.options_function and config.options_function() + local widget_type = config.widget_type + + if not widget_type then + if options then + widget_type = "dropdown" + else + local get_function = config.get_function + + if get_function then + local value = get_function(config) + local value_type = value ~= nil and type(value) or default_value_type + + if value_type == "boolean" then + widget_type = "checkbox" + elseif value_type == "number" then + widget_type = "value_slider" + elseif value_type == "string" then + widget_type = "settings_button" + else + widget_type = "settings_button" + end + end + end + end + + if widget_type == "button" then + config.ignore_focus = true + end + + local widget = nil + local template = _content_blueprints[widget_type] + local size = template.size_function and template.size_function(self, config) or template.size + local indentation_level = config.indentation_level or 0 + local indentation_spacing = _view_settings.indentation_spacing * indentation_level + local new_size = { + size[1] - indentation_spacing, + size[2] + } + local pass_template_function = template.pass_template_function + local pass_template = pass_template_function and pass_template_function(self, config, new_size) or template.pass_template + local widget_definition = pass_template and UIWidget.create_definition(pass_template, scenegraph_id, nil, new_size) + local name = "widget_" .. suffix + + if widget_definition then + widget = self:_create_widget(name, widget_definition) + widget.type = widget_type + local init = template.init + + if init then + init(self, widget, config, callback_name) + end + end + + if widget then + return widget, { + horizontal_alignment = "right", + size = size, + name = name + } + else + return nil, { + size = size + } + end +end + +VMFOptionsView._handle_keybind_rebind = function (self, dt, t, input_service) + if self._handling_keybind then + local input_manager = Managers.input + local results = input_manager:key_watch_result() + + if results then + local entry = self._active_keybind_entry + local widget = self._active_keybind_widget + local presentation_string = InputUtils.localized_string_from_key_info(results) + local service_type = entry.service_type + local alias_name = entry.alias_name + local value = entry.value + local can_close = entry.on_activated(results, value) + + if can_close then + self:close_keybind_popup() + else + Managers.input:stop_key_watch() + + local devices = entry.devices + + Managers.input:start_key_watch(devices) + end + end + end +end + +VMFOptionsView._handling_keybinding = function (self) + return self._handling_keybind or self._close_keybind_popup_duration ~= nil +end + +VMFOptionsView.show_keybind_popup = function (self, widget, entry) + if not self:_handling_keybinding() then + self._active_keybind_entry = entry + self._active_keybind_widget = widget + local layer = 100 + local reference_name = "keybind_popup" + self._keybind_popup = self:_add_element(ViewElementKeybindPopup, reference_name, layer) + local display_name = entry.display_name or self:_localize("loc_settings_option_unavailable") + + self._keybind_popup:set_action_text(display_name) + + if entry.cancel_keys then + local input_text = entry.cancel_keys[1] + local description_text = Localize("loc_setting_keybinding_press_new_button", true, { + cancel_input = InputUtils.key_axis_locale(input_text) + }) + + self._keybind_popup:set_description_text(description_text) + end + + local value = entry:get_function() + local devices = entry.devices + local value_text = value and InputUtils.localized_string_from_key_info(value) or self:_localize("loc_keybind_unassigned") + + self._keybind_popup:set_value_text(value_text) + Managers.input:start_key_watch(devices) + + self._handling_keybind = true + + self:set_can_exit(false) + end +end + +VMFOptionsView.close_keybind_popup = function (self, force_close) + if force_close then + Managers.input:stop_key_watch() + + local reference_name = "keybind_popup" + self._keybind_popup = nil + + self:_remove_element(reference_name) + self:set_can_exit(true, true) + else + self._close_keybind_popup_duration = 0.2 + end + + self._handling_keybind = false + self._active_keybind_entry = nil + self._active_keybind_widget = nil +end + +VMFOptionsView._set_warning_text = function (self) + local widgets_by_name = self._widgets_by_name + local warning_text = widgets_by_name.warning_text + local action = "TEST" + local color_1 = self:_get_color_string_by_color(Color.ui_brown_light(255, true)) + local color_2 = self:_get_color_string_by_color(Color.red(255, true)) + warning_text.content.text = string.format("Warning! Input for action %s%s%s has been unassigned.", color_1, action, color_2) +end + +VMFOptionsView.cb_on_category_pressed = function (self, widget, entry) + local pressed_function = entry.pressed_function + + if pressed_function then + pressed_function(self, widget, entry) + end +end + +VMFOptionsView.cb_on_settings_pressed = function (self, widget, entry) + if not self._can_close or self._selected_settings_widget or self._navigation_column_changed_this_frame then + return + end + + local pressed_function = entry.pressed_function + + if pressed_function then + pressed_function(self, widget, entry) + end + + if not entry.ignore_focus then + local widget_name = widget.name + local selected_widget = self:_set_exclusive_focus_on_grid_widget(widget_name) + selected_widget.offset[3] = selected_widget and 90 or 0 + end +end + +VMFOptionsView._enable_settings_overlay = function (self, enable) + local widgets_by_name = self._widgets_by_name + local settings_overlay_widget = widgets_by_name.settings_overlay + settings_overlay_widget.content.visible = enable +end + +VMFOptionsView._set_exclusive_focus_on_grid_widget = function (self, widget_name) + local widgets = self._settings_content_widgets + local selected_widget = nil + + for i = 1, #widgets do + local widget = widgets[i] + local selected = widget.name == widget_name + local content = widget.content + content.exclusive_focus = selected + local hotspot = content.hotspot or content.button_hotspot + + if hotspot then + hotspot.is_selected = selected + + if selected then + selected_widget = widget + end + end + end + + self._selected_settings_widget = selected_widget + local has_exclusive_focus = selected_widget ~= nil and not self._using_cursor_navigation + + self:_enable_settings_overlay(has_exclusive_focus) + self:set_can_exit(not has_exclusive_focus, not has_exclusive_focus) + + return selected_widget +end + +VMFOptionsView._change_navigation_column = function (self, column_index) + local navigation_widgets = self._navigation_widgets + local num_columns = #navigation_widgets + local success = false + + if column_index < 1 or num_columns < column_index or self._navigation_column_changed_this_frame then + return success + else + success = true + self._navigation_column_changed_this_frame = true + end + + local widgets = navigation_widgets[column_index] + + for i = 1, #widgets do + local widget = widgets[i] + local content = widget.content + local hotspot = content.hotspot or content.button_hotspot + + if hotspot and hotspot.is_selected then + self:_set_selected_navigation_widget(widget) + + return success + end + end + + local navigation_grid = self._navigation_grids[column_index] + local scrollbar_progress = navigation_grid:scrollbar_progress() + + for i = 1, #widgets do + local widget = widgets[i] + local content = widget.content + local hotspot = content.hotspot or content.button_hotspot + + if hotspot then + local scroll_position = navigation_grid:get_scrollbar_percentage_by_index(i) or 0 + + if scrollbar_progress <= scroll_position then + self:_set_selected_navigation_widget(widget) + + return success + end + end + end +end + +VMFOptionsView._set_default_navigation_widget = function (self) + local navigation_widgets = self._navigation_widgets + + for i = 1, #navigation_widgets do + if self:_change_navigation_column(i) then + return + end + end +end + +VMFOptionsView._set_selected_navigation_widget = function (self, widget) + local widget_name = widget and widget.name + local selected_row, selected_column = nil + local navigation_widgets = self._navigation_widgets + + for column_index = 1, #navigation_widgets do + local widgets = navigation_widgets[column_index] + local _, focused_grid_index = self:_set_focused_grid_widget(widgets, widget_name) + + if focused_grid_index then + self:_set_selected_grid_widget(widgets, widget_name) + + selected_row = focused_grid_index + selected_column = column_index + end + end + + local navigation_grids = self._navigation_grids + + for column_index = 1, #navigation_grids do + local selected_grid = column_index == selected_column + local navigation_grid = navigation_grids[column_index] + + navigation_grid:select_grid_index(selected_grid and selected_row or nil, nil, nil, column_index == CATEGORIES_GRID) + end + + self._selected_navigation_row_index = selected_row + self._selected_navigation_column_index = selected_column +end + +VMFOptionsView._set_focused_grid_widget = function (self, widgets, widget_name) + local selected_widget, selected_widget_index = nil + + for i = 1, #widgets do + local widget = widgets[i] + local is_focused = widget.name == widget_name + local content = widget.content + local hotspot = content.hotspot or content.button_hotspot + + if hotspot then + hotspot.is_focused = is_focused + + if is_focused then + selected_widget = widget + selected_widget_index = i + end + end + end + + return selected_widget, selected_widget_index +end + +VMFOptionsView._set_selected_grid_widget = function (self, widgets, widget_name) + local selected_widget, selected_widget_index = nil + + for i = 1, #widgets do + local widget = widgets[i] + local is_selected = widget.name == widget_name + local content = widget.content + local hotspot = content.hotspot or content.button_hotspot + + if hotspot then + hotspot.is_selected = is_selected + + if is_selected then + selected_widget = widget + selected_widget_index = i + end + end + end + + return selected_widget, selected_widget_index +end + +return VMFOptionsView diff --git a/vmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_content_blueprints.lua b/vmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_content_blueprints.lua new file mode 100644 index 0000000..64518d0 --- /dev/null +++ b/vmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_content_blueprints.lua @@ -0,0 +1,780 @@ +local vmf = get_mod("VMF") + +local _view_settings = vmf:dofile("dmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_settings") + +local ButtonPassTemplates = require("scripts/ui/pass_templates/button_pass_templates") +local CheckboxPassTemplates = require("scripts/ui/pass_templates/checkbox_pass_templates") +local DropdownPassTemplates = require("scripts/ui/pass_templates/dropdown_pass_templates") +local InputUtils = require("scripts/managers/input/input_utils") +local KeybindPassTemplates = require("scripts/ui/pass_templates/keybind_pass_templates") +local SliderPassTemplates = require("scripts/ui/pass_templates/slider_pass_templates") +local UIFonts = require("scripts/managers/ui/ui_fonts") +local UIFontSettings = require("scripts/managers/ui/ui_font_settings") +local UIRenderer = require("scripts/managers/ui/ui_renderer") + +local grid_size = _view_settings.grid_size +local grid_width = grid_size[1] + +local settings_grid_width = 1000 +local settings_value_width = 500 +local settings_value_height = 64 + +local group_header_height = 80 + +local DEFAULT_NUM_DECIMALS = 0 + +local value_font_style = table.clone(UIFontSettings.list_button) +value_font_style.offset = { + settings_grid_width - settings_value_width + 25, + 0, + 8 +} + +local description_font_style = table.clone(UIFontSettings.list_button) +description_font_style.offset = { + 25, + 0, + 3 +} + +local header_font_style = table.clone(UIFontSettings.header_2) +header_font_style.text_vertical_alignment = "bottom" + +local blueprints = { + spacing_vertical = { + size = { + grid_width, + 20 + } + }, + settings_button = { + size = { + grid_width, + settings_value_height + }, + pass_template = ButtonPassTemplates.list_button, + init = function (parent, widget, entry, callback_name) + local content = widget.content + local hotspot = content.hotspot + + hotspot.pressed_callback = function () + local is_disabled = entry.disabled or false + + if is_disabled then + return + end + + callback(parent, callback_name, widget, entry)() + end + + local display_name = entry.display_name + content.text = display_name + content.entry = entry + end + }, + button = { + size = { + settings_grid_width, + settings_value_height + }, + pass_template = ButtonPassTemplates.settings_button(settings_grid_width, settings_value_height, settings_value_width, true), + init = function (parent, widget, entry, callback_name) + local content = widget.content + + content.hotspot.pressed_callback = function () + local is_disabled = entry.disabled or false + + if is_disabled then + return + end + + callback(parent, callback_name, widget, entry)() + end + + local display_name = entry.display_name + content.text = display_name + content.button_text = Localize("loc_settings_change") + content.entry = entry + end + }, + group_header = { + size = { + settings_grid_width, + group_header_height + }, + pass_template = { + { + pass_type = "text", + value_id = "text", + style = header_font_style, + value = Localize("loc_settings_option_unavailable") + } + }, + init = function (parent, widget, entry, callback_name) + local content = widget.content + local display_name = entry.display_name + content.text = display_name + end + }, + checkbox = { + size = { + settings_grid_width, + settings_value_height + }, + pass_template_function = function (parent, config, size) + return CheckboxPassTemplates.settings_checkbox(size[1], settings_value_height, settings_value_width, 2, true) + end, + init = function (parent, widget, entry, callback_name) + local content = widget.content + local display_name = entry.display_name or Localize("loc_settings_option_unavailable") + content.text = display_name + content.entry = entry + + for i = 1, 2 do + local widget_option_id = "option_" .. i + content[widget_option_id] = i == 1 and Managers.localization:localize("loc_setting_checkbox_on") or Managers.localization:localize("loc_setting_checkbox_off") + end + end, + update = function (parent, widget, input_service, dt, t) + local content = widget.content + local entry = content.entry + local value = entry:get_function() + local on_activated = entry.on_activated + local pass_input = true + local hotspot = content.hotspot + local is_disabled = entry.disabled or false + content.disabled = is_disabled + local new_value = nil + + if hotspot.on_pressed and not is_disabled then + new_value = not value + end + + for i = 1, 2 do + local widget_option_id = "option_hotspot_" .. i + local option_hotspot = content[widget_option_id] + local is_selected = value and i == 1 or not value and i == 2 + option_hotspot.is_selected = is_selected + end + + if new_value ~= nil and new_value ~= value then + on_activated(new_value, entry) + end + end + } +} + +local function slider_init_function(parent, widget, entry, callback_name) + local content = widget.content + local display_name = entry.display_name or Localize("loc_settings_option_unavailable") + content.text = display_name + content.entry = entry + content.area_length = settings_value_width + content.step_size = entry.normalized_step_size + + if not entry.normalized_step_size and entry.step_size_value then + local value_range = entry.max_value - entry.min_value + content.step_size = entry.step_size_value / value_range + end + + content.apply_on_drag = entry.apply_on_drag and true + local get_function = entry.get_function + local value = get_function(entry) + content.previous_slider_value = value + content.slider_value = value + + content.pressed_callback = function () + local is_disabled = entry.is_disabled + + if is_disabled then + return + end + + callback(parent, callback_name, widget, entry)() + end +end + +blueprints.percent_slider = { + size = { + settings_grid_width, + settings_value_height + }, + pass_template_function = function (parent, config, size) + return SliderPassTemplates.settings_percent_slider(size[1], settings_value_height, settings_value_width, true) + end, + init = function (parent, widget, entry, callback_name) + slider_init_function(parent, widget, entry, callback_name) + end, + update = function (parent, widget, input_service, dt, t) + local content = widget.content + local entry = content.entry + local pass_input = true + local is_disabled = entry.disabled or false + content.disabled = is_disabled + local using_gamepad = not parent:using_cursor_navigation() + local get_function = entry.get_function + local value = get_function(entry) / 100 + local on_activated = entry.on_activated + local format_value_function = entry.format_value_function + local drag_value, new_value = nil + local apply_on_drag = content.apply_on_drag and not is_disabled + local drag_active = content.drag_active and not is_disabled + local focused = using_gamepad and content.exclusive_focus and not is_disabled + + if drag_active or focused then + if not parent._selected_settings_widget then + parent:set_exclusive_focus_on_grid_widget(widget.name) + end + + local slider_value = content.slider_value + drag_value = slider_value or get_function(entry) / 100 + elseif not focused then + local previous_slider_value = content.previous_slider_value + local slider_value = content.slider_value + + if content.drag_previously_active then + parent:set_exclusive_focus_on_grid_widget(nil) + + if previous_slider_value ~= slider_value then + new_value = slider_value + drag_value = new_value or get_function(entry) / 100 + end + elseif value ~= slider_value then + content.slider_value = value + content.previous_slider_value = value + content.scroll_add = nil + end + + content.previous_slider_value = slider_value + end + + content.drag_previously_active = drag_active + local display_value = format_value_function((drag_value or value) * 100) + + if display_value then + content.value_text = display_value + end + + local hotspot = content.hotspot + + if hotspot.on_pressed and not is_disabled then + if focused then + new_value = content.slider_value + elseif using_gamepad then + content.pressed_callback() + end + end + + if focused and parent:can_exit() then + parent:set_can_exit(false) + end + + if apply_on_drag and drag_value and not new_value and content.slider_value ~= content.previous_slider_value then + new_value = content.slider_value + end + + if new_value then + on_activated(new_value * 100, entry) + + content.slider_value = new_value + content.previous_slider_value = new_value + content.scroll_add = nil + end + + return pass_input + end +} + +blueprints.value_slider = { + size = { + settings_grid_width, + settings_value_height + }, + pass_template_function = function (parent, config, size) + return SliderPassTemplates.settings_value_slider(size[1], settings_value_height, settings_value_width, true) + end, + init = function (parent, widget, entry, callback_name) + slider_init_function(parent, widget, entry, callback_name) + end, + update = function (parent, widget, input_service, dt, t) + local content = widget.content + local entry = content.entry + local pass_input = true + local is_disabled = entry.disabled or false + content.disabled = is_disabled + local using_gamepad = not parent:using_cursor_navigation() + local min_value = entry.min_value + local max_value = entry.max_value + local get_function = entry.get_function + local explode_function = entry.explode_function + local value = get_function(entry) + local normalized_value = math.normalize_01(value, min_value, max_value) + local on_activated = entry.on_activated + local format_value_function = entry.format_value_function + local drag_value, new_normalized_value = nil + local apply_on_drag = content.apply_on_drag and not is_disabled + local drag_active = content.drag_active and not is_disabled + local drag_previously_active = content.drag_previously_active + local focused = content.exclusive_focus and using_gamepad and not is_disabled + + if drag_active or focused then + if not parent._selected_settings_widget then + parent:set_exclusive_focus_on_grid_widget(widget.name) + end + + local slider_value = content.slider_value + drag_value = slider_value and explode_function(slider_value, entry) or get_function(entry) + elseif not focused or drag_previously_active then + local previous_slider_value = content.previous_slider_value + local slider_value = content.slider_value + + if drag_previously_active then + parent:set_exclusive_focus_on_grid_widget(nil) + + if previous_slider_value ~= slider_value then + new_normalized_value = slider_value + drag_value = slider_value and explode_function(slider_value, entry) or get_function(entry) + end + elseif normalized_value ~= slider_value then + content.slider_value = normalized_value + content.previous_slider_value = normalized_value + content.scroll_add = nil + end + + content.previous_slider_value = slider_value + end + + content.drag_previously_active = drag_active + local display_value = format_value_function(drag_value or value) + + if display_value then + content.value_text = display_value + end + + local hotspot = content.hotspot + + if hotspot.on_pressed then + if focused then + new_normalized_value = content.slider_value + elseif using_gamepad then + content.pressed_callback() + end + end + + if focused and parent:can_exit() then + parent:set_can_exit(false) + end + + if apply_on_drag and drag_value and not new_normalized_value and content.slider_value ~= content.previous_slider_value then + new_normalized_value = content.slider_value + end + + if new_normalized_value then + local new_value = explode_function(new_normalized_value, entry) + + on_activated(new_value, entry) + + content.slider_value = new_normalized_value + content.previous_slider_value = new_normalized_value + content.scroll_add = nil + end + + return pass_input + end +} + +blueprints.slider = { + size = { + settings_grid_width, + settings_value_height + }, + pass_template_function = function (parent, config, size) + return SliderPassTemplates.settings_value_slider(size[1], settings_value_height, settings_value_width, true) + end, + init = function (parent, widget, entry, callback_name) + local content = widget.content + local display_name = entry.display_name or Localize("loc_settings_option_unavailable") + content.text = display_name + content.entry = entry + content.area_length = settings_value_width + content.step_size = entry.step_size_fraction + content.apply_on_drag = entry.apply_on_drag and true + local get_function = entry.get_function + local value, value_fraction = get_function(entry) + content.previous_slider_value = value_fraction + content.slider_value = value_fraction + content.pressed_callback = callback(parent, callback_name, widget, entry) + end, + update = function (parent, widget, input_service, dt, t) + local content = widget.content + local entry = content.entry + local pass_input = true + local is_disabled = entry.disabled or false + content.disabled = is_disabled + local using_gamepad = not parent:using_cursor_navigation() + local get_function = entry.get_function + local value, value_fraction = get_function(entry) + local on_activated = entry.on_activated + local format_value_function = entry.format_value_function + local num_decimals = entry.num_decimals + local drag_value, new_value_fraction = nil + local apply_on_drag = entry.apply_on_drag and not is_disabled + local drag_active = content.drag_active and not is_disabled + local drag_previously_active = content.drag_previously_active + local focused = content.exclusive_focus and using_gamepad and not is_disabled + + if drag_active or focused then + drag_value = math.lerp(entry.min_value, entry.max_value, content.slider_value) + elseif not focused or drag_previously_active then + local previous_slider_value = content.previous_slider_value + local slider_value = content.slider_value + + if drag_previously_active then + if previous_slider_value ~= slider_value then + new_value_fraction = slider_value + drag_value = math.lerp(entry.min_value, entry.max_value, new_value_fraction) + end + elseif value_fraction ~= slider_value then + content.slider_value = value_fraction + content.previous_slider_value = value_fraction + content.scroll_add = nil + end + + content.previous_slider_value = slider_value + end + + content.drag_previously_active = drag_active + local display_value = nil + + if format_value_function then + display_value = format_value_function(entry, drag_value or value) + else + local number_format = string.format("%%.%sf", num_decimals or DEFAULT_NUM_DECIMALS) + display_value = string.format(number_format, drag_value or value) + end + + if display_value then + content.value_text = display_value + end + + local hotspot = content.hotspot + + if hotspot.on_pressed and not is_disabled then + if focused then + new_value_fraction = content.slider_value + elseif not hotspot.is_hover then + content.pressed_callback() + end + end + + if focused and parent:can_exit() then + parent:set_can_exit(false) + end + + if apply_on_drag and drag_value and not new_value_fraction and content.slider_value ~= content.previous_slider_value then + new_value_fraction = content.slider_value + end + + if new_value_fraction then + local new_value = math.lerp(entry.min_value, entry.max_value, new_value_fraction) + + on_activated(new_value, entry) + + content.slider_value = new_value_fraction + content.previous_slider_value = new_value_fraction + content.scroll_add = nil + end + + return pass_input + end +} + +local max_visible_options = _view_settings.max_visible_dropdown_options or 5 +blueprints.dropdown = { + size = { + settings_grid_width, + settings_value_height + }, + pass_template_function = function (parent, entry, size) + local has_options_function = entry.options_function ~= nil + local has_dynamic_contents = entry.has_dynamic_contents + local display_name = entry.display_name or Localize("loc_settings_option_unavailable") + local options = entry.options_function and entry.options_function() or entry.options + local num_visible_options = math.min(#options, max_visible_options) + + return DropdownPassTemplates.settings_dropdown(size[1], settings_value_height, settings_value_width, num_visible_options, true) + end, + init = function (parent, widget, entry, callback_name) + local content = widget.content + local display_name = entry.display_name or Localize("loc_settings_option_unavailable") + content.text = display_name + content.entry = entry + local has_options_function = entry.options_function ~= nil + local has_dynamic_contents = entry.has_dynamic_contents + local options = entry.options or entry.options_function and entry.options_function() + local options_by_id = {} + local num_options = #options + local num_visible_options = math.min(num_options, max_visible_options) + content.num_visible_options = num_visible_options + local optional_num_decimals = entry.optional_num_decimals + local number_format = string.format("%%.%sf", optional_num_decimals or DEFAULT_NUM_DECIMALS) + + for i = 1, num_options do + local option = options[i] + options_by_id[option.id] = option + end + + content.number_format = number_format + content.options_by_id = options_by_id + content.options = options + + content.hotspot.pressed_callback = function () + local is_disabled = entry.disabled or false + + if is_disabled then + return + end + + callback(parent, callback_name, widget, entry)() + end + + local widget_type = widget.type + local template = blueprints[widget_type] + local size = template.size + content.area_length = size[2] * num_visible_options + local scroll_length = math.max(size[2] * num_options - content.area_length, 0) + content.scroll_length = scroll_length + local spacing = 0 + local scroll_amount = scroll_length > 0 and (size[2] + spacing) / scroll_length or 0 + content.scroll_amount = scroll_amount + local value = entry.get_function and entry:get_function() or entry.default_value + end, + update = function (parent, widget, input_service, dt, t) + local content = widget.content + local entry = content.entry + local pass_input = true + local is_disabled = entry.disabled or false + content.disabled = is_disabled + local using_gamepad = not parent:using_cursor_navigation() + local offset = widget.offset + local style = widget.style + local options = content.options + local options_by_id = content.options_by_id + local num_visible_options = content.num_visible_options + local num_options = #options + local focused = content.exclusive_focus and not is_disabled + + if focused and parent:can_exit() then + content.selected_index = nil + + parent:set_can_exit(false) + end + + local selected_index = content.selected_index + local value, new_value = nil + local hotspot = content.hotspot + local hotspot_style = style.hotspot + + if selected_index and focused then + if using_gamepad and hotspot.on_pressed then + new_value = options[selected_index].id + end + + hotspot_style.on_pressed_sound = hotspot_style.on_pressed_fold_in_sound + else + hotspot_style.on_pressed_sound = hotspot_style.on_pressed_fold_out_sound + end + + value = entry.get_function and entry:get_function() or content.internal_value or "" + + local preview_option = options_by_id[value] + local preview_option_id = preview_option and preview_option.id + local preview_value = preview_option and preview_option.display_name or Localize("loc_settings_option_unavailable") + + content.value_text = preview_value + + local widget_type = widget.type + local template = blueprints[widget_type] + local size = template.size + local scroll_amount = parent:settings_scroll_amount() + local scroll_area_height = parent:settings_grid_length() + local dropdown_length = size[2] * (num_visible_options + 1) + local grow_downwards = true + + if scroll_area_height <= offset[2] - scroll_amount + dropdown_length then + grow_downwards = false + end + + content.grow_downwards = grow_downwards + local new_selection_index = nil + + if not selected_index or not focused then + for i = 1, #options do + local option = options[i] + + if option.id == preview_option_id then + selected_index = i + + break + end + end + + selected_index = selected_index or 1 + end + + if selected_index and focused then + if input_service:get("navigate_up_continuous") then + if grow_downwards then + new_selection_index = math.max(selected_index - 1, 1) + else + new_selection_index = math.min(selected_index + 1, num_options) + end + elseif input_service:get("navigate_down_continuous") then + if grow_downwards then + new_selection_index = math.min(selected_index + 1, num_options) + else + new_selection_index = math.max(selected_index - 1, 1) + end + end + end + + if new_selection_index or not content.selected_index then + if new_selection_index then + selected_index = new_selection_index + end + + if num_visible_options < num_options then + local step_size = 1 / num_options + local new_scroll_percentage = math.min(selected_index - 1, num_options) * step_size + content.scroll_percentage = new_scroll_percentage + content.scroll_add = nil + end + + content.selected_index = selected_index + end + + local scroll_percentage = content.scroll_percentage + + if scroll_percentage then + local step_size = 1 / (num_options - (num_visible_options - 1)) + content.start_index = math.max(1, math.ceil(scroll_percentage / step_size)) + end + + local option_hovered = false + local option_index = 1 + local start_index = content.start_index or 1 + local end_index = math.min(start_index + num_visible_options - 1, num_options) + local using_scrollbar = num_visible_options < num_options + + for i = start_index, end_index do + local option_text_id = "option_text_" .. option_index + local option_hotspot_id = "option_hotspot_" .. option_index + local outline_style_id = "outline_" .. option_index + local option_hotspot = content[option_hotspot_id] + option_hovered = option_hovered or option_hotspot.is_hover + option_hotspot.is_selected = i == selected_index + local option = options[i] + + if not new_value and focused and not using_gamepad and option_hotspot.on_pressed then + option_hotspot.on_pressed = nil + new_value = option.id + content.selected_index = i + end + + local option_display_name = option.display_name + content[option_text_id] = option_display_name + local options_y = size[2] * option_index + style[option_hotspot_id].offset[2] = grow_downwards and options_y or -options_y + style[option_text_id].offset[2] = grow_downwards and options_y or -options_y + local entry_length = using_scrollbar and settings_value_width - style.scrollbar_hotspot.size[1] or settings_value_width + style[outline_style_id].size[1] = entry_length + style[option_text_id].size[1] = settings_value_width + option_index = option_index + 1 + end + + local value_changed = new_value ~= nil + + if value_changed and new_value ~= value then + local on_activated = entry.on_activated + + on_activated(new_value, entry) + end + + local scrollbar_hotspot = content.scrollbar_hotspot + local scrollbar_hovered = scrollbar_hotspot.is_hover + pass_input = using_gamepad or value_changed or not option_hovered and not scrollbar_hovered + + return pass_input + end +} + +blueprints.keybind = { + size = { + settings_grid_width, + settings_value_height + }, + pass_template = KeybindPassTemplates.settings_keybind(settings_grid_width, settings_value_height, settings_value_width), + init = function (parent, widget, entry, callback_name) + local content = widget.content + local display_name = entry.display_name or Localize("loc_settings_option_unavailable") + content.text = display_name + content.entry = entry + content.key_unassigned_string = Managers.localization:localize("loc_keybind_unassigned") + end, + update = function (parent, widget, input_service, dt, t) + local content = widget.content + local entry = content.entry + local value = entry:get_function() + local preview_value = value and InputUtils.localized_string_from_key_info(value) or content.key_unassigned_string + content.value_text = preview_value + local hotspot = content.hotspot + + if hotspot.on_released then + parent:show_keybind_popup(widget, entry, content.entry.cancel_keys) + end + end +} + +local description_font_style = table.clone(UIFontSettings.body_small) +description_font_style.offset = { + 25, + 0, + 3 +} +description_font_style.text_horizontal_alignment = "left" +description_font_style.text_vertical_alignment = "center" +description_font_style.hover_text_color = Color.ui_brown_super_light(255, true) + +blueprints.description = { + size = { + settings_grid_width - 225, + settings_value_height + }, + pass_template = { + { + value_id = "text", + pass_type = "text", + style_id = "text", + style = description_font_style, + value = Localize("loc_settings_option_unavailable") + } + }, + init = function (parent, widget, entry, callback_name) + local content = widget.content + local style = widget.style + local text_style = style.text + local display_name = entry.display_name + local display_text = display_name + local ui_renderer = parent._ui_renderer + local size = content.size + local text_options = UIFonts.get_font_options_by_style(text_style) + local _, height = UIRenderer.text_size(ui_renderer, display_text, text_style.font_type, text_style.font_size, size, text_options) + size[2] = math.ceil(height) + content.text = display_text + end, + update = function (parent, widget, input_service, dt, t) + return + end +} + +return settings("VMFOptionsViewContentBlueprints", blueprints) diff --git a/vmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_definitions.lua b/vmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_definitions.lua new file mode 100644 index 0000000..8f970c1 --- /dev/null +++ b/vmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_definitions.lua @@ -0,0 +1,454 @@ +local vmf = get_mod("VMF") + +local _view_settings = vmf:dofile("dmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_settings") + +local ScrollbarPassTemplates = require("scripts/ui/pass_templates/scrollbar_pass_templates") +local UIFontSettings = require("scripts/managers/ui/ui_font_settings") +local UIWidget = require("scripts/managers/ui/ui_widget") +local UIWorkspaceSettings = require("scripts/settings/ui/ui_workspace_settings") + +local scrollbar_width = _view_settings.scrollbar_width + +local grid_size = _view_settings.grid_size +local grid_width = grid_size[1] +local grid_height = grid_size[2] +local grid_blur_edge_size = _view_settings.grid_blur_edge_size + +local mask_size = { + grid_width + grid_blur_edge_size[1] * 2, + grid_height + grid_blur_edge_size[2] * 2 +} +local mask_offset_y = 16 +local settings_mask_size = { + 1080 + grid_blur_edge_size[1] * 2, + grid_height + grid_blur_edge_size[2] +} + +local settings_grid_height = grid_height + mask_offset_y + +local tooltip_text_style = table.clone(UIFontSettings.body) +tooltip_text_style.text_horizontal_alignment = "left" +tooltip_text_style.text_vertical_alignment = "center" +tooltip_text_style.horizontal_alignment = "left" +tooltip_text_style.vertical_alignment = "center" +tooltip_text_style.color = Color.white(255, true) +tooltip_text_style.offset = { + 0, + 0, + 2 +} + +local scenegraph_definition = { + screen = UIWorkspaceSettings.screen, + tooltip = { + vertical_alignment = "top", + parent = "screen", + horizontal_alignment = "left", + size = { + 0, + 0 + }, + position = { + 0, + 0, + 200 + } + }, + background = { + vertical_alignment = "top", + parent = "screen", + horizontal_alignment = "left", + size = { + grid_width, + grid_height + }, + position = { + 180, + 240, + 1 + } + }, + background_icon = { + vertical_alignment = "center", + parent = "screen", + horizontal_alignment = "center", + size = { + 1250, + 1250 + }, + position = { + 0, + 0, + 0 + } + }, + grid_start = { + vertical_alignment = "top", + parent = "background", + horizontal_alignment = "left", + size = { + 0, + 0 + }, + position = { + 0, + 0, + 0 + } + }, + grid_content_pivot = { + vertical_alignment = "top", + parent = "grid_start", + horizontal_alignment = "left", + size = { + 0, + 0 + }, + position = { + 0, + 0, + 1 + } + }, + grid_mask = { + vertical_alignment = "center", + parent = "background", + horizontal_alignment = "center", + size = mask_size, + position = { + 0, + 0, + 0 + } + }, + grid_interaction = { + vertical_alignment = "top", + parent = "background", + horizontal_alignment = "left", + size = { + grid_width + scrollbar_width * 2, + mask_size[2] + }, + position = { + 0, + 0, + 0 + } + }, + scrollbar = { + vertical_alignment = "center", + parent = "background", + horizontal_alignment = "right", + size = { + scrollbar_width, + grid_height + }, + position = { + 50, + 0, + 1 + } + }, + button = { + vertical_alignment = "left", + parent = "grid_content_pivot", + horizontal_alignment = "top", + size = { + 500, + 64 + }, + position = { + 0, + 0, + 0 + } + }, + title_divider = { + vertical_alignment = "top", + parent = "screen", + horizontal_alignment = "left", + size = { + 335, + 18 + }, + position = { + 180, + 145, + 1 + } + }, + title_text = { + vertical_alignment = "bottom", + parent = "title_divider", + horizontal_alignment = "left", + size = { + 500, + 50 + }, + position = { + 0, + -35, + 1 + } + }, + settings_grid_background = { + vertical_alignment = "top", + parent = "screen", + horizontal_alignment = "right", + size = { + 1000, + settings_grid_height + }, + position = { + -180, + 130, + 1 + } + }, + settings_grid_start = { + vertical_alignment = "top", + parent = "settings_grid_background", + horizontal_alignment = "left", + size = { + 0, + 0 + }, + position = { + 0, + 0, + 0 + } + }, + settings_grid_content_pivot = { + vertical_alignment = "top", + parent = "settings_grid_start", + horizontal_alignment = "left", + size = { + 0, + 0 + }, + position = { + 0, + 0, + 1 + } + }, + settings_scrollbar = { + vertical_alignment = "top", + parent = "settings_grid_background", + horizontal_alignment = "right", + size = { + scrollbar_width, + grid_height - 26 + }, + position = { + 50, + 45, + 1 + } + }, + settings_grid_mask = { + vertical_alignment = "top", + parent = "settings_grid_background", + horizontal_alignment = "center", + size = settings_mask_size, + position = { + 0, + mask_offset_y, + 0 + } + }, + settings_grid_interaction = { + vertical_alignment = "top", + parent = "settings_grid_background", + horizontal_alignment = "left", + size = { + 1000 + scrollbar_width * 2, + mask_size[2] + }, + position = { + 0, + 0, + 0 + } + } +} + +local widget_definitions = { + settings_overlay = UIWidget.create_definition({ + { + pass_type = "rect", + style = { + offset = { + 0, + 0, + 20 + }, + color = { + 160, + 0, + 0, + 0 + } + } + } + }, "screen"), + background = UIWidget.create_definition({ + { + pass_type = "rect", + style = { + color = { + 160, + 0, + 0, + 0 + } + } + } + }, "screen"), + title_divider = UIWidget.create_definition({ + { + pass_type = "texture", + value = "content/ui/materials/dividers/skull_rendered_left_01" + } + }, "title_divider"), + title_text = UIWidget.create_definition({ + { + value_id = "text", + style_id = "text", + pass_type = "text", + value = vmf:localize("mods_options"), + style = table.clone(UIFontSettings.header_1) + } + }, "title_text"), + background_icon = UIWidget.create_definition({ + { + value = "content/ui/vector_textures/symbols/cog_skull_01", + pass_type = "slug_icon", + style = { + offset = { + 0, + 0, + 0 + }, + color = { + 80, + 0, + 0, + 0 + } + } + } + }, "background_icon"), + tooltip = UIWidget.create_definition({ + { + pass_type = "rect", + style = { + vertical_alignment = "center", + horizontal_alignment = "center", + offset = { + 0, + 0, + 0 + }, + color = Color.ui_terminal(255, true), + size_addition = { + 23, + 23 + } + } + }, + { + pass_type = "rect", + style = { + vertical_alignment = "center", + horizontal_alignment = "center", + offset = { + 0, + 0, + 1 + }, + color = Color.black(255, true), + size_addition = { + 20, + 20 + } + } + }, + { + value_id = "text", + style_id = "text", + pass_type = "text", + value = "", + style = tooltip_text_style + } + }, "tooltip", { + visible = false + }), + scrollbar = UIWidget.create_definition(ScrollbarPassTemplates.default_scrollbar, "scrollbar"), + grid_mask = UIWidget.create_definition({ + { + value = "content/ui/materials/offscreen_masks/ui_overlay_offscreen_vertical_blur", + pass_type = "texture", + style = { + color = { + 255, + 255, + 255, + 255 + } + } + } + }, "grid_mask"), + grid_interaction = UIWidget.create_definition({ + { + pass_type = "hotspot", + content_id = "hotspot" + } + }, "grid_interaction"), + settings_scrollbar = UIWidget.create_definition(ScrollbarPassTemplates.default_scrollbar, "settings_scrollbar"), + settings_grid_mask = UIWidget.create_definition({ + { + value = "content/ui/materials/offscreen_masks/ui_overlay_offscreen_vertical_blur", + pass_type = "texture", + style = { + color = { + 255, + 255, + 255, + 255 + } + } + } + }, "settings_grid_mask"), + settings_grid_interaction = UIWidget.create_definition({ + { + pass_type = "hotspot", + content_id = "hotspot" + } + }, "settings_grid_interaction") +} +local legend_inputs = { + { + input_action = "back", + on_pressed_callback = "cb_on_back_pressed", + display_name = "loc_settings_menu_close_menu", + alignment = "left_alignment" + }, + { + input_action = "next", + display_name = "loc_settings_menu_reset_to_default", + on_pressed_callback = "cb_reset_category_to_default", + visibility_function = function (parent) + return not not parent._selected_category and parent._categories_by_display_name[parent._selected_category].can_be_reset + end + } +} + +local VMFOptionsViewDefinitions = { + legend_inputs = legend_inputs, + widget_definitions = widget_definitions, + scenegraph_definition = scenegraph_definition +} + +return settings("VMFOptionsViewDefinitions", VMFOptionsViewDefinitions) diff --git a/vmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_settings.lua b/vmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_settings.lua new file mode 100644 index 0000000..94c2636 --- /dev/null +++ b/vmf/scripts/mods/vmf/modules/ui/options/vmf_options_view_settings.lua @@ -0,0 +1,20 @@ +local vmf_options_view_settings = { + scrollbar_width = 10, + max_visible_dropdown_options = 5, + indentation_spacing = 40, + shading_environment = "content/shading_environments/ui/system_menu", + grid_size = { + 500, + 800 + }, + grid_spacing = { + 0, + 10 + }, + grid_blur_edge_size = { + 8, + 8 + } +} + +return settings("VMFOptionsViewSettings", vmf_options_view_settings) diff --git a/vmf/scripts/mods/vmf/modules/vmf_dummy.lua b/vmf/scripts/mods/vmf/modules/vmf_dummy.lua new file mode 100644 index 0000000..e04ff52 --- /dev/null +++ b/vmf/scripts/mods/vmf/modules/vmf_dummy.lua @@ -0,0 +1,4 @@ +local vmf = get_mod("VMF") + +-- Add vmf functions with a value of dummy_func if they need to be defined while a module is disabled. +local dummy_func = function() return end \ No newline at end of file diff --git a/vmf/scripts/mods/vmf/modules/vmf_mod_data.lua b/vmf/scripts/mods/vmf/modules/vmf_mod_data.lua index e72cb35..bf512ab 100644 --- a/vmf/scripts/mods/vmf/modules/vmf_mod_data.lua +++ b/vmf/scripts/mods/vmf/modules/vmf_mod_data.lua @@ -11,7 +11,7 @@ end -- ##################################################################################################################### -- Defining VMFMod class. -VMFMod = class(VMFMod) +VMFMod = class("VMFMod") -- Creating mod data table when object of VMFMod class is created. function VMFMod:init(mod_name) diff --git a/vmf/scripts/mods/vmf/modules/vmf_mod_manager.lua b/vmf/scripts/mods/vmf/modules/vmf_mod_manager.lua index 1c8e386..b8a9c15 100644 --- a/vmf/scripts/mods/vmf/modules/vmf_mod_manager.lua +++ b/vmf/scripts/mods/vmf/modules/vmf_mod_manager.lua @@ -168,40 +168,18 @@ function vmf.initialize_mod_data(mod, mod_data) vmf.register_mod_as_mutator(mod, mod_data.mutator_settings) end - -- Mod's options initialization (with legacy widget definitions support) + -- Mod's options initialization if mod_data.options or ((mod_data.is_togglable and not mod_data.is_mutator) and not mod_data.options_widgets) then local success, error_message = pcall(vmf.initialize_mod_options, mod, mod_data.options) if not success then mod:error(ERRORS.REGULAR.mod_options_initializing_failed, error_message) return end - elseif mod_data.options_widgets then - vmf.initialize_mod_options_legacy(mod, mod_data.options_widgets) end -- Textures initialization @TODO: move to a separate function if type(mod_data.custom_gui_textures) == "table" then - local custom_gui_textures = mod_data.custom_gui_textures - - if type(custom_gui_textures.textures) == "table" then - vmf.custom_textures(mod, unpack(custom_gui_textures.textures)) - end - - if type(custom_gui_textures.atlases) == "table" then - for _, atlas_settings in ipairs(custom_gui_textures.atlases) do - if type(atlas_settings) == "table" then - vmf.custom_atlas(mod, unpack(atlas_settings)) - end - end - end - - if type(custom_gui_textures.ui_renderer_injections) == "table" then - for _, injection_settings in ipairs(custom_gui_textures.ui_renderer_injections) do - if type(injection_settings) == "table" then - vmf.inject_materials(mod, unpack(injection_settings)) - end - end - end + -- @TODO: Not implemented end return true diff --git a/vmf/scripts/mods/vmf/modules/vmf_options.lua b/vmf/scripts/mods/vmf/modules/vmf_options.lua index 6c435cb..fc34627 100644 --- a/vmf/scripts/mods/vmf/modules/vmf_options.lua +++ b/vmf/scripts/mods/vmf/modules/vmf_options.lua @@ -1,7 +1,7 @@ local vmf = get_mod("VMF") local vmf_mod_data = {} -vmf_mod_data.name = "Vermintide Mod Framework" +vmf_mod_data.name = "Darktide Mod Framework" vmf_mod_data.options = { widgets = { { @@ -10,11 +10,7 @@ vmf_mod_data.options = { default_value = {"f4"}, keybind_trigger = "pressed", keybind_type = "view_toggle", - view_name = "vmf_options_view", - transition_data = { - open_view_transition_name = "vmf_options_view_open", - close_view_transition_name = "vmf_options_view_close" - } + view_name = "vmf_options_view" }, { setting_id = "vmf_options_scrolling_speed", @@ -60,40 +56,67 @@ vmf_mod_data.options = { default_value = "default", options = { {text = "settings_default", value = "default"}, - {text = "settings_custom", value = "custom", show_widgets = {1, 2, 3, 4, 5}}, + {text = "settings_custom", value = "custom", show_widgets = {1, 2, 3, 4, 5, 6}}, }, sub_widgets = { + { + setting_id = "output_mode_notification", + type = "dropdown", + default_value = 5, + options = { + {text = "output_disabled", value = 0}, + {text = "output_log", value = 1}, + {text = "output_chat", value = 2}, + {text = "output_notification", value = 3}, + {text = "output_log_and_chat", value = 4}, + {text = "output_log_and_notification", value = 5}, + {text = "output_chat_and_notification", value = 6}, + {text = "output_all", value = 7}, + } + }, { setting_id = "output_mode_echo", type = "dropdown", - default_value = 3, + default_value = 4, options = { - {text = "output_disabled", value = 0}, - {text = "output_log", value = 1}, - {text = "output_chat", value = 2}, - {text = "output_log_and_chat", value = 3}, + {text = "output_disabled", value = 0}, + {text = "output_log", value = 1}, + {text = "output_chat", value = 2}, + {text = "output_notification", value = 3}, + {text = "output_log_and_chat", value = 4}, + {text = "output_log_and_notification", value = 5}, + {text = "output_chat_and_notification", value = 6}, + {text = "output_all", value = 7}, } }, { setting_id = "output_mode_error", type = "dropdown", - default_value = 3, + default_value = 4, options = { - {text = "output_disabled", value = 0}, - {text = "output_log", value = 1}, - {text = "output_chat", value = 2}, - {text = "output_log_and_chat", value = 3}, + {text = "output_disabled", value = 0}, + {text = "output_log", value = 1}, + {text = "output_chat", value = 2}, + {text = "output_notification", value = 3}, + {text = "output_log_and_chat", value = 4}, + {text = "output_log_and_notification", value = 5}, + {text = "output_chat_and_notification", value = 6}, + {text = "output_all", value = 7}, } }, { setting_id = "output_mode_warning", type = "dropdown", - default_value = 3, + default_value = 4, options = { - {text = "output_disabled", value = 0}, - {text = "output_log", value = 1}, - {text = "output_chat", value = 2}, - {text = "output_log_and_chat", value = 3}, + {text = "output_disabled", value = 0}, + {text = "output_log", value = 1}, + {text = "output_chat", value = 2}, + {text = "output_notification", value = 3}, + {text = "output_log_and_chat", value = 4}, + {text = "output_log_and_notification", value = 5}, + {text = "output_chat_and_notification", value = 6}, + {text = "output_all", value = 7}, } }, { @@ -101,10 +124,14 @@ vmf_mod_data.options = { type = "dropdown", default_value = 1, options = { - {text = "output_disabled", value = 0}, - {text = "output_log", value = 1}, - {text = "output_chat", value = 2}, - {text = "output_log_and_chat", value = 3}, + {text = "output_disabled", value = 0}, + {text = "output_log", value = 1}, + {text = "output_chat", value = 2}, + {text = "output_notification", value = 3}, + {text = "output_log_and_chat", value = 4}, + {text = "output_log_and_notification", value = 5}, + {text = "output_chat_and_notification", value = 6}, + {text = "output_all", value = 7}, } }, { @@ -112,10 +139,14 @@ vmf_mod_data.options = { type = "dropdown", default_value = 0, options = { - {text = "output_disabled", value = 0}, - {text = "output_log", value = 1}, - {text = "output_chat", value = 2}, - {text = "output_log_and_chat", value = 3}, + {text = "output_disabled", value = 0}, + {text = "output_log", value = 1}, + {text = "output_chat", value = 2}, + {text = "output_notification", value = 3}, + {text = "output_log_and_chat", value = 4}, + {text = "output_log_and_notification", value = 5}, + {text = "output_chat_and_notification", value = 6}, + {text = "output_all", value = 7}, } } } @@ -139,7 +170,7 @@ vmf_mod_data.options = { { setting_id = "chat_history_remove_dups", type = "checkbox", - default_value = false, + default_value = true, sub_widgets = { { setting_id = "chat_history_remove_dups_mode", @@ -170,7 +201,10 @@ vmf.on_setting_changed = function (setting_id) if setting_id == "vmf_options_scrolling_speed" then - vmf.load_vmf_options_view_settings() + -- Not necessary until the view is loaded + if vmf.load_vmf_options_view_settings then + vmf.load_vmf_options_view_settings() + end elseif setting_id == "developer_mode" then @@ -192,6 +226,7 @@ vmf.on_setting_changed = function (setting_id) vmf.load_custom_textures_settings() elseif setting_id == "logging_mode" + or setting_id == "output_mode_notification" or setting_id == "output_mode_echo" or setting_id == "output_mode_error" or setting_id == "output_mode_warning" @@ -234,7 +269,11 @@ if not vmf:get("vmf_initialized") then vmf.load_custom_textures_settings() vmf.load_dev_console_settings() vmf.load_chat_history_settings() - --vmf.load_vmf_options_view_settings() + + -- Not necessary until the view is loaded + if vmf.load_vmf_options_view_settings then + vmf.load_vmf_options_view_settings() + end vmf:set("vmf_initialized", true) end diff --git a/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua b/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua index 166db7d..16e7aea 100644 --- a/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua +++ b/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua @@ -12,9 +12,6 @@ local PUBLIC_STATUSES = { local ERRORS = { REGULAR = { - -- check_vt1: - cant_use_vmf_package_manager_in_vt1 = "[VMF Package Manager] (%s): you can't use VMF package manager in VT1 " .. - "because VT1 mods don't support more than 1 resource package.", -- VMFMod:load_package: package_already_loaded = "[VMF Package Manager] (load_package): package '%s' has already been loaded.", package_not_found = "[VMF Package Manager] (load_package): could not find package '%s'.", @@ -41,21 +38,13 @@ local WARNINGS = { -- ##### Local functions ############################################################################################### -- ##################################################################################################################### -local function check_vt1(mod, function_name) - if VT1 then - mod:error(ERRORS.REGULAR.cant_use_vmf_package_manager_in_vt1, function_name) - return true - end -end - - -- Brings resources of the loaded package in game and executes callback. Or unloads package's resources, if loading was -- cancelled. local function flush_package(package_name) local package_data = _packages[package_name] if package_data.status == "loading_cancelled" then - Mod.release_resource_package(package_data.resource_package) + Application.release_resource_package(package_data.resource_package) _packages[package_name] = nil else package_data.resource_package:flush() @@ -88,8 +77,7 @@ end * sync [boolean] : (optional) load the packages synchronously, freezing the game until it is loaded --]] function VMFMod:load_package(package_name, callback, sync) - if check_vt1(self, "load_package") or - vmf.check_wrong_argument_type(self, "load_package", "package_name", package_name, "string") or + if vmf.check_wrong_argument_type(self, "load_package", "package_name", package_name, "string") or vmf.check_wrong_argument_type(self, "load_package", "callback", callback, "function", "nil") or vmf.check_wrong_argument_type(self, "load_package", "sync", sync, "boolean", "nil") then @@ -101,7 +89,7 @@ function VMFMod:load_package(package_name, callback, sync) return end - local resource_package = Mod.resource_package(self:get_internal_data("mod_handle"), package_name) + local resource_package = Application.resource_package(package_name) if not resource_package then self:error(ERRORS.REGULAR.package_not_found, package_name) return @@ -150,8 +138,7 @@ end * package_name [string]: package name. needs to be the full path to the `.package` file without the extension --]] function VMFMod:unload_package(package_name) - if check_vt1(self, "unload_package") or - vmf.check_wrong_argument_type(self, "unload_package", "package_name", package_name, "string") + if vmf.check_wrong_argument_type(self, "unload_package", "package_name", package_name, "string") then return end @@ -168,7 +155,7 @@ function VMFMod:unload_package(package_name) elseif package_status == "loading" then _packages[package_name].status = "loading_cancelled" elseif package_status == "loaded" then - Mod.release_resource_package(_packages[package_name].resource_package) + Application.release_resource_package(_packages[package_name].resource_package) _packages[package_name] = nil end end @@ -179,8 +166,7 @@ end * package_name [string]: package name. needs to be the full path to the `.package` file without the extension --]] function VMFMod:package_status(package_name) - if check_vt1(self, "package_status") or - vmf.check_wrong_argument_type(self, "package_status", "package_name", package_name, "string") + if vmf.check_wrong_argument_type(self, "package_status", "package_name", package_name, "string") then return end diff --git a/vmf/scripts/mods/vmf/vmf_loader.lua b/vmf/scripts/mods/vmf/vmf_loader.lua index b86dfa7..13f653e 100644 --- a/vmf/scripts/mods/vmf/vmf_loader.lua +++ b/vmf/scripts/mods/vmf/vmf_loader.lua @@ -1,48 +1,46 @@ local vmf -- Global variable indicating which version of the game is currently running -VT1 = not pcall(require, "PlayFab.json") +VT1 = false -- Native mod object used by Fatshark mod manager local vmf_mod_object = {} +-- Global method to load a file through iowith a return +local mod_dofile = Mods.file.dofile + -- ##################################################################################################################### -- ##### Initialization ################################################################################################ -- ##################################################################################################################### function vmf_mod_object:init() - dofile("scripts/mods/vmf/modules/vmf_mod_data") - dofile("scripts/mods/vmf/modules/vmf_mod_manager") - dofile("scripts/mods/vmf/modules/vmf_package_manager") - dofile("scripts/mods/vmf/modules/core/safe_calls") - dofile("scripts/mods/vmf/modules/core/events") - dofile("scripts/mods/vmf/modules/core/settings") - dofile("scripts/mods/vmf/modules/core/logging") - dofile("scripts/mods/vmf/modules/core/misc") - dofile("scripts/mods/vmf/modules/core/persistent_tables") - dofile("scripts/mods/vmf/modules/debug/dev_console") - dofile("scripts/mods/vmf/modules/debug/table_dump") - dofile("scripts/mods/vmf/modules/core/hooks") - dofile("scripts/mods/vmf/modules/core/toggling") - dofile("scripts/mods/vmf/modules/core/keybindings") - dofile("scripts/mods/vmf/modules/core/chat") - dofile("scripts/mods/vmf/modules/core/localization") - dofile("scripts/mods/vmf/modules/core/options") - dofile("scripts/mods/vmf/modules/legacy/options") - dofile("scripts/mods/vmf/modules/core/network") - dofile("scripts/mods/vmf/modules/core/commands") - dofile("scripts/mods/vmf/modules/gui/custom_textures") - dofile("scripts/mods/vmf/modules/gui/custom_views") - dofile("scripts/mods/vmf/modules/ui/chat/chat_actions") - dofile("scripts/mods/vmf/modules/ui/options/mod_options") - dofile("scripts/mods/vmf/modules/vmf_options") - - if VT1 then - dofile("scripts/mods/vmf/modules/core/mutators/mutators_manager") - dofile("scripts/mods/vmf/modules/ui/mutators/mutators_gui") - else - dofile("scripts/mods/vmf/modules/gui/custom_hud_components") - end + mod_dofile("dmf/scripts/mods/vmf/modules/vmf_mod_data") + mod_dofile("dmf/scripts/mods/vmf/modules/vmf_mod_manager") + --mod_dofile("dmf/scripts/mods/vmf/modules/vmf_dummy") + mod_dofile("dmf/scripts/mods/vmf/modules/vmf_package_manager") + mod_dofile("dmf/scripts/mods/vmf/modules/core/safe_calls") + mod_dofile("dmf/scripts/mods/vmf/modules/core/events") + mod_dofile("dmf/scripts/mods/vmf/modules/core/settings") + mod_dofile("dmf/scripts/mods/vmf/modules/core/logging") + mod_dofile("dmf/scripts/mods/vmf/modules/core/misc") + mod_dofile("dmf/scripts/mods/vmf/modules/core/persistent_tables") + mod_dofile("dmf/scripts/mods/vmf/modules/debug/dev_console") + mod_dofile("dmf/scripts/mods/vmf/modules/debug/table_dump") + mod_dofile("dmf/scripts/mods/vmf/modules/core/hooks") + mod_dofile("dmf/scripts/mods/vmf/modules/core/require") + mod_dofile("dmf/scripts/mods/vmf/modules/core/toggling") + mod_dofile("dmf/scripts/mods/vmf/modules/core/keybindings") + mod_dofile("dmf/scripts/mods/vmf/modules/core/chat") + mod_dofile("dmf/scripts/mods/vmf/modules/core/localization") + mod_dofile("dmf/scripts/mods/vmf/modules/core/options") + mod_dofile("dmf/scripts/mods/vmf/modules/core/network") + mod_dofile("dmf/scripts/mods/vmf/modules/core/commands") + mod_dofile("dmf/scripts/mods/vmf/modules/gui/custom_textures") + mod_dofile("dmf/scripts/mods/vmf/modules/gui/custom_views") + mod_dofile("dmf/scripts/mods/vmf/modules/ui/chat/chat_actions") + mod_dofile("dmf/scripts/mods/vmf/modules/ui/options/mod_options") + mod_dofile("dmf/scripts/mods/vmf/modules/vmf_options") + mod_dofile("dmf/scripts/mods/vmf/modules/core/mutators/mutators_manager") vmf = get_mod("VMF") vmf.delayed_chat_messages_hook() @@ -61,7 +59,6 @@ function vmf_mod_object:update(dt) vmf.mods_update_event(dt) vmf.check_keybinds() vmf.execute_queued_chat_command() - if VT1 then vmf.check_mutators_state() end if not vmf.all_mods_were_loaded and Managers.mod._state == "done" then @@ -70,9 +67,6 @@ function vmf_mod_object:update(dt) vmf.create_network_dictionary() vmf.ping_vmf_users() - if VT1 then vmf.modify_map_view() end - if VT1 then vmf.mutators_delete_raw_config() end - vmf.all_mods_loaded_event() vmf.all_mods_were_loaded = true @@ -84,32 +78,27 @@ function vmf_mod_object:on_unload() print("VMF:ON_UNLOAD()") vmf.save_chat_history() vmf.save_unsaved_settings_to_file() - vmf.network_unload() + vmf.destroy_command_gui() end function vmf_mod_object:on_reload() print("VMF:ON_RELOAD()") - vmf.disable_mods_options_button() - if VT1 then - vmf.reset_map_view() - else - vmf.remove_injected_hud_components() - end vmf.mods_unload_event(false) vmf.remove_custom_views() vmf.unload_all_resource_packages() vmf.hooks_unload() vmf.reset_guis() + vmf.destroy_command_gui() end function vmf_mod_object:on_game_state_changed(status, state) print("VMF:ON_GAME_STATE_CHANGED(), status: " .. tostring(status) .. ", state: " .. tostring(state)) - if VT1 then vmf.check_old_vmf() end vmf.mods_game_state_changed_event(status, state) vmf.save_unsaved_settings_to_file() vmf.apply_delayed_hooks(status, state) + vmf.destroy_command_gui() if status == "enter" and state == "StateIngame" then vmf.create_keybinds_input_service() diff --git a/vmf/settings.ini b/vmf/settings.ini deleted file mode 100644 index d66c0e0..0000000 --- a/vmf/settings.ini +++ /dev/null @@ -1 +0,0 @@ -boot_package = "resource_packages/vmf/vmf_resources" diff --git a/vmf/vmf.mod b/vmf/vmf.mod index 7b03a69..3111913 100644 --- a/vmf/vmf.mod +++ b/vmf/vmf.mod @@ -1,8 +1,5 @@ return { run = function() - return dofile("scripts/mods/vmf/vmf_loader") - end, - packages = { - "resource_packages/vmf" - } + return Mods.file.dofile("dmf/scripts/mods/vmf/vmf_loader") + end }