Initial rewrite for Darktide

This commit is contained in:
Aussiemon 2023-01-06 03:03:36 -07:00
parent 279169243f
commit 988ec944bc
65 changed files with 3788 additions and 7291 deletions

View file

@ -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"
]
}
}

Binary file not shown.

View file

@ -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
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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"];

View file

@ -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;

View file

@ -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

View file

@ -1 +0,0 @@
valid_tags = {}

View file

@ -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, },
},
}

View file

@ -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 = {
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

View file

@ -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"
]

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 <text_id> 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)

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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
}

View file

@ -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 ########################################################################################################

View file

@ -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")

View file

@ -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
}

View file

@ -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"},
},
})

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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 ########################################################################################################
-- #####################################################################################################################

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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<number,string] (required) : Array of visibility group names for the component to be
included in. "alive" is most common.
** use_hud_scale [boolean] (optional) : Set to 'true' if ingame_hud should scale the component.
** validation_function [function] (optional) : Function called by ingame_hud to determine whether to
create the component.
Return 'true' to enable.
Set to nil to always enable.
--]]
function VMFMod:register_hud_component(component_settings)
if vmf.check_wrong_argument_type(self, "register_hud_component", "component_data", component_settings, "table") then
return
end
component_settings = table.clone(component_settings)
local component_name = component_settings.class_name
if not vmf.safe_call_nrc(self,
{
ERRORS.PREFIX.component_validation,
component_name
},
validate_component_data,
component_settings
) then
return
end
_components_data[component_name] = {
mod = self,
component_settings = component_settings,
status = COMPONENT_STATUS.REGISTERED
}
if _ingame_hud then
if not vmf.safe_call_nrc(self,
{
ERRORS.PREFIX.component_injection,
component_name
},
inject_hud_component,
component_name
) then
_components_data[component_name] = nil
return
end
end
return true
end
-- #####################################################################################################################
-- ##### Hooks #########################################################################################################
-- #####################################################################################################################
vmf:hook_safe(IngameHud, "_setup_components", function(self)
_ingame_hud = self
for component_name, _ in pairs(_components_data) do
if not vmf.safe_call_nrc(self,
{
ERRORS.PREFIX.ingamehud_hook_injection,
component_name
},
inject_hud_component,
component_name
) then
_components_data[component_name] = nil
end
end
end)
vmf:hook_safe(IngameHud, "destroy", function()
_ingame_hud = nil
-- HUD components are reset every time the party is changed (including the initial local-player-only lobby)
-- We need to reset injection status as well.
reset_component_status()
end)
-- #####################################################################################################################
-- ##### VMF internal functions and variables ##########################################################################
-- #####################################################################################################################
function vmf.inject_hud_components(mod)
if _ingame_hud then
local components_to_inject = get_mod_hud_components(mod)
for component_name, component_data in pairs(components_to_inject) do
if not vmf.safe_call_nrc(mod,
{
ERRORS.PREFIX.component_injection,
component_name
},
inject_hud_component,
component_name
) then
_components_data[component_name] = nil
end
end
end
end
function vmf.remove_injected_hud_components(mod)
if _ingame_hud then
remove_injected_hud_components(mod)
end
end
-- #####################################################################################################################
-- ##### Script ########################################################################################################
-- #####################################################################################################################
-- If VMF is reloaded mid-game, get ingame_hud.
_ingame_hud = Managers.ui and Managers.ui._ingame_ui.ingame_hud

View file

@ -1,49 +1,13 @@
local vmf = get_mod("VMF")
local _ui_renderers = vmf:persistent_table("_ui_renderers")
local _custom_none_atlas_textures = {}
local _custom_ui_atlas_settings = {}
local _injected_materials = {}
local _show_debug_info = false
-- ####################################################################################################################
-- ##### Local functions ##############################################################################################
-- ####################################################################################################################
local original_gasbtn_function = UIAtlasHelper.get_atlas_settings_by_texture_name
local function check_texture_availability(mod, texture_name)
local texture_exists, texture_settings = pcall(original_gasbtn_function, texture_name)
if texture_exists then
if type(texture_settings) == "nil" then
mod:error("(custom texture/atlas): texture name '%s' is already used by Fatshark in 'none_atlas_textures'",
texture_name)
else
mod:error("(custom texture/atlas): texture name '%s' is already used by Fatshark in atlas '%s'",
texture_name, tostring(texture_settings.material_name))
end
return false
end
if _custom_none_atlas_textures[texture_name] then
mod:error("(custom texture/atlas): texture name '%s' is already used by the mod '%s' as none atlas texture",
texture_name, _custom_none_atlas_textures[texture_name])
return false
end
if _custom_ui_atlas_settings[texture_name] then
texture_settings = _custom_ui_atlas_settings[texture_name]
mod:error("(custom texture/atlas): texture name '%s' is already used by the mod '%s' in atlas '%s'",
texture_name, texture_settings.mod_name, tostring(texture_settings.material_name))
return false
end
return true
-- @TODO: Method for checking texture availability
end
-- ####################################################################################################################
@ -51,222 +15,21 @@ end
-- ####################################################################################################################
vmf.custom_textures = function (mod, ...)
for i, texture_name in ipairs({...}) do
if type(texture_name) == "string" then
if check_texture_availability(mod, texture_name) then
_custom_none_atlas_textures[texture_name] = mod:get_name()
end
else
mod:error("(custom_textures): all arguments should have the string type, but the argument #%s is %s",
i, type(texture_name))
end
end
-- @TODO: Not implemented
end
vmf.custom_atlas = function (mod, material_settings_file, material_name, masked_material_name,
point_sample_material_name, masked_point_sample_material_name,
saturated_material_name)
if vmf.check_wrong_argument_type(mod, "custom_atlas", "material_settings_file",
material_settings_file, "string") or
vmf.check_wrong_argument_type(mod, "custom_atlas", "material_name",
material_name, "string", "nil") or
vmf.check_wrong_argument_type(mod, "custom_atlas", "masked_material_name",
masked_material_name, "string", "nil") or
vmf.check_wrong_argument_type(mod, "custom_atlas", "point_sample_material_name",
point_sample_material_name, "string", "nil") or
vmf.check_wrong_argument_type(mod, "custom_atlas", "masked_point_sample_material_name",
masked_point_sample_material_name, "string", "nil") or
vmf.check_wrong_argument_type(mod, "custom_atlas", "saturated_material_name",
saturated_material_name, "string", "nil") then
return
end
local material_settings = mod:dofile(material_settings_file)
if material_settings then
local mod_name = mod:get_name()
for texture_name, texture_settings in pairs(material_settings) do
if check_texture_availability(mod, texture_name) then
texture_settings.mod_name = mod_name
texture_settings.material_name = material_name
texture_settings.masked_material_name = masked_material_name
texture_settings.point_sample_material_name = point_sample_material_name
texture_settings.masked_point_sample_material_name = masked_point_sample_material_name
texture_settings.saturated_material_name = saturated_material_name
_custom_ui_atlas_settings[texture_name] = texture_settings
end
end
else
mod:error("(custom_atlas): can't load 'material_settings'")
end
vmf.custom_atlas = function (mod, ...)
-- @TODO: Not implemented
end
vmf.inject_materials = function (mod, ui_renderer_creator, ...)
if vmf.check_wrong_argument_type(mod, "inject_materials", "ui_renderer_creator", ui_renderer_creator, "string") then
return
end
local injected_materials_list = _injected_materials[ui_renderer_creator] or {}
local can_inject
for i, new_injected_material in ipairs({...}) do
if type(new_injected_material) == "string" then
can_inject = true
-- check if injected_materials_list already contains current material
for _, injected_material in ipairs(injected_materials_list) do
if new_injected_material == injected_material then
can_inject = false
break
end
end
if can_inject then
table.insert(injected_materials_list, new_injected_material)
end
else
mod:error("(inject_materials): all arguments should have the string type, but the argument #%s is %s",
i + 1, type(new_injected_material) )
end
end
_injected_materials[ui_renderer_creator] = injected_materials_list
-- recreate GUIs with injected materials for ui_renderers created by 'ui_renderer_creator'
local vmf_data
for ui_renderer, _ in pairs(_ui_renderers) do
vmf_data = rawget(ui_renderer, "vmf_data")
if vmf_data.ui_renderer_creator == ui_renderer_creator then
local new_materials_list = table.clone(vmf_data.original_materials)
for _, injected_material in ipairs(injected_materials_list) do
table.insert(new_materials_list, "material")
table.insert(new_materials_list, injected_material)
end
World.destroy_gui(ui_renderer.world, ui_renderer.gui)
World.destroy_gui(ui_renderer.world, ui_renderer.gui_retained)
ui_renderer.gui = World.create_screen_gui(ui_renderer.world, "immediate", unpack(new_materials_list))
ui_renderer.gui_retained = World.create_screen_gui(ui_renderer.world, unpack(new_materials_list))
vmf_data.is_modified = true
end
end
vmf.inject_materials = function (mod, ...)
-- @TODO: Not implemented
end
-- ####################################################################################################################
-- ##### Hooks ########################################################################################################
-- ####################################################################################################################
local LUA_SCRIPT_CALLER_POSITION = 4
vmf:hook(UIRenderer, "create", function(func, world, ...)
local is_modified = false
-- FINDING OUT WHO CREATED UI_RENDERER
local ui_renderer_creator = nil
local callstack = debug.traceback()
-- get the name of lua script which called 'UIRenderer.create'
-- it's the 4th string of the 'debug.traceback()' output
local i = 0
for s in callstack:gmatch("(.-)\n") do
i = i + 1
if i == LUA_SCRIPT_CALLER_POSITION then
ui_renderer_creator = s:match("([^%/]+)%.lua:")
break
end
end
if not ui_renderer_creator then
print(Script.callstack())
vmf:error("(UIRenderer.create): ui_renderer_creator not found. You're never supposed to see this message. " ..
"If you see this, please, save the game log and report about this incident to the VMF team.")
return func(world, ...)
end
-- CREATING THE LIST OF TEXTURES FOR THE NEW UI_RENDERER
local ui_renderer_materials = {...}
if _injected_materials[ui_renderer_creator] then
for _, injected_material in ipairs(_injected_materials[ui_renderer_creator]) do
table.insert(ui_renderer_materials, "material")
table.insert(ui_renderer_materials, injected_material)
end
is_modified = true
end
-- DEBUG INFO
if _show_debug_info then
vmf:info("UI_RENDERER CREATED BY:")
vmf:info(" %s", ui_renderer_creator)
vmf:info("UI_RENDERER MATERIALS:")
for n, material in ipairs(ui_renderer_materials) do
vmf:info(" [%s]: %s:", n, material)
end
end
-- CREATING THE NEW UI_RENDERER AND SAVING SOME DATA INSIDE OF IT
local ui_renderer = func(world, unpack(ui_renderer_materials))
_ui_renderers[ui_renderer] = true
local vmf_data = {}
vmf_data.original_materials = {...}
vmf_data.ui_renderer_creator = ui_renderer_creator
vmf_data.is_modified = is_modified
rawset(ui_renderer, "vmf_data", vmf_data)
return ui_renderer
end)
vmf:hook_safe(UIRenderer, "destroy", function(self)
_ui_renderers[self] = nil
end)
vmf:hook(UIAtlasHelper, "has_atlas_settings_by_texture_name", function(func, texture_name, ...)
if _custom_ui_atlas_settings[texture_name] then
return true
end
return func(texture_name, ...)
end)
vmf:hook(UIAtlasHelper, "get_atlas_settings_by_texture_name", function(func, texture_name, ...)
if _custom_none_atlas_textures[texture_name] then
return
end
if _custom_ui_atlas_settings[texture_name] then
return _custom_ui_atlas_settings[texture_name]
end
return func(texture_name, ...)
end)
-- ####################################################################################################################
-- ##### VMF internal functions and variables #########################################################################
-- ####################################################################################################################
@ -276,16 +39,7 @@ vmf.load_custom_textures_settings = function()
end
vmf.reset_guis = function()
for ui_renderer, _ in pairs(_ui_renderers) do
local vmf_data = rawget(ui_renderer, "vmf_data")
if vmf_data.is_modified then
World.destroy_gui(ui_renderer.world, ui_renderer.gui)
World.destroy_gui(ui_renderer.world, ui_renderer.gui_retained)
ui_renderer.gui = World.create_screen_gui(ui_renderer.world, "immediate", unpack(vmf_data.original_materials))
ui_renderer.gui_retained = World.create_screen_gui(ui_renderer.world, unpack(vmf_data.original_materials))
vmf_data.is_modified = false
end
end
-- @TODO: Method to reset VMF-spawned guis
end
-- ####################################################################################################################

View file

@ -1,10 +1,10 @@
local vmf = get_mod("VMF")
local _custom_view_data = vmf:persistent_table("custom_view_data")
local _ingame_ui
local _ingame_ui_disabled
-- There's no direct access to local variable 'transitions' in ingame_ui.
local _ingame_ui_transitions = require("scripts/ui/views/ingame_ui_settings").transitions
local _views_data = {}
local _loaded_views = {}
local ERRORS = {
THROWABLE = {
@ -57,10 +57,8 @@ local ERRORS = {
-- #####################################################################################################################
local function is_view_active_for_current_level(view_name)
local active = _views_data[view_name].view_settings.active
if _ingame_ui.is_in_inn and active.inn or not _ingame_ui.is_in_inn and active.ingame then
return true
end
-- @TODO: Add active setting per mechanism type
return true
end
@ -74,76 +72,60 @@ local function inject_view(view_name)
local mod = _views_data[view_name].mod
local init_view_function = view_settings.init_view_function
local transitions = _views_data[view_name].view_transitions
local blocked_transitions = view_settings.blocked_transitions[_ingame_ui.is_in_inn and "inn" or "ingame"]
-- Check for collisions.
if _ingame_ui.views[view_name] then
vmf.throw_error(ERRORS.THROWABLE.view_already_exists, view_name)
end
for transition_name, _ in pairs(transitions) do
if _ingame_ui_transitions[transition_name] then
vmf.throw_error(ERRORS.THROWABLE.transition_already_exists, transition_name)
end
end
-- Check for collisions. @TODO: Check for collisions by mod
--if _ingame_ui._view_list[view_name] then
-- vmf.throw_error(ERRORS.THROWABLE.view_already_exists, view_name)
--end
--for transition_name, _ in pairs(transitions) do
-- if _ingame_ui_transitions[transition_name] then
-- vmf.throw_error(ERRORS.THROWABLE.transition_already_exists, transition_name)
-- end
--end
-- Initialize and inject view.
local success, view = vmf.safe_call(mod, ERRORS.PREFIX.view_initializing, init_view_function,
_ingame_ui.ingame_ui_context)
local success = vmf.safe_call(mod, ERRORS.PREFIX.view_initializing, init_view_function,
view_settings, {})
if success then
_ingame_ui.views[view_name] = view
_ingame_ui._view_list[view_name] = view_settings
else
vmf.throw_error(ERRORS.THROWABLE.view_initializing_failed)
end
-- Inject view transitions.
for transition_name, transition_function in pairs(transitions) do
_ingame_ui_transitions[transition_name] = transition_function
end
--for transition_name, transition_function in pairs(transitions) do
-- _ingame_ui_transitions[transition_name] = transition_function
--end
-- Inject view blocked transitions.
for blocked_transition_name, _ in pairs(blocked_transitions) do
_ingame_ui.blocked_transitions[blocked_transition_name] = true
end
--for blocked_transition_name, _ in pairs(blocked_transitions) do
-- _ingame_ui.blocked_transitions[blocked_transition_name] = true
--end
end
local function remove_injected_views(on_reload)
-- These elements should be removed only on_reload, because, otherwise, they will be deleted automatically.
if on_reload then
-- If some custom view is active, safely close it.
if _views_data[_ingame_ui.current_view] then
-- Hack to ensure cursor stack safety.
ShowCursorStack.stack_depth = ShowCursorStack.stack_depth + 1
_ingame_ui:handle_transition("exit_menu")
ShowCursorStack.stack_depth = 1
ShowCursorStack.pop()
end
for view_name, view_data in pairs(_views_data) do
for view_name, _ in pairs(_views_data) do
-- Remove injected views.
local view = _ingame_ui.views[view_name]
if view then
if type(view.destroy) == "function" then
vmf.safe_call_nr(view_data.mod, {ERRORS.PREFIX.view_destroying, view_name}, view.destroy, view)
end
_ingame_ui.views[view_name] = nil
end
_ingame_ui._view_list[view_name] = nil
end
end
for _, view_data in pairs(_views_data) do
--for _, view_data in pairs(_views_data) do
-- Remove injected transitions.
for transition_name, _ in pairs(view_data.view_transitions) do
_ingame_ui_transitions[transition_name] = nil
end
-- for transition_name, _ in pairs(view_data.view_transitions) do
-- _ingame_ui_transitions[transition_name] = nil
-- end
-- Remove blocked transitions
local blocked_transitions = view_data.view_settings.blocked_transitions[_ingame_ui.is_in_inn and "inn" or "ingame"]
for blocked_transition_name, _ in pairs(blocked_transitions) do
_ingame_ui.blocked_transitions[blocked_transition_name] = nil
end
end
-- local blocked_transitions = view_data.view_settings.blocked_transitions[_ingame_ui.is_in_inn and "inn" or "ingame"]
-- for blocked_transition_name, _ in pairs(blocked_transitions) do
-- _ingame_ui.blocked_transitions[blocked_transition_name] = nil
-- end
--end
end
@ -189,48 +171,90 @@ local function validate_view_data(view_data)
vmf.throw_error(ERRORS.THROWABLE.init_view_function_wrong_type, type(view_settings.init_view_function))
end
-- Verify active if present
local active = view_settings.active
if type(active) ~= "table" then
vmf.throw_error(ERRORS.THROWABLE.active_wrong_type, type(active))
end
if active.inn == nil or active.ingame == nil then
vmf.throw_error(ERRORS.THROWABLE.active_missing_element)
end
for level_name, value in pairs(active) do
if level_name ~= "inn" and level_name ~= "ingame" then
vmf.throw_error(ERRORS.THROWABLE.active_element_wrong_name, level_name)
if active then
if type(active) ~= "table" then
vmf.throw_error(ERRORS.THROWABLE.active_wrong_type, type(active))
end
if type(value) ~= "boolean" then
vmf.throw_error(ERRORS.THROWABLE.active_element_wrong_type, level_name, type(value))
if active.inn == nil or active.ingame == nil then
vmf.throw_error(ERRORS.THROWABLE.active_missing_element)
end
for level_name, value in pairs(active) do
if level_name ~= "inn" and level_name ~= "ingame" then
vmf.throw_error(ERRORS.THROWABLE.active_element_wrong_name, level_name)
end
if type(value) ~= "boolean" then
vmf.throw_error(ERRORS.THROWABLE.active_element_wrong_type, level_name, type(value))
end
end
end
-- Verify blocked transitions if present
local blocked_transitions = view_settings.blocked_transitions
if type(blocked_transitions) ~= "table" then
vmf.throw_error(ERRORS.THROWABLE.blocked_transitions_wrong_type, type(blocked_transitions))
end
if not blocked_transitions.inn or not blocked_transitions.ingame then
vmf.throw_error(ERRORS.THROWABLE.blocked_transitions_missing_element)
end
for level_name, level_blocked_transitions in pairs(blocked_transitions) do
if level_name ~= "inn" and level_name ~= "ingame" then
vmf.throw_error(ERRORS.THROWABLE.blocked_transitions_element_wrong_name, level_name)
if blocked_transitions then
if type(blocked_transitions) ~= "table" then
vmf.throw_error(ERRORS.THROWABLE.blocked_transitions_wrong_type, type(blocked_transitions))
end
if type(level_blocked_transitions) ~= "table" then
vmf.throw_error(ERRORS.THROWABLE.blocked_transitions_element_wrong_type, level_name,
type(level_blocked_transitions))
if not blocked_transitions.inn or not blocked_transitions.ingame then
vmf.throw_error(ERRORS.THROWABLE.blocked_transitions_missing_element)
end
for transition_name, value in pairs(level_blocked_transitions) do
if not view_transitions[transition_name] then
vmf.throw_error(ERRORS.THROWABLE.blocked_transition_invalid, transition_name, level_name)
for level_name, level_blocked_transitions in pairs(blocked_transitions) do
if level_name ~= "inn" and level_name ~= "ingame" then
vmf.throw_error(ERRORS.THROWABLE.blocked_transitions_element_wrong_name, level_name)
end
if value ~= true then
vmf.throw_error(ERRORS.THROWABLE.blocked_transition_wrong_value, level_name, transition_name)
if type(level_blocked_transitions) ~= "table" then
vmf.throw_error(ERRORS.THROWABLE.blocked_transitions_element_wrong_type, level_name,
type(level_blocked_transitions))
end
for transition_name, value in pairs(level_blocked_transitions) do
if not view_transitions[transition_name] then
vmf.throw_error(ERRORS.THROWABLE.blocked_transition_invalid, transition_name, level_name)
end
if value ~= true then
vmf.throw_error(ERRORS.THROWABLE.blocked_transition_wrong_value, level_name, transition_name)
end
end
end
end
end
-- Checks:
-- * View registered
-- * View has settings
-- * View is either loaded or configured to load on call
-- View settings only apply when the app has switched to the view loader.
local function check_load_status(view_name)
local view_settings = _views_data[view_name] and _views_data[view_name].view_settings
return view_settings and _loaded_views[view_name] or
(_custom_view_data.loader_initialized and
(view_settings.load_always or
view_settings.is_hub and view_settings.load_in_hub))
end
-- Checks:
-- * View registered
-- * View is loaded/loadable
-- * View is not already active
-- * View is not in the middle of closing
local function can_open_view(view_name)
if _ingame_ui then
if
_views_data[view_name] and
_custom_view_data.loader_initialized and
not Managers.ui:view_active(view_name) and
not Managers.ui:is_view_closing(view_name)
then
return true
end
end
return false
end
-- #####################################################################################################################
-- ##### VMFMod ########################################################################################################
-- #####################################################################################################################
@ -243,46 +267,8 @@ end
* transition_params [anything]: parameter, which will be passed to callable transition function, 'on_exit' method of
the old view and 'on_enter' method of the new view
--]]
function VMFMod:handle_transition(transition_name, ignore_active_menu, fade, transition_params)
if vmf.check_wrong_argument_type(self, "handle_transition", "transition_name", transition_name, "string") then
return
end
local vt2_player_list_active
if not VT1 then
local ingame_player_list_ui = _ingame_ui.ingame_hud:component("IngamePlayerListUI")
vt2_player_list_active = ingame_player_list_ui and ingame_player_list_ui:is_active()
end
if _ingame_ui
and not _ingame_ui_disabled
and not _ingame_ui:pending_transition()
and not _ingame_ui:end_screen_active()
and (not _ingame_ui.menu_active or ignore_active_menu)
and not _ingame_ui.leave_game
and not _ingame_ui.return_to_title_screen
and (
VT1
and not _ingame_ui.menu_suspended
and not _ingame_ui.popup_join_lobby_handler.visible
or not VT1
and not Managers.transition:in_fade_active()
and not _ingame_ui:cutscene_active()
and not _ingame_ui:unavailable_hero_popup_active()
and (not vt2_player_list_active or ignore_active_menu)
)
then
if fade then
vmf.safe_call_nr(self, {ERRORS.PREFIX.handle_transition_fade, transition_name}, _ingame_ui.transition_with_fade,
_ingame_ui, transition_name,
transition_params)
else
vmf.safe_call_nr(self, {ERRORS.PREFIX.handle_transition_no_fade, transition_name}, _ingame_ui.handle_transition,
_ingame_ui, transition_name,
transition_params)
end
return true
end
function VMFMod:handle_transition()
return true
end
@ -298,6 +284,11 @@ function VMFMod:register_view(view_data)
view_data = table.clone(view_data)
local view_name = view_data.view_name
view_data.view_settings.name = view_name
if view_data.view_settings.close_on_hotkey_pressed == nil then
view_data.view_settings.close_on_hotkey_pressed = true
end
if not vmf.safe_call_nrc(self, {ERRORS.PREFIX.register_view_validation, view_name}, validate_view_data,
view_data) then
@ -307,7 +298,8 @@ function VMFMod:register_view(view_data)
_views_data[view_name] = {
mod = self,
view_settings = view_data.view_settings,
view_transitions = view_data.view_transitions
view_transitions = view_data.view_transitions,
view_options = view_data.view_options,
}
if _ingame_ui then
@ -323,7 +315,34 @@ end
-- ##### Hooks #########################################################################################################
-- #####################################################################################################################
vmf:hook_safe(IngameUI, "init", function(self)
-- Track the creation of the view loader
vmf:hook_safe(ViewLoader, "init", function()
_custom_view_data.loader_initialized = true
end)
-- Track the deletion of the view loader
vmf:hook_safe(ViewLoader, "destroy", function()
_custom_view_data.loader_initialized = false
end)
-- Track the loading of views, set the loader flag if class selection is reached
vmf:hook_safe(UIManager, "load_view", function(self, view_name)
if view_name == "class_selection_view" then
_custom_view_data.loader_initialized = true
end
_loaded_views[view_name] = true
end)
-- Track the unloading of views
vmf:hook_safe(UIManager, "unload_view", function(self, view_name)
_loaded_views[view_name] = nil
end)
-- Store the view handler for later use and inject views
vmf:hook_safe(UIViewHandler, "init", function(self)
_ingame_ui = self
for view_name, _ in pairs(_views_data) do
if not vmf.safe_call_nrc(self, {ERRORS.PREFIX.ingameui_hook_injection, view_name}, inject_view, view_name) then
@ -332,17 +351,6 @@ vmf:hook_safe(IngameUI, "init", function(self)
end
end)
vmf:hook_safe(IngameUI, "destroy", function()
remove_injected_views(false)
_ingame_ui = nil
end)
vmf:hook_safe(IngameUI, "update", function(self, dt_, t_, disable_ingame_ui)
_ingame_ui_disabled = disable_ingame_ui
end)
-- #####################################################################################################################
-- ##### VMF internal functions and variables ##########################################################################
-- #####################################################################################################################
@ -356,7 +364,8 @@ end
-- Opens/closes a view if all conditions are met. Since keybinds module can't do UI-related checks, all the cheks are
-- done in this function. This function is called every time some view-toggling keybind is pressed.
function vmf.keybind_toggle_view(mod, view_name, keybind_transition_data, can_be_opened, is_keybind_pressed)
function vmf.keybind_toggle_view(mod, view_name, keybind_transition_data, can_perform_action, is_keybind_pressed)
--[[
if _ingame_ui then
local view_data = _views_data[view_name]
if not view_data or (view_data.mod ~= mod) then
@ -377,7 +386,7 @@ function vmf.keybind_toggle_view(mod, view_name, keybind_transition_data, can_be
end
end
-- Can open views only when keybind is pressed.
elseif can_be_opened and is_keybind_pressed then
elseif can_perform_action and is_keybind_pressed then
if keybind_transition_data.open_view_transition_name then
if view_data.view_transitions[keybind_transition_data.open_view_transition_name] then
mod:handle_transition(keybind_transition_data.open_view_transition_name, true,
@ -391,6 +400,56 @@ function vmf.keybind_toggle_view(mod, view_name, keybind_transition_data, can_be
end
end
end
--]]
if _ingame_ui then
-- Check that the view is registered
local view_data = _views_data[view_name]
if not view_data or (view_data.mod ~= mod) then
mod:error(ERRORS.REGULAR.view_not_registered, view_name)
return
end
-- If the view is open, this is a toggle close
if Managers.ui:view_active(view_name) then
-- Don't close the view if it's already closing
if not Managers.ui:is_view_closing(view_name) then
local force_close = true
Managers.ui:close_view(view_name, force_close)
end
-- Otherwise, this is a toggle open
elseif can_perform_action and is_keybind_pressed then
local validation_function = view_data.view_settings.validation_function
local can_open_and_validated = can_open_view(view_name) and (not validation_function or validation_function())
-- Checks for inactive, not closing, no other open view, loaded/loadable, and validation
if not can_open_and_validated then
return
end
local view_options = view_data.view_options
local close_all = view_options and view_options.close_all or false
local close_previous = view_options and view_options.close_previous or false
local close_transition_time = view_options and view_options.close_transition_time or nil
local transition_time = view_options and view_options.transition_time or nil
local view_context = {}
local use_transition_ui = view_data.view_settings.use_transition_ui
local no_transition_ui = use_transition_ui == false
local view_settings_override = no_transition_ui and {
use_transition_ui = false
}
-- Open the view with default parameters
Managers.ui:open_view(view_name, transition_time, close_previous,
close_all, close_transition_time, view_context, view_settings_override)
end
end
end
-- #####################################################################################################################
@ -398,4 +457,4 @@ end
-- #####################################################################################################################
-- If VMF is reloaded mid-game, get ingame_ui.
_ingame_ui = (VT1 and Managers.matchmaking and Managers.matchmaking.ingame_ui) or (Managers.ui and Managers.ui._ingame_ui)
_ingame_ui = Managers.ui and Managers.ui._view_handler

View file

@ -1,273 +0,0 @@
local vmf = get_mod("VMF")
-- Legacy definitions can't be stripped because it will break following mods.
-- Warning for these mods is not shown and their authors were notified about the update.
-- However, new mods should not use legacy definitions.
local LEGACY_MODS_VT2 = {
["crosshairs"] = true, -- Crosshairs Fix (by Skwuruhl)
["BuffInfo"] = true, -- Buff Info (by 🔰SkacikPL🗾)
["Spooktober"] = true, -- Spooktober (by 🔰SkacikPL🗾)
["fly"] = true, -- Aussiemon's Free Flight Mod (by tour.dlang.org/)
["Console"] = true, -- Console (by 🔰SkacikPL🗾)
["lootRatAmmo"] = true, -- Sack Rat drops ammo (by NonzeroGeoduck7)
["ff_notifier"] = true, -- The Not-So-Friendly-Fire Snitch (by Zaphio)
["NoGlow"] = true, -- No Glow On Unique Weapons (by prop joe)
["StreamingInfo"] = true, -- Info Dump For Streaming (by prop joe)
["keyPickupMessage"] = true, -- Notice Key Pickup (by NonzeroGeoduck7)
["oldTorch"] = true, -- Torch is not a weapon (by NonzeroGeoduck7)
["color"] = true, -- Colorful Unique Weapons (by tour.dlang.org/)
["zoom sens"] = true, -- Customizable Zoom Sensitivity (by Skwuruhl)
["item_filter"] = true, -- Item Filter (by Gohas)
["QuickGameMapSelect"] = true, -- Quick Play - select maps in random order (by NonzeroGeoduck7)
["deedNoPickup"] = true, -- Deeds - Tomes / Grims in No-Pickup Mutation (by NonzeroGeoduck7)
["hostQuickPlay"] = true, -- Host Solo Quick Play Games (by NonzeroGeoduck7)
["RerollImprovements"] = true, -- Reroll Improvements (by prop joe)
["bots_impulse_control"] = true, -- Bot Improvements - Impulse Control (by Squatting Bear)
["Headshot Only"] = true, -- Headshot Only Mode (by Gohas)
["convenience_key_actions"] = true, -- Convenience Key Actions (by Squatting Bear)
["Stances"] = true, -- Stances (by 🔰SkacikPL🗾)
["bloodyWeapons"] = true, -- Blood for the blood god (by ElCamino)
["HideBuffs"] = true, -- UI Tweaks (by prop joe)
["Pause"] = true, -- Pause (by prop joe)
["armory"] = true, -- Armory (by Fracticality)
["InstaKick"] = true, -- Instant Kick (by NonzeroGeoduck7)
["AimLines"] = true, -- Aim Lines (by 🔰SkacikPL🗾)
["ChatWheel"] = true, -- Chat Wheel (by NonzeroGeoduck7)
["traps"] = true, -- Traps (by 🔰SkacikPL🗾)
["BossTimer"] = true, -- BossKillTimer (by NonzeroGeoduck7)
["Parry Indicator"] = true, -- Parry Indicator (by Gohas)
["MMONames"] = true, -- MMO Names (by 🔰SkacikPL🗾)
["lumberfoots"] = true, -- Handmaiden has a limited vocabulary (by raindish)
["NoWobble"] = true, -- No Wobble (by 🔰SkacikPL🗾)
["Weapon Zoom"] = true, -- Weapon Zoom (by Fracticality)
["RTAT"] = true, -- Game Speed Changer (by Seelixh 🐥🦆)
["loadout_manager_vt2"] = true, -- Loadout Manager (by Squatting Bear)
["toggle-crits"] = true, -- Toggle Crits (by Orange Chris)
["VermintideReloaded"] = true, -- Vermintide: Reloaded (by Alone and Afraid)
["Instagib"] = true, -- Instagib (by Seelixh 🐥🦆)
["fb"] = true, -- Fortress Brawl + Melee Friendly Fire (by tour.dlang.org/)
["SPF"] = true, -- Single Player Framework (by 🔰SkacikPL🗾)
["disco"] = true, -- Disco Lights! (by tour.dlang.org/)
["Characters"] = true, -- Any Character! (by tour.dlang.org/)
["Bestiary"] = true, -- Bestiary (by Fracticality)
["Fleeting time"] = true, -- Fleeting time (by th-om)
["gm"] = true, -- Red Shell Disabler + God Mode (by tour.dlang.org/)
["GiveWeapon"] = true, -- Give Weapon (by prop joe)
["MoreItemsLibrary"] = true, -- More Items Library (by Aussiemon)
["Mission timer"] = true, -- Mission timer (by th-om)
["feigndeath"] = true, -- Feign Death (by 🔰SkacikPL🗾)
["UltReset"] = true, -- Ready Ult (by prop joe)
["NerfBats"] = true, -- Nerf Bats (by dragonman68)
["perfectdark"] = true, -- Perfect Dark (by 🔰SkacikPL🗾)
["Extra-Sensory Deprivation"] = true, -- Extra-Sensory Deprivation (by Dwarf3d)
["NoUltCooldown"] = true, -- No Ult Cooldown (by ThePageMan)
["BoltStaffTweaks"] = true, -- Bolt Staff Tweaks (by dragonman68)
["preparations"] = true, -- Adaptation (Access inventory in match) (by 🔰SkacikPL🗾)
["Less Annoying Friendly Fire"] = true, -- Less Annoying Friendly Fire (by pixaal)
["RestartLevelCommand"] = true, -- Restart Level Command (by prop joe)
["D-Lang"] = true, -- D-Lang (by tour.dlang.org/)
["Larger Hordes"] = true, -- Larger Hordes (by \҉/̵i͏cto̡r͘ S̛al͡t̕zpy̵r͏e̢)
["Barrels"] = true, -- Barrels Spawns For The Memes! aka the Barrel Meta (by tour.dlang.org/)
["Fail Level Hotkey"] = true, -- Restart Level or Return to Keep Hotkeys (by \҉/̵i͏cto̡r͘ S̛al͡t̕zpy̵r͏e̢)
["Needii"] = true, -- Needii (by Tomoko 👿)
["LockedAndLoaded"] = true, -- LockedAndLoaded (by Badwin)
["StickyGrim"] = true, -- StickyGrim (by Badwin)
["Dofile"] = true, -- Execute External Lua File (by prop joe)
["SoundEventMonitor"] = true, -- [Tool] Sound Event Monitor (by Aussiemon)
["E308TestMod"] = true, -- Ammo Decimal Leftovers (by Sgt. Buttersworth [E-308])
["Waypoints"] = true, -- Waypoints (by Badwin)
["BotImprovements_HeroSelection"] = true, -- Bot Improvements - Hero Selection (by Grimalackt)
["NumericUI"] = true, -- Numeric UI (by Necrossin)
["BotImprovements_Combat"] = true, -- Bot Improvements - Combat (by Grimalackt)
["CreatureSpawner"] = true, -- Creature Spawner (by Aussiemon)
["SpawnTweaks"] = true, -- Spawn Tweaks (by prop joe)
["MutatorsSelector"] = true, -- Mutators Selector (by prop joe)
["FailLevelCommand"] = true, -- Fail/Win/Restart Level Command (by prop joe)
["ItemSpawner"] = true, -- Item Spawner (by prop joe)
["ui_improvements"] = true, -- UI Improvements (by grasmann)
["SkipCutscenes"] = true, -- Skip Cutscenes (by Aussiemon)
["HeatIndicator"] = true, -- Heat Indicator (by grasmann)
["Healthbars"] = true, -- Healthbars (by grasmann)
["ChatBlock"] = true, -- Chat Block (by grasmann)
["ShowDamage"] = true, -- Show Damage (by grasmann)
["Killbots"] = true, -- Killbots (by prop joe)
["CustomHUD"] = true, -- Custom HUD (by prop joe)
["ThirdPersonEquipment"] = true, -- Third Person Equipment (by grasmann)
["CrosshairCustomization"] = true, -- Crosshair Customization (by prop joe)
["TrueSoloQoL"] = true, -- True Solo QoL Tweaks (by prop joe)
["NeuterUltEffects"] = true, -- Neuter Ult Effects (by prop joe)
["PositiveReinforcementTweaks"] = true, -- Killfeed Tweaks (by prop joe)
["ThirdPerson"] = true, -- Third Person (by grasmann)
}
vmf.initialize_mod_options_legacy = function (mod, widgets_definition)
if VT1 or LEGACY_MODS_VT2[mod:get_name()] then
mod:info("Using deprecated widget definitions. Please, update your mod.")
else
mod:warning("Using deprecated widget definitions. Please, update your mod.")
end
local mod_settings_list_widgets_definitions = {}
local new_widget_definition
local new_widget_index
local options_menu_favorite_mods = vmf:get("options_menu_favorite_mods")
local options_menu_collapsed_widgets = vmf:get("options_menu_collapsed_widgets")
local mod_collapsed_widgets = nil
if options_menu_collapsed_widgets then
mod_collapsed_widgets = options_menu_collapsed_widgets[mod:get_name()]
end
-- defining header widget
new_widget_index = 1
new_widget_definition = {}
new_widget_definition.type = "header"
new_widget_definition.index = new_widget_index
new_widget_definition.mod_name = mod:get_name()
new_widget_definition.readable_mod_name = mod:get_readable_name()
new_widget_definition.tooltip = mod:get_description()
new_widget_definition.default = true
new_widget_definition.is_togglable = mod:get_internal_data("is_togglable") and
not mod:get_internal_data("is_mutator")
new_widget_definition.is_collapsed = vmf:get("options_menu_collapsed_mods")[mod:get_name()]
if options_menu_favorite_mods then
for _, current_mod_name in pairs(options_menu_favorite_mods) do
if current_mod_name == mod:get_name() then
new_widget_definition.is_favorited = true
break
end
end
end
table.insert(mod_settings_list_widgets_definitions, new_widget_definition)
-- defining its subwidgets
if widgets_definition then
local level = 1
local parent_number = new_widget_index
local parent_widget = {["widget_type"] = "header", ["sub_widgets"] = widgets_definition}
local current_widget = widgets_definition[1]
local current_widget_index = 1
local parent_number_stack = {}
local parent_widget_stack = {}
local current_widget_index_stack = {}
while new_widget_index <= 1024 do
-- if 'nil', we reached the end of the current level widgets list and need to go up
if current_widget then
new_widget_index = new_widget_index + 1
new_widget_definition = {}
new_widget_definition.type = current_widget.widget_type -- all
new_widget_definition.index = new_widget_index -- all [gen]
new_widget_definition.depth = level -- all [gen]
new_widget_definition.mod_name = mod:get_name() -- all [gen]
new_widget_definition.setting_id = current_widget.setting_name -- all
new_widget_definition.title = current_widget.text -- all
new_widget_definition.tooltip = current_widget.tooltip and (current_widget.text .. "\n" ..
current_widget.tooltip) -- all [optional]
new_widget_definition.unit_text = current_widget.unit_text -- numeric [optional]
new_widget_definition.range = current_widget.range -- numeric
new_widget_definition.decimals_number = current_widget.decimals_number -- numeric [optional]
new_widget_definition.options = current_widget.options -- dropdown
new_widget_definition.default_value = current_widget.default_value -- all
new_widget_definition.function_name = current_widget.action -- keybind [optional?]
new_widget_definition.show_widget_condition = current_widget.show_widget_condition -- all
new_widget_definition.parent_index = parent_number -- all [gen]
if mod_collapsed_widgets then
new_widget_definition.is_collapsed = mod_collapsed_widgets[current_widget.setting_name]
end
if type(mod:get(current_widget.setting_name)) == "nil" then
mod:set(current_widget.setting_name, current_widget.default_value)
end
if current_widget.widget_type == "keybind" then
new_widget_definition.keybind_trigger = "pressed"
if current_widget.action == "toggle_mod_state" then
new_widget_definition.keybind_type = "mod_toggle"
new_widget_definition.function_name = nil
else
new_widget_definition.keybind_type = "function_call"
end
local keybind = mod:get(current_widget.setting_name)
if current_widget.action then
vmf.add_mod_keybind(
mod,
new_widget_definition.setting_id,
{
trigger = new_widget_definition.keybind_trigger,
type = new_widget_definition.keybind_type,
keys = keybind,
function_name = new_widget_definition.function_name
}
)
end
end
table.insert(mod_settings_list_widgets_definitions, new_widget_definition)
end
if current_widget and (
current_widget.widget_type == "header" or
current_widget.widget_type == "group" or
current_widget.widget_type == "checkbox" or
current_widget.widget_type == "dropdown"
) and current_widget.sub_widgets then
-- going down to the first subwidget
level = level + 1
table.insert(parent_number_stack, parent_number)
parent_number = new_widget_index
table.insert(parent_widget_stack, parent_widget)
parent_widget = current_widget
table.insert(current_widget_index_stack, current_widget_index)
current_widget_index = 1
current_widget = current_widget.sub_widgets[1]
else
current_widget_index = current_widget_index + 1
if parent_widget.sub_widgets[current_widget_index] then
-- going to the next widget
current_widget = parent_widget.sub_widgets[current_widget_index]
else
-- going up to the widget next to the parent one
level = level - 1
parent_number = table.remove(parent_number_stack)
parent_widget = table.remove(parent_widget_stack)
current_widget_index = table.remove(current_widget_index_stack)
if not current_widget_index then
break
end
current_widget_index = current_widget_index + 1
-- widget next to parent one, or 'nil', if there are no more widgets on this level
current_widget = parent_widget.sub_widgets[current_widget_index]
end
end
end
if new_widget_index == 1025 then
mod:error("(vmf_options_view) The limit of 256 options widgets was reached. You can't add any more widgets.")
end
end
table.insert(vmf.options_widgets_data, mod_settings_list_widgets_definitions)
end

View file

@ -11,6 +11,7 @@ local _commands_list = {}
local _command_index = 0 -- 0 => 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()

View file

@ -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 }

View file

@ -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

View file

@ -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
}

View file

@ -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 #######################################################################################################
-- ####################################################################################################################

File diff suppressed because it is too large Load diff

View file

@ -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 "<not selected>"
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)

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -1 +0,0 @@
boot_package = "resource_packages/vmf/vmf_resources"

View file

@ -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
}