Compare commits

..

36 commits

Author SHA1 Message Date
Ron Au
39383b9fb1
Add parameter defaults and better error messages for dump and dump_to_file (#37)
* feat(dump_to_file): Allow empty object_name

* feat(dump, dump_to_file): Allow empty max_depth

* fix(dump, dump_to_file): Return actual name in error

* style(dump): Rename `dumped_object_name` to `object_name`
- To be consistent with `dump_to_file`
2023-05-10 14:23:01 -06:00
zombine
a11efd275d
Added Japanese localization. (#35)
* Added Japanese localization.

* Updated Japanese localization.
2023-04-25 15:40:46 -06:00
xsSplater
745542b921
Updated Russian localization. (#32) 2023-04-12 15:12:30 -06:00
Aussiemon
124002bb1f
Rewrite keybinds module to function more like vanilla keybinds (#33)
* Rewrite keybinding module to work like vanilla keybinds

* Treat left and right modifier keys separately

* Fix minor crash bug

* Don't close views if actively watching for keys
2023-04-12 15:07:04 -06:00
Aussiemon
c617fc69b0
Remember last selected category when reopening Mod Options (#31)
* Remember last selected category when reopening mod options

* Change setting name for consistency

* Update comment
2023-04-05 00:37:52 -06:00
Aussiemon
1a5b806f90
Change scroll speed to scroll amount and 1.0.45 options view changes (#29)
* Change scroll speed to scroll amount

* Include 1.0.45 options view changes
2023-04-04 13:57:48 -06:00
Aussiemon
b13ef309af
Keybind and mod option improvements (#27)
* Fix category widget cutoff

* Fix keybind reset-to-default

* Fix multiple keybinds using the same set of keys

* Comment out unimplemented settings

* Reposition mod options widgets
2023-04-01 00:02:34 -06:00
Aussiemon
836d8e2f88
Merge pull request #19 from deluxghost/patch-1
update translation
2023-03-29 21:08:01 -06:00
Aussiemon
cdac4841db
Merge pull request #24 from Darktide-Mod-Framework/marchfixes2
Disable pressed callback on checkboxes
2023-03-29 21:07:02 -06:00
Aussiemon
f1cf3ad7db Disable pressed callback on checkboxes 2023-03-29 21:04:30 -06:00
deluxghost
088c0997f8 Update dmf.lua 2023-03-30 10:08:35 +08:00
Aussiemon
fcfd5477dd
Merge pull request #23 from Darktide-Mod-Framework/marchfixes
Fix command window output, add require_restart support, gamepad fixes
2023-03-29 15:03:17 -06:00
Aussiemon
7075152aae Support for optional require_restart, callbacks, and gamepad fixes 2023-03-29 14:55:35 -06:00
Aussiemon
c83ef8f911 Fix mod:dump output to command window and auto-close 2023-03-29 14:50:42 -06:00
Aussiemon
70b3492d35
Update CONTRIBUTORS.MD 2023-03-26 21:32:13 -06:00
alxl
9633b29fed
All mod toggles in separate category (#16)
* mod toggle category

* mods with no config appear in Toggle Mod list

---------
2023-03-26 21:29:58 -06:00
Aussiemon
3b387c294d Port latest OptionsView changes into DMF 2023-03-26 21:27:51 -06:00
Ron Au
c87b0fd9c0
docs(README): Replace unsupported <style> tag (#18)
- Optimise PNGs
2023-03-26 18:48:36 -06:00
Ron Au
068cd94de3
Add light/dark mode support for wiki docs and better dark mode for README logo (#17)
* docs(README): Invert logo image in dark mode

* docs(theme): Add light/dark mode support
2023-03-26 10:21:16 -06:00
Aussiemon
de9468c6ab Fix crash on invalid widget type 2023-03-19 01:43:59 -06:00
Aussiemon
293ca07904 Repostion command list gui text 2023-03-19 01:42:46 -06:00
deluxghost
c1e05b5425
add zh-cn hardcoded mod_options translation (#13) 2023-03-19 01:14:04 -06:00
Aussiemon
76e6d748b6 Draw command description strings in proper order 2023-03-12 20:17:59 -06:00
Aussiemon
3182e06c25 De-hardcode font material for command list gui to fix localization 2023-03-12 20:03:14 -06:00
Aussiemon
99799a4a10 Fix for keybind popup opening after dropdown selection 2023-03-12 18:18:58 -06:00
Aussiemon
14242acb54 Update contributors 2023-03-04 15:00:14 -07:00
fracticality
fea449b52f
Add support for "group" widget type (#9) 2023-03-04 14:24:14 -07:00
deluxghost
bacd49f221
Add zh-cn localization (#7) 2023-03-04 14:22:12 -07:00
Aussiemon
24502d9957
Merge pull request #4 from danreeves/inject-mod-options
Inject Mod Options button into Esc menu
2023-02-25 22:07:48 -07:00
Aussiemon
87ba024f1d Update readme.md 2023-02-25 22:05:46 -07:00
Aussiemon
d1502daed9
Create CNAME 2023-02-25 21:13:15 -07:00
Aussiemon
7af8c22a8c
Merge pull request #6 from danreeves/gh-pages
Add docs setup
2023-02-25 21:08:13 -07:00
Dan Reeves
ae1564f973 Inject Mod Options button into Esc menu 2023-02-24 15:18:16 +00:00
Aussiemon
943313515f Fix loadstring issue 2023-02-22 14:05:08 -07:00
Dan Reeves
d83bf6d86c add docs setup 2023-02-18 22:49:58 +00:00
Aussiemon
66360e3438
Merge pull request #5 from Darktide-Mod-Framework/file_hook_callback
Change file hook handler to callback function, rename to hook_require
2023-02-17 17:08:50 -07:00
28 changed files with 3189 additions and 636 deletions

2
.gitignore vendored
View file

@ -1,4 +1,6 @@
.temp .temp
.vscode .vscode
**/*.zip
vmf/bundleV1 vmf/bundleV1
vmf/bundleV2 vmf/bundleV2
node_modules

View file

@ -1,9 +1,13 @@
# DMF contributors (sorted alphabetically) # DMF contributors (sorted alphabetically)
Contribute to DMF -- add your name here! Contribute to DMF -- add your name here!
+ [alxl](https://github.com/ItsAlxl)
+ [Aussiemon](https://github.com/Aussiemon) + [Aussiemon](https://github.com/Aussiemon)
+ [deluxghost](https://github.com/deluxghost)
+ [raindish](https://github.com/danreeves)
+ [Fracticality](https://github.com/fracticality) + [Fracticality](https://github.com/fracticality)
+ [grasmann](https://github.com/grasmann) + [grasmann](https://github.com/grasmann)
+ [philipdestroyer](https://github.com/philippedavid) + [philipdestroyer](https://github.com/philippedavid)
+ [ronvoluted](https://github.com/ronvoluted)
+ [SirAiedail](https://github.com/SirAiedail) + [SirAiedail](https://github.com/SirAiedail)
# VMF contributors (sorted alphabetically) # VMF contributors (sorted alphabetically)

View file

@ -1,13 +1,10 @@
<p align="center"> <div align="center">
<a href="#welcome-to-the-darktide-mod-framework-vmf-repository"> <picture>
<img <source media="(prefers-color-scheme: dark)" srcset="./assets/dmf_logo_white.png">
alt="Darktide Mod Framework" <source media="(prefers-color-scheme: light)" srcset="./assets/dmf_logo_black.png">
src="./assets/dmf_logo_black.png" <img alt="Darktide Mod Framework" width="600" src="./assets/dmf_logo_black.png">
width="600" </picture>
/> </div>
</a>
</p>
## Welcome to the Darktide Mod Framework (DMF) Repository! ## Welcome to the Darktide Mod Framework (DMF) Repository!
@ -22,36 +19,6 @@ Mods created for the project may utilize:
* Rewritten, lightweight mod functions * Rewritten, lightweight mod functions
* An on-event call system * An on-event call system
The Darktide Mod Framework originally started in Warhammer End Times: Vermintide as an unofficial modding platform. In the time since, DMF has been rewritten and redesigned with contributions from many unique members of the community; culminating in this unified project. The Darktide Mod Framework is a fork of the Vermintide Mod Framework that originally started in Warhammer End Times: Vermintide as an unofficial modding platform. In the time since, the framework has been rewritten and redesigned with contributions from many unique members of the community; culminating in this unified project.
For more information, check out **[the framework's wiki pages](https://github.com/Darktide-Mod-Framework/Darktide-Mod-Framework/wiki)**. For more information, check out **[the framework's wiki pages](http://dmf-docs.darkti.de)**.
## Building the Framework
### Prerequisites:
- To start, you should be subscribed to the VMF Beta in Steam Workshop. (**[Warhammer End Times - Vermintide](https://steamcommunity.com/sharedfiles/filedetails/?id=1500136933)** or **[Warhammer: Vermintide 2](https://steamcommunity.com/sharedfiles/filedetails/?id=1500112422)**, depending on which version you want to compile)
- You should also install **[Vermintide Mod Builder (VMB)](https://github.com/Vermintide-Mod-Framework/Vermintide-Mod-Framework/wiki/Get-Vermintide-Mod-Builder)**.
### Building Steps:
1. Navigate to your VMB directory. Let's assume it's unpacked into a folder named `vermintide-mod-builder`.
2. Create a folder inside `vermintide-mod-builder` (we'll call it `vermintide-mod-framework`) and **[clone](https://git-scm.com/docs/git-clone)** in the VMF repository's contents.
3. Open a console/Command Prompt/PowerShell window inside your `vermintide-mod-builder` directory and use the following VMB command: `vmb build vmf -f vermintide-mod-framework -g [1|2]`, where the number after `-g` indicates the target Vermintide game.
You can find more VMB mod-building information in the **[Vermintide Mod Builder documentation](https://github.com/Vermintide-Mod-Framework/Vermintide-Mod-Builder/blob/master/README.md)**.
## Steam Workshop Links
Beta builds:
- [Warhammer End Times - Vermintide](https://steamcommunity.com/sharedfiles/filedetails/?id=1500136933)
- [Warhammer: Vermintide 2](https://steamcommunity.com/sharedfiles/filedetails/?id=1500112422)
Stable builds:
- [Warhammer End Times - Vermintide](https://steamcommunity.com/sharedfiles/filedetails/?id=1289946781)
- [Warhammer: Vermintide 2](https://steamcommunity.com/sharedfiles/filedetails/?id=1369573612)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 146 KiB

BIN
assets/dmf_logo_white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

View file

@ -3,75 +3,108 @@ return {
en = "Mod Options", en = "Mod Options",
es = "Configuración de mods", es = "Configuración de mods",
ru = "Настройки модов", ru = "Настройки модов",
["zh-cn"] = "模组选项",
ja = "Modオプション",
}, },
open_dmf_options = { open_dmf_options = {
en = "Open Options Menu", en = "Open Options Menu",
es = "Abrir el menu de configuración", es = "Abrir el menu de configuración",
ru = "Открыть меню настроек", ru = "Открыть меню настроек",
["zh-cn"] = "打开选项菜单",
ja = "オプションメニューを開く",
}, },
open_dmf_options_description = { open_dmf_options_description = {
en = "Keybind for opening and closing mods options menu.", en = "Keybind for opening and closing mods options menu.",
es = "Atajo para abrir/cerrar el menu de configuración de mods.", es = "Atajo para abrir/cerrar el menu de configuración de mods.",
ru = "Клавиша/сочетание клавиш для открытия и закрытия меню настроек модов.", ru = "Клавиша/сочетание клавиш для открытия и закрытия меню настроек модов.",
["zh-cn"] = "打开关闭模组选项菜单的按键绑定。",
ja = "オプションメニューを開閉するキーバインド",
}, },
dmf_options_scrolling_speed = { dmf_options_scrolling_speed = {
en = "Options Menu Scrolling Speed", en = "Options Menu Scrolling Speed",
es = "Velocidad de desplazamiento en el menu", es = "Velocidad de desplazamiento en el menu",
ru = "Скорость прокрутки меню", ru = "Скорость прокрутки меню",
["zh-cn"] = "选项菜单滚动速度",
ja = "オプションメニューのスクロール速度",
}, },
dmf_first_run_notification = { dmf_first_run_notification = {
en = "Welcome to the Darktide Mod Framework. Mod options have been added to the Options Menu.", en = "Welcome to the Darktide Mod Framework. Mod options have been added to the Options Menu.",
es = "Bienvenidos a el Mod Framework de Darktide. Hemos agregado las opciones de Mod a el menu de opciones.", es = "Bienvenidos a el Mod Framework de Darktide. Hemos agregado las opciones de Mod a el menu de opciones.",
de = "Willkommen beim Darktide Mod Framework. Ein Button für Mod-Optionen wurde dem Hauptmenu hinzugefügt.", de = "Willkommen beim Darktide Mod Framework. Ein Button für Mod-Optionen wurde dem Hauptmenu hinzugefügt.",
["zh-cn"] = "欢迎使用 Darktide Mod Framework。模组选项已添加到选项菜单。",
ru = "Добро пожаловать в Darktide Mod Framework. Параметры мода были добавлены в меню параметров.",
ja = "Darktide Mod Frameworkのご利用ありがとうございます。Modオプションがオプションメニューに追加されました。",
}, },
percent = { percent = {
en = "%%", en = "%%",
}, },
toggle_mod = { toggle_mods = {
en = "Toggle Mod", en = "Toggle Mods",
["zh-cn"] = "开启关闭模组",
ru = "Включение/выключение модов",
ja = "Modのオン/オフ",
}, },
toggle_mod_description = { toggle_mods_description = {
en = "Enable or disable the mod", en = "Enable or disable your mods.",
["zh-cn"] = "启用或禁用安装的模组。",
ru = "Включите или отключите ваши моды.",
ja = "Modを有効化/無効化します。",
}, },
ui_scaling = { ui_scaling = {
en = "UI Scaling for FHD+ Resolutions", en = "UI Scaling for FHD+ Resolutions",
es = "Reescalado de la interfaz para resoluciones Full HD+", es = "Reescalado de la interfaz para resoluciones Full HD+",
ru = "Нормализация масштаба UI для FHD+ разрешений", ru = "Нормализация масштаба интерфейса для FHD+ разрешений",
["zh-cn"] = "高分辨率 UI 缩放",
ja = "解像度FHD以上でのUIスケーリング",
}, },
ui_scaling_description = { ui_scaling_description = {
en = "Automatically scale UI when resolution exceeds 1080p.", en = "Automatically scale UI when resolution exceeds 1080p.",
es = "Redimensionar automáticamente la interfaz cuando la resolución exceda 1080p.", es = "Redimensionar automáticamente la interfaz cuando la resolución exceda 1080p.",
ru = "Нормализует масштаб элементов интерфейса, если разрешений экрана превышает 1080p.", ru = "Нормализует масштаб элементов интерфейса, если разрешений экрана превышает 1080p.",
["zh-cn"] = "分辨率超过 1080p 时自动缩放 UI",
ja = "1080pを超える解像度でUIの大きさを自動調節します。",
}, },
developer_mode = { developer_mode = {
en = "Developer Mode", en = "Developer Mode",
es = "Modo de desarrollo", es = "Modo de desarrollo",
ru = "Режим разработчика", ru = "Режим разработчика",
["zh-cn"] = "开发者模式",
ja = "開発者モード",
}, },
developer_mode_description = { developer_mode_description = {
en = "Allows you to reload DMF and mods (CTRL+SHIFT+R), gives you access to some debug features.", en = "Allows you to reload DMF and mods (CTRL+SHIFT+R), gives you access to some debug features.",
es = "Permite recargar los mods (CTRL+SHIFT+R) y configurar herramientas de depuración.", es = "Permite recargar los mods (CTRL+SHIFT+R) y configurar herramientas de depuración.",
ru = "Позволяет перезагружать DMF и моды (CTRL+SHIFT+R), даёт доступ к инструментам отладки.", ru = "Позволяет перезагружать DMF и моды (CTRL+SHIFT+R), даёт доступ к инструментам отладки.",
["zh-cn"] = "允许重新加载 DMF 和模组CTRL+SHIFT+R并启用一些调试功能。",
ja = "DMFとModのリロード (CTRL+SHIFT+R) や、いくつかのデバッグ機能へのアクセスを可能にします。",
}, },
show_developer_console = { show_developer_console = {
en = "Show Developer Console", en = "Show Developer Console",
es = "Mostrar el registro (log) a tiempo real", es = "Mostrar el registro (log) a tiempo real",
ru = "Консоль разработчика", ru = "Консоль разработчика",
["zh-cn"] = "显示开发者控制台",
ja = "開発者コンソールの表示",
}, },
show_developer_console_description = { show_developer_console_description = {
en = "Opens up the new window showing game log in real time.", en = "Opens up the new window showing game log in real time.",
es = "Abre una ventana que muestra el registro (log) del juego en tiempo real.", es = "Abre una ventana que muestra el registro (log) del juego en tiempo real.",
ru = "Открывает новое окно, в которое в реальном времени выводится игровой лог.", ru = "Открывает новое окно, в которое в реальном времени выводится игровой лог.",
["zh-cn"] = "在一个新窗口中显示游戏实时日志。",
ja = "ゲームのログをリアルタイムで表示する新たなウィンドウを開きます。",
}, },
toggle_developer_console = { toggle_developer_console = {
en = "Toggle Developer Console", en = "Toggle Developer Console",
es = "Abrir el registro (log) a tiempo real", es = "Abrir el registro (log) a tiempo real",
ru = "Открыть/закрыть консоль разработчика", ru = "Открыть/закрыть консоль разработчика",
["zh-cn"] = "开关开发者控制台",
ja = "開発者コンソールの表示/非表示",
}, },
show_network_debug_info = { show_network_debug_info = {
en = "Log Network Calls", en = "Log Network Calls",
es = "Depurar las llamadas de red", es = "Depurar las llamadas de red",
ru = "Логирование сетевых вызовов", ru = "Логирование сетевых вызовов",
["zh-cn"] = "记录网络调用日志",
ja = "ネットワーク呼び出しの記録",
}, },
show_network_debug_info_description = { show_network_debug_info_description = {
en = "Log all the DMF network calls and all the data transfered with them.\n\n" .. en = "Log all the DMF network calls and all the data transfered with them.\n\n" ..
@ -80,11 +113,17 @@ return {
"Esta información se registra en el nivel 'info'.", "Esta información se registra en el nivel 'info'.",
ru = "Логирование всех сетевых вызовов DMF и передаваемых с ними данных.\n\n" .. ru = "Логирование всех сетевых вызовов DMF и передаваемых с ними данных.\n\n" ..
"Для логирования используется метод 'info'.", "Для логирования используется метод 'info'.",
["zh-cn"] = "记录所有 DMF 网络调用及其所传输数据的日志。\n\n" ..
"记录日志时会使用 'info' 方法。",
ja = "すべてのDMFのネットワーク呼び出しと通信データをログに記録します。\n\n" ..
"記録には 'info' メソッドが使用されます。",
}, },
log_ui_renderers_info = { log_ui_renderers_info = {
en = "Log UI Renderers Creation Info", en = "Log UI Renderers Creation Info",
es = "Depurar la renderización de la interfaz de usuario", es = "Depurar la renderización de la interfaz de usuario",
ru = "Логирование информации при создании UI Renderer", ru = "Логирование информации при создании UI Renderer",
["zh-cn"] = "记录 UI 渲染器创建信息",
ja = "UIレンダラー生成情報の記録",
}, },
log_ui_renderers_info_description = { log_ui_renderers_info_description = {
en = "Log the UI Renderer's creator name and all the materials passed as the arguments.\n\n" .. en = "Log the UI Renderer's creator name and all the materials passed as the arguments.\n\n" ..
@ -93,86 +132,131 @@ return {
"Esta información se registra en el nivel 'info'.", "Esta información se registra en el nivel 'info'.",
ru = "Логирование имени создателя UI Renderer'а и всех материалов, переданных в качестве аргументов.\n\n" .. ru = "Логирование имени создателя UI Renderer'а и всех материалов, переданных в качестве аргументов.\n\n" ..
"Для логирования используется метод 'info'.", "Для логирования используется метод 'info'.",
["zh-cn"] = "记录 UI 渲染器的创建者名称以及作为参数传入的所有材质。\n\n" ..
"记录日志时会使用 'info' 方法。",
ja = "UIレンダラー生成元の名称と、引数として渡されたすべてのマテリアルをログに記録します。\n\n" ..
"記録には 'info' メソッドが使用されます。",
}, },
logging_mode = { logging_mode = {
en = "Logging Settings", en = "Logging Settings",
es = "Opciones de logging", es = "Opciones de logging",
ru = "Настройки логирования", ru = "Настройки логирования",
["zh-cn"] = "日志设置",
ja = "ログの設定",
}, },
settings_default = { settings_default = {
en = "Default", en = "Default",
es = "Valor por defecto", es = "Valor por defecto",
ru = "Стандартные", ru = "Стандартные",
["zh-cn"] = "默认",
ja = "デフォルト",
}, },
settings_custom = { settings_custom = {
en = "Custom", en = "Custom",
es = "Personalizado", es = "Personalizado",
ru = "Пользовательские", ru = "Пользовательские",
["zh-cn"] = "自定义",
ja = "カスタム",
}, },
output_mode_notification = { output_mode_notification = {
en = "'Notification' Output", en = "'Notification' Output",
["zh-cn"] = "'Notification' 通知输出",
ru = "Вывод 'Notification'",
ja = "'Notification' の出力",
}, },
output_mode_echo = { output_mode_echo = {
en = "'Echo' Output", en = "'Echo' Output",
es = "Mensajes de 'Echo'", es = "Mensajes de 'Echo'",
ru = "Вывод 'Echo'", ru = "Вывод 'Echo'",
["zh-cn"] = "'Echo' 回显输出",
ja = "'Echo' の出力",
}, },
output_mode_error = { output_mode_error = {
en = "'Error' Output", en = "'Error' Output",
es = "Mensajes de 'Error'", es = "Mensajes de 'Error'",
ru = "Вывод 'Error'", ru = "Вывод 'Error'",
["zh-cn"] = "'Error' 错误输出",
ja = "'Error' の出力",
}, },
output_mode_warning = { output_mode_warning = {
en = "'Warning' Output", en = "'Warning' Output",
es = "Mensajes de 'Warning'", es = "Mensajes de 'Warning'",
ru = "Вывод 'Warning'", ru = "Вывод 'Warning'",
["zh-cn"] = "'Warning' 警告输出",
ja = "'Warning' の出力",
}, },
output_mode_info = { output_mode_info = {
en = "'Info' Output", en = "'Info' Output",
es = "Mensajes de 'Info'", es = "Mensajes de 'Info'",
ru = "Вывод 'Info'", ru = "Вывод 'Info'",
["zh-cn"] = "'Info' 信息输出",
ja = "'Info' の出力",
}, },
output_mode_debug = { output_mode_debug = {
en = "'Debug' Output", en = "'Debug' Output",
es = "Mensajes de 'Debug'", es = "Mensajes de 'Debug'",
ru = "Вывод 'Debug'", ru = "Вывод 'Debug'",
["zh-cn"] = "'Debug' 调试输出",
ja = "'Debug' の出力",
}, },
output_disabled = { output_disabled = {
en = "Disabled", en = "Disabled",
es = "Desactivado", es = "Desactivado",
ru = "Выключен", ru = "Выключен",
["zh-cn"] = "禁用",
ja = "無効",
}, },
output_log = { output_log = {
en = "Log", en = "Log",
es = "Registro (log)", es = "Registro (log)",
ru = "Лог", ru = "Лог",
["zh-cn"] = "日志",
ja = "ログ",
}, },
output_chat = { output_chat = {
en = "Chat", en = "Chat",
es = "Chat", es = "Chat",
ru = "Чат", ru = "Чат",
["zh-cn"] = "聊天",
ja = "チャット",
}, },
output_notification = { output_notification = {
en = "Notification", en = "Notification",
["zh-cn"] = "通知",
ru = "Уведомление",
ja = "通知",
}, },
output_log_and_chat = { output_log_and_chat = {
en = "Log & Chat", en = "Log & Chat",
es = "Registro (log) y chat", es = "Registro (log) y chat",
ru = "Лог и чат", ru = "Лог и чат",
["zh-cn"] = "日志与聊天",
ja = "ログとチャット",
}, },
output_all = { output_all = {
en = "All", en = "All",
["zh-cn"] = "全部",
ru = "Все",
ja = "すべて",
}, },
output_log_and_notification = { output_log_and_notification = {
en = "Log & Notification", en = "Log & Notification",
["zh-cn"] = "日志与通知",
ru = "Лог и Уведомление",
ja = "ログと通知",
}, },
output_chat_and_notification = { output_chat_and_notification = {
en = "Chat & Notification", en = "Chat & Notification",
["zh-cn"] = "聊天与通知",
ru = "Чат и Уведомление",
ja = "チャットと通知",
}, },
chat_history_enable = { chat_history_enable = {
en = "Chat Input History", en = "Chat Input History",
es = "Historial de chat", es = "Historial de chat",
ru = "История ввода чата", ru = "История ввода чата",
["zh-cn"] = "聊天输入历史记录",
ja = "チャット入力の履歴",
}, },
chat_history_enable_description = { chat_history_enable_description = {
en = "Saves all the messages and commands you typed in the chat window.\n\n" .. en = "Saves all the messages and commands you typed in the chat window.\n\n" ..
@ -181,21 +265,31 @@ return {
"Puedes navegar por tu historial de comandos abriendo el chat y usando las flechas del teclado.", "Puedes navegar por tu historial de comandos abriendo el chat y usando las flechas del teclado.",
ru = "Сохраняет все сообщения и команды, введённые в чате.\n\n" .. ru = "Сохраняет все сообщения и команды, введённые в чате.\n\n" ..
"Чтобы пролистывать историю ввода, откройте чат и используйте клавиши \"стрелка вверх\" и \"стрелка вниз\".", "Чтобы пролистывать историю ввода, откройте чат и используйте клавиши \"стрелка вверх\" и \"стрелка вниз\".",
["zh-cn"] = "保存所有你在聊天窗口内输入过的消息和命令。\n\n" ..
"你可以打开聊天窗口,按“上下方向键”浏览输入历史记录。",
ja = "チャット欄に記入したすべてのメッセージとコマンドを保存します。\n\n" ..
"入力履歴はチャットを開いて「上矢印」と「下矢印」キーで表示できます。",
}, },
chat_history_save = { chat_history_save = {
en = "Save Input History Between Game Sessions", en = "Save Input History Between Game Sessions",
es = "Guardar la entrada", es = "Guardar la entrada",
ru = "Сохранять историю ввода между сеансами игры", ru = "Сохранять историю ввода между сеансами игры",
["zh-cn"] = "重启游戏仍保留输入历史",
ja = "ゲームセッション間での入力履歴の保存",
}, },
chat_history_save_description = { chat_history_save_description = {
en = "Your chat input history will be saved even after reloading your game (or just DMF).", en = "Your chat input history will be saved even after reloading your game (or just DMF).",
es = "El texto que introduzcas en el chat se guardara incluso al recargar el juego (o solo DMF)", es = "El texto que introduzcas en el chat se guardara incluso al recargar el juego (o solo DMF)",
ru = "Когда игрок выключает игру (или перезагружает DMF), DMF cохраняет историю ввода в файл настроек, чтобы загрузить её при следующем запуске игры.", ru = "Когда игрок выключает игру (или перезагружает DMF), DMF cохраняет историю ввода в файл настроек, чтобы загрузить её при следующем запуске игры.",
["zh-cn"] = "即使重新启动游戏(或者重新加载 DMF仍然保存聊天输入历史记录。",
ja = "ゲームの再起動 (またはDMFのリロード) 後もチャットの入力履歴が保持されます。",
}, },
chat_history_buffer_size = { chat_history_buffer_size = {
en = "Input History Buffer Size", en = "Input History Buffer Size",
es = "Número de comandos antiguos guardados", es = "Número de comandos antiguos guardados",
ru = "Размер буфера истории ввода", ru = "Размер буфера истории ввода",
["zh-cn"] = "输入历史记录大小",
ja = "入力履歴のバッファサイズ",
}, },
chat_history_buffer_size_description = { chat_history_buffer_size_description = {
en = "Maximum number of saved entries.\n\n" .. en = "Maximum number of saved entries.\n\n" ..
@ -204,16 +298,24 @@ return {
"ATENCIÓN: Cambiar esta preferencia borra el historial del chat.", "ATENCIÓN: Cambiar esta preferencia borra el historial del chat.",
ru = "Максимальное количество сохраняемых записей.\n\n" .. ru = "Максимальное количество сохраняемых записей.\n\n" ..
"ВНИМАНИЕ: изменение этой настройки очистит вашу историю ввода.", "ВНИМАНИЕ: изменение этой настройки очистит вашу историю ввода.",
["zh-cn"] = "最大保存记录条数。\n\n" ..
"警告:更改此设置会删除所有聊天历史记录。",
ja = "履歴の最大保存数。\n\n" ..
"警告:この設定を変更するとチャット履歴が消去されます。",
}, },
chat_history_remove_dups = { chat_history_remove_dups = {
en = "Remove Duplicate Entries", en = "Remove Duplicate Entries",
es = "Eliminar lineas repetidas", es = "Eliminar lineas repetidas",
ru = "Удалять повторяющиеся записи", ru = "Удалять повторяющиеся записи",
["zh-cn"] = "删除重复记录",
ja = "重複する履歴の削除",
}, },
chat_history_remove_dups_mode = { chat_history_remove_dups_mode = {
en = "Removal Mode", en = "Removal Mode",
es = "Modo de eliminación de repetidos", es = "Modo de eliminación de repetidos",
ru = "Режим удаления", ru = "Режим удаления",
["zh-cn"] = "删除模式",
ja = "削除方式",
}, },
chat_history_remove_dups_mode_description = { chat_history_remove_dups_mode_description = {
en = "Which duplicate entries should be removed.\n\n" .. en = "Which duplicate entries should be removed.\n\n" ..
@ -225,21 +327,33 @@ return {
ru = "Повторяющиеся записи, которые будут удалены.\n\n" .. ru = "Повторяющиеся записи, которые будут удалены.\n\n" ..
"-- ПОСЛЕДНИЕ --\nПредпоследняя запись будет удалена, если она совпадает с последней.\n\n" .. "-- ПОСЛЕДНИЕ --\nПредпоследняя запись будет удалена, если она совпадает с последней.\n\n" ..
"-- ВСЕ --\nВсе записи, совпадающие с последней записью, будут удалены.", "-- ВСЕ --\nВсе записи, совпадающие с последней записью, будут удалены.",
["zh-cn"] = "应该删除哪些重复记录。\n\n" ..
"-- 最新 --\n如果和最新一条匹配,则删除上一条记录。\n\n" ..
"-- 所有 --\n如果和最新一条匹配,则删除所有匹配的记录。",
ja = "重複した際にどの履歴を削除するか。\n\n" ..
"-- 直前 --\n直前の履歴が重複する場合、それを削除します。\n\n" ..
"-- すべて --\n重複するすべての履歴を削除します。",
}, },
settings_last = { settings_last = {
en = "Last", en = "Last",
es = "Última", es = "Última",
ru = "Последние", ru = "Последние",
["zh-cn"] = "最新",
ja = "直前",
}, },
settings_all = { settings_all = {
en = "All", en = "All",
es = "Todas", es = "Todas",
ru = "Все", ru = "Все",
["zh-cn"] = "所有",
ja = "すべて",
}, },
chat_history_commands_only = { chat_history_commands_only = {
en = "Save only executed commands", en = "Save only executed commands",
es = "Salvar unicamente los comandos ejecutados", es = "Salvar unicamente los comandos ejecutados",
ru = "Сохранять только выполненные команды", ru = "Сохранять только выполненные команды",
["zh-cn"] = "只保存执行的命令",
ja = "実行したコマンドのみを保存",
}, },
chat_history_commands_only_description = { chat_history_commands_only_description = {
en = "Only successfully executed commands will be saved in the chat history.\n\n" .. en = "Only successfully executed commands will be saved in the chat history.\n\n" ..
@ -248,28 +362,50 @@ return {
"ATENCIÓN: Cambiar esta preferencia borra el historial del chat.", "ATENCIÓN: Cambiar esta preferencia borra el historial del chat.",
ru = "Только успешно выполненные команды будут сохранены в истории ввода.\n\n" .. ru = "Только успешно выполненные команды будут сохранены в истории ввода.\n\n" ..
"ВНИМАНИЕ: изменение этой настройки очистит вашу историю ввода.", "ВНИМАНИЕ: изменение этой настройки очистит вашу историю ввода.",
["zh-cn"] = "只有成功执行的命令才会保存在聊天历史记录里。\n\n" ..
"警告:更改此设置会删除所有聊天历史记录。",
ja = "実行できたコマンドのみをチャット履歴に保存します。\n\n" ..
"警告:この設定を変更するとチャット履歴が消去されます。",
}, },
chat_command_not_recognized = { chat_command_not_recognized = {
en = "Command not recognized", en = "Command not recognized",
["zh-cn"] = "无法识别的命令",
ru = "Команда не распознана",
ja = "不明なコマンド",
}, },
clean_chat_history = { clean_chat_history = {
en = "cleans chat input history", en = "cleans chat input history",
es = "Borra el historial de usuario", es = "Borra el historial de usuario",
ru = "очищает историю ввода", ru = "очищает историю ввода",
["zh-cn"] = "清除聊天输入历史记录",
ja = "チャット入力履歴の消去",
}, },
clean_chat_notifications = { clean_chat_notifications = {
en = "cleans chat notification alerts" en = "cleans chat notification alerts",
["zh-cn"] = "清除聊天通知警告",
ru = "очищает предупреждения об уведомлениях чата",
ja = "チャット通知警告の消去",
}, },
dev_console_opened = { dev_console_opened = {
en = "Developer console opened.", en = "Developer console opened.",
es = "Abierto la consola de desarrollo.", es = "Abierto la consola de desarrollo.",
ru = "Консоль разработчика открыта.", ru = "Консоль разработчика открыта.",
["zh-cn"] = "已打开开发者控制台。",
ja = "開発者コンソールを開きました。",
}, },
dev_console_closed = { dev_console_closed = {
en = "Developer console closed.", en = "Developer console closed.",
es = "Cerrado la consola de desarrollo.", es = "Cerrado la consola de desarrollo.",
ru = "Консоль разработчика закрыта.", ru = "Консоль разработчика закрыта.",
["zh-cn"] = "已关闭开发者控制台。",
ja = "開発者コンソールを閉じました。",
},
dev_console_close_warning = {
en = "The developer console is disabled, but must be closed manually.",
["zh-cn"] = "开发者控制台已禁用,但必须手动关闭。",
ru = "Консоль разработчика отключена, но ее необходимо закрыть вручную.",
ja = "開発者コンソールが無効になっていますが、手動で閉じる必要があります。",
}, },
@ -278,116 +414,197 @@ return {
mutator_no_description_provided = { mutator_no_description_provided = {
en = "No description provided.", en = "No description provided.",
es = "No se proporcionó una descripción.", es = "No se proporcionó una descripción.",
["zh-cn"] = "未提供描述。",
ru = "Описание не предоставлено.",
ja = "説明がありません。",
}, },
-- Difficulties' names -- Difficulties' names
lowest = { lowest = {
en = "Sedition" en = "Sedition",
["zh-cn"] = "煽动",
ru = "Мятеж",
ja = "反乱",
}, },
low = { low = {
en = "Uprising" en = "Uprising",
["zh-cn"] = "暴乱",
ru = "Восстание",
ja = "アップライジング",
}, },
medium = { medium = {
en = "Malice" en = "Malice",
["zh-cn"] = "憎恶",
ru = "Злоба",
ja = "悪意",
}, },
high = { high = {
en = "Heresy" en = "Heresy",
["zh-cn"] = "异端",
ru = "Ересь",
ja = "異端",
}, },
highest = { highest = {
en = "Damnation" en = "Damnation",
["zh-cn"] = "诅咒",
ru = "Проклятие",
ja = "破滅",
}, },
-- Chat messages -- Chat messages
broadcast_enabled_mutators = { broadcast_enabled_mutators = {
en = "ENABLED MUTATORS", en = "ENABLED MUTATORS",
es = "MUTACIONES ACTIVADAS", es = "MUTACIONES ACTIVADAS",
["zh-cn"] = "启用突变器",
ru = "МУТАТОРЫ ВКЛЮЧЕНЫ",
ja = "ミューテーターが有効化されました",
}, },
broadcast_all_disabled = { broadcast_all_disabled = {
en = "ALL MUTATORS DISABLED", en = "ALL MUTATORS DISABLED",
es = "TODAS LAS MUTACIONES DESACTIVADAS", es = "TODAS LAS MUTACIONES DESACTIVADAS",
["zh-cn"] = "禁用所有突变器",
ru = "ВСЕ МУТАТОРЫ ОТКЛЮЧЕНЫ",
ja = "すべてのミューテーターが無効化されました",
}, },
broadcast_disabled_mutators = { broadcast_disabled_mutators = {
en = "MUTATORS DISABLED", en = "MUTATORS DISABLED",
es = "MUTACIONES DESACTIVADAS", es = "MUTACIONES DESACTIVADAS",
["zh-cn"] = "禁用突变器",
ru = "МУТАТОРЫ ОТКЛЮЧЕНЫ",
ja = "ミューテーターが無効化されました",
}, },
local_disabled_mutators = { local_disabled_mutators = {
en = "Mutators disabled", en = "Mutators disabled",
es = "Mutaciones desactivadas", es = "Mutaciones desactivadas",
["zh-cn"] = "突变器已禁用",
ru = "Мутаторы отключены",
ja = "ミューテーターが無効化されました",
}, },
whisper_enabled_mutators = { whisper_enabled_mutators = {
en = "[Automated message] This lobby has the following mutators active", en = "[Automated message] This lobby has the following mutators active",
es = "[Mensaje automático] Esta partida tiene las siguientes mutaciones", es = "[Mensaje automático] Esta partida tiene las siguientes mutaciones",
["zh-cn"] = "[自动消息] 此大厅激活了以下突变器",
ru = "[Автоматическое сообщение] В этом лобби активны следующие мутаторы",
ja = "[自動メッセージ] このロビーでは以下のミューテーターが有効になっています",
}, },
disabled_reason_not_server = { disabled_reason_not_server = {
en = "because you're no longer the host", en = "because you're no longer the host",
es = "porque ya no eres el anfitrión", es = "porque ya no eres el anfitrión",
["zh-cn"] = "因为你不再是主机",
ru = "потому что вы больше не хост",
ja = "あなたがホストではなくなったため",
}, },
disabled_reason_difficulty_change = { disabled_reason_difficulty_change = {
en = "DUE TO CHANGE IN DIFFICULTY", en = "DUE TO CHANGE IN DIFFICULTY",
es = "DEBIDO A UN CAMBIO DE DIFICULTAD", es = "DEBIDO A UN CAMBIO DE DIFICULTAD",
["zh-cn"] = "由于难度变更",
ru = "ИЗ-ЗА ИЗМЕНЕНИЯ СЛОЖНОСТИ",
ja = "難易度が変更されたため",
}, },
-- Interface -- Interface
mutators_title = { mutators_title = {
en = "Mutators", en = "Mutators",
es = "Mutaciones", es = "Mutaciones",
["zh-cn"] = "突变器",
ru = "Мутаторы",
ja = "ミューテーター",
}, },
mutators_banner_description = { mutators_banner_description = {
en = "Enable and disable mutators", en = "Enable and disable mutators",
es = "Activa y desactiva las mutaciones", es = "Activa y desactiva las mutaciones",
["zh-cn"] = "启用和禁用突变器",
ru = "Включить и отключить мутаторы",
ja = "ミューテーターのオン/オフ",
}, },
no_mutators = { no_mutators = {
en = "No mutators installed", en = "No mutators installed",
es = "No hay mutaciones instaladas", es = "No hay mutaciones instaladas",
["zh-cn"] = "未安装突变器",
ru = "Нет установленных мутаторов",
ja = "ミューテーターがインストールされていません",
}, },
no_mutators_description = { no_mutators_description = {
en = "Subscribe to mods and mutators on the workshop", en = "Subscribe to mods and mutators on the workshop",
es = "Subscribete a mutaciones en el Steam Workshop", es = "Subscribete a mutaciones en el Steam Workshop",
["zh-cn"] = "在创意工坊订阅模组和突变器",
ru = "Подпишитесь на моды и мутаторы в мастерской Steam",
ja = "ワークショップでModやミューテーターをサブスクライブしてください",
}, },
-- Mutator widgets' tooltips -- Mutator widgets' tooltips
tooltip_incompatible_mutators = { tooltip_incompatible_mutators = {
en = "\n\n-- INCOMPATIBLE WITH MUTATORS --\n", en = "\n\n-- INCOMPATIBLE WITH MUTATORS --\n",
es = "\n\n-- INCOMPATIBLE CON LAS MUTACIONES --\n", es = "\n\n-- INCOMPATIBLE CON LAS MUTACIONES --\n",
["zh-cn"] = "\n\n-- 不兼容突变器 --\n",
ru = "\n\n-- НЕСОВМЕСТИМО С МУТАТОРАМИ --\n",
ja = "\n\n-- ミューテーターと互換性なし --\n",
}, },
tooltip_compatible_mutators = { tooltip_compatible_mutators = {
en = "\n\n-- COMPATIBLE ONLY WITH MUTATORS --\n", en = "\n\n-- COMPATIBLE ONLY WITH MUTATORS --\n",
es = "\n\n-- COMPATIBLE CON LAS MUTACIONES --\n", es = "\n\n-- COMPATIBLE CON LAS MUTACIONES --\n",
["zh-cn"] = "\n\n-- 仅兼容突变器 --\n",
ru = "\n\n-- СОВМЕСТИМО ТОЛЬКО С МУТАТОРАМИ --\n",
ja = "\n\n-- ミューテーターとのみ互換性あり",
}, },
tooltip_compatible_with_all_mutators = { tooltip_compatible_with_all_mutators = {
en = "\n\n-- COMPATIBLE WITH ALL MUTATORS --", en = "\n\n-- COMPATIBLE WITH ALL MUTATORS --",
es = "\n\n-- COMPATIBLE CON TODAS LAS MUTACIONES --", es = "\n\n-- COMPATIBLE CON TODAS LAS MUTACIONES --",
["zh-cn"] = "\n\n-- 兼容所有突变器 --\n",
ru = "\n\n-- СОВМЕСТИМО СО ВСЕМИ МУТАТОРАМИ --\n",
ja = "\n\n-- すべてのミューテーターと互換性あり --\n",
}, },
tooltip_incompatible_with_all_mutators = { tooltip_incompatible_with_all_mutators = {
en = "\n\n-- INCOMPATIBLE WITH ALL MUTATORS --", en = "\n\n-- INCOMPATIBLE WITH ALL MUTATORS --",
es = "\n\n-- INCOMPATIBLE CON TODAS LAS MUTACIONES --", es = "\n\n-- INCOMPATIBLE CON TODAS LAS MUTACIONES --",
["zh-cn"] = "\n\n-- 不兼容所有突变器 --\n",
ru = "\n\n-- НЕСОВМЕСТИМО СО ВСЕМИ МУТАТОРАМИ --\n",
ja = "\n\n-- すべてのミューテーターと互換性なし --\n",
}, },
tooltip_incompatible_diffs = { tooltip_incompatible_diffs = {
en = "\n\n-- INCOMPATIBLE WITH DIFFICULTIES --\n", en = "\n\n-- INCOMPATIBLE WITH DIFFICULTIES --\n",
es = "\n\n-- INCOMPATIBLE CON LAS DIFICULTADES --\n", es = "\n\n-- INCOMPATIBLE CON LAS DIFICULTADES --\n",
["zh-cn"] = "\n\n-- 不兼容难度 --\n",
ru = "\n\n-- НЕСОВМЕСТИМО СО СЛОЖНОСТЯМИ --\n",
ja = "\n\n-- 難易度と互換性なし --\n",
}, },
tooltip_compatible_diffs = { tooltip_compatible_diffs = {
en = "\n\n-- COMPATIBLE ONLY WITH DIFFICULTIES --\n", en = "\n\n-- COMPATIBLE ONLY WITH DIFFICULTIES --\n",
es = "\n\n-- COMPATIBLE CON LAS DIFICULTADES --\n", es = "\n\n-- COMPATIBLE CON LAS DIFICULTADES --\n",
["zh-cn"] = "\n\n-- 仅兼容难度 --\n",
ru = "\n\n-- СОВМЕСТИМО ТОЛЬКО СО СЛОЖНОСТЯМИ --\n",
ja = "\n\n-- 難易度とのみ互換性あり --\n",
}, },
tooltip_compatible_with_all_diffs = { tooltip_compatible_with_all_diffs = {
en = "\n\n-- COMPATIBLE WITH ALL DIFFICULTIES --", en = "\n\n-- COMPATIBLE WITH ALL DIFFICULTIES --",
es = "\n\n-- COMPATIBLE CON TODAS LAS DIFICULTADES --", es = "\n\n-- COMPATIBLE CON TODAS LAS DIFICULTADES --",
["zh-cn"] = "\n\n-- 兼容所有难度 --\n",
ru = "\n\n-- СОВМЕСТИМО СО ВСЕМИ СЛОЖНОСТЯМИ --\n",
ja = "\n\n-- すべての難易度と互換性あり --\n",
}, },
tooltip_conflicts = { tooltip_conflicts = {
en = "\n\n-- CONFLICTS --\n", en = "\n\n-- CONFLICTS --\n",
es = "\n\n-- CONFLICTOS --\n", es = "\n\n-- CONFLICTOS --\n",
["zh-cn"] = "\n\n-- 冲突 --\n",
ru = "\n\n-- КОНФЛИКТЫ --\n",
ja = "\n\n-- 競合 --\n",
}, },
tooltip_append_mutator = { tooltip_append_mutator = {
en = " (mutator)", en = " (mutator)",
es = " (mutacion)", es = " (mutacion)",
["zh-cn"] = "(突变)",
ru = " (мутатор)",
ja = " (ミューテーター)",
}, },
tooltip_append_difficulty = { tooltip_append_difficulty = {
en = " (difficulty)", en = " (difficulty)",
es = " (dificultad)", es = " (dificultad)",
["zh-cn"] = "(难度)",
ru = " (сложность)",
ja = " (難易度)",
}, },
} }

View file

@ -7,6 +7,9 @@ if not _io.initialized then
_io = dmf.deepcopy(Mods.lua.io) _io = dmf.deepcopy(Mods.lua.io)
end end
-- Local backup of the loadstring function
local _loadstring = Mods.lua.loadstring
local _mod_directory = "./../mods" local _mod_directory = "./../mods"
-- ##################################################################################################################### -- #####################################################################################################################
@ -62,7 +65,7 @@ local function read_or_execute(file_path, args, return_type)
-- Either execute the data or leave it unmodified -- Either execute the data or leave it unmodified
if return_type == "exec_result" or return_type == "exec_boolean" then if return_type == "exec_result" or return_type == "exec_boolean" then
local func = loadstring(result, file_path) local func = _loadstring(result, file_path)
result = func(args) result = func(args)
end end
end end

View file

@ -2,197 +2,27 @@ local dmf = get_mod("DMF")
local InputUtils = require("scripts/managers/input/input_utils") local InputUtils = require("scripts/managers/input/input_utils")
local PRIMARY_BINDABLE_KEYS = { local MODIFIER_KEYS = {
["keyboard"] = { ["keyboard_left shift"] = {160, "shift", "keyboard", 161},
[8] = {"Backspace", "backspace"}, ["keyboard_right shift"] = {160, "shift", "keyboard", 161},
[9] = {"Tab", "tab"}, ["keyboard_shift"] = {160, "shift", "keyboard", 161},
[13] = {"Enter", "enter"}, ["keyboard_left ctrl"] = {162, "ctrl", "keyboard", 163},
[20] = {"Caps Lock", "caps lock"}, ["keyboard_right ctrl"] = {162, "ctrl", "keyboard", 163},
[32] = {"Space", "space"}, ["keyboard_ctrl"] = {162, "ctrl", "keyboard", 163},
[33] = {"Page Up", "page up"}, ["keyboard_left alt"] = {164, "alt", "keyboard", 165},
[34] = {"Page Down", "page down"}, ["keyboard_right alt"] = {164, "alt", "keyboard", 165},
[35] = {"End", "end"}, ["keyboard_alt"] = {164, "alt", "keyboard", 165},
[36] = {"Home", "home"},
[37] = {"Left", "left"},
[38] = {"Up", "up"},
[39] = {"Right", "right"},
[40] = {"Down", "down"},
[45] = {"Insert", "insert"},
[46] = {"Delete", "delete"},
[48] = {"0", "0"},
[49] = {"1", "1"},
[50] = {"2", "2"},
[51] = {"3", "3"},
[52] = {"4", "4"},
[53] = {"5", "5"},
[54] = {"6", "6"},
[55] = {"7", "7"},
[56] = {"8", "8"},
[57] = {"9", "9"},
[65] = {"A", "a"},
[66] = {"B", "b"},
[67] = {"C", "c"},
[68] = {"D", "d"},
[69] = {"E", "e"},
[70] = {"F", "f"},
[71] = {"G", "g"},
[72] = {"H", "h"},
[73] = {"I", "i"},
[74] = {"J", "j"},
[75] = {"K", "k"},
[76] = {"L", "l"},
[77] = {"M", "m"},
[78] = {"N", "n"},
[79] = {"O", "o"},
[80] = {"P", "p"},
[81] = {"Q", "q"},
[82] = {"R", "r"},
[83] = {"S", "s"},
[84] = {"T", "t"},
[85] = {"U", "u"},
[86] = {"V", "v"},
[87] = {"W", "w"},
[88] = {"X", "x"},
[89] = {"Y", "y"},
[90] = {"Z", "z"},
[91] = {"Win", "win"},
[92] = {"RWin", "right win"},
[96] = {"Num 0", "numpad 0"},
[97] = {"Num 1", "numpad 1"},
[98] = {"Num 2", "numpad 2"},
[99] = {"Num 3", "numpad 3"},
[100] = {"Num 4", "numpad 4"},
[101] = {"Num 5", "numpad 5"},
[102] = {"Num 6", "numpad 6"},
[103] = {"Num 7", "numpad 7"},
[104] = {"Num 8", "numpad 8"},
[105] = {"Num 9", "numpad 9"},
[106] = {"Num *", "numpad *"},
[107] = {"Num +", "numpad +"},
[109] = {"Num -", "numpad -"},
[110] = {"Num .", "numpad ."},
[111] = {"Num /", "numpad /"},
[112] = {"F1", "f1"},
[113] = {"F2", "f2"},
[114] = {"F3", "f3"},
[115] = {"F4", "f4"},
[116] = {"F5", "f5"},
[117] = {"F6", "f6"},
[118] = {"F7", "f7"},
[119] = {"F8", "f8"},
[120] = {"F9", "f9"},
[121] = {"F10", "f10"},
[122] = {"F11", "f11"},
[123] = {"F12", "f12"},
[144] = {"Num Lock", "num lock"},
[145] = {"Scroll Lock", "scroll lock"},
[166] = {"Browser Back", "browser back"},
[167] = {"Browser Forward", "browser forward"},
[168] = {"Browser Refresh", "browser refresh"},
[169] = {"Browser Stop", "browser stop"},
[170] = {"Browser Search", "browser search"},
[171] = {"Browser Favorites", "browser favorites"},
[172] = {"Browser Home", "browser home"},
[173] = {"Volume Mute", "volume mute"},
[174] = {"Volume Down", "volume down"},
[175] = {"Volume Up", "volume up"},
[176] = {"Next Track", "next track"},
[177] = {"Previous Track", "previous track"},
[178] = {"Stop", "stop"},
[179] = {"Play/Pause", "play pause"},
[180] = {"Mail", "mail"},
[181] = {"Media", "media"},
[182] = {"Start Application 1", "start app 1"},
[183] = {"Start Application 2", "start app 2"},
[186] = {";", ";"},
[187] = {"=", "="},
[188] = {",", ","},
[189] = {"-", "-"},
[190] = {".", "."},
[191] = {"/", "/"},
[192] = {"`", "`"},
[219] = {"[", "["},
[220] = {"\\", "\\"},
[221] = {"]", "]"},
[222] = {"'", "'"},
--?[226] = {"\", "oem_102 (> <)"},
[256] = {"Num Enter", "numpad enter"}
},
["mouse"] = {
[0] = {"Mouse Left", "mouse left"},
[1] = {"Mouse Right", "mouse right"},
[2] = {"Mouse Middle", "mouse middle"},
[3] = {"Mouse Extra 1", "mouse extra 1"},
[4] = {"Mouse Extra 2", "mouse extra 2"},
[10] = {"Mouse Wheel Up", "mouse wheel up"},
[11] = {"Mouse Wheel Down", "mouse wheel down"},
[12] = {"Mouse Wheel Left", "mouse wheel left"},
[13] = {"Mouse Wheel Right", "mouse wheel right"}
},
--[[ -- will work on this if it will be needed
["gamepad"] = {
[0] = {"", "d_up"},
[1] = {"", "d_down"},
[2] = {"", "d_left"},
[3] = {"", "d_right"},
[4] = {"", "start"},
[5] = {"", "back"},
[6] = {"", "left_thumb"},
[7] = {"", "right_thumb"},
[8] = {"", "left_shoulder"},
[9] = {"", "right_shoulder"},
[10] = {"", "left_trigger"},
[11] = {"", "right_trigger"},
[12] = {"", "a"},
[13] = {"", "b"},
[14] = {"", "x"},
[15] = {"", "y"},
}]]
} }
local OTHER_KEYS = { -- Both are treated equally in keybinds, but these global keys aren't localized
-- modifier keys local MODIFIER_KEYS_LOC_ALIAS = {
["left shift"] = {160, "Shift", "keyboard", 161}, keyboard_ctrl = "keyboard_left ctrl",
["right shift"] = {160, "Shift", "keyboard", 161}, keyboard_alt = "keyboard_left alt",
["shift"] = {160, "Shift", "keyboard", 161}, keyboard_shift = "keyboard_left shift",
["left ctrl"] = {162, "Ctrl", "keyboard", 163},
["right ctrl"] = {162, "Ctrl", "keyboard", 163},
["ctrl"] = {162, "Ctrl", "keyboard", 163},
["left alt"] = {164, "Alt", "keyboard", 165},
["right alt"] = {164, "Alt", "keyboard", 165},
["alt"] = {164, "Alt", "keyboard", 165},
-- hack for 'dmf.build_keybind_string' function
["no_button"] = {-1, ""}
}
local KEYS_INFO = {}
-- Populate KEYS_INFO: Index, readable name, device name
for input_device_name, input_device_keys in pairs(PRIMARY_BINDABLE_KEYS) do
for key_index, key_info in pairs(input_device_keys) do
KEYS_INFO[key_info[2]] = {key_index, key_info[1], input_device_name}
end
end
for key_id, key_data in pairs(OTHER_KEYS) do
KEYS_INFO[key_id] = key_data
end
-- Can't use 'Device.released' because it will break keybinds if button is released when game window is not active.
local CHECK_INPUT_FUNCTIONS = {
keyboard = {
PRESSED = function(key_id) return Keyboard.pressed(KEYS_INFO[key_id][1]) end,
RELEASED = function(key_id) return Keyboard.button(KEYS_INFO[key_id][1]) == 0 end
},
mouse = {
PRESSED = function(key_id) return Mouse.pressed(KEYS_INFO[key_id][1]) end,
RELEASED = function(key_id) return Mouse.button(KEYS_INFO[key_id][1]) == 0 end
}
} }
local _raw_keybinds_data = {} local _raw_keybinds_data = {}
local _keybinds = {} local _keybinds = {}
local _pressed_key
local ERRORS = { local ERRORS = {
PREFIX = { PREFIX = {
@ -203,12 +33,17 @@ local ERRORS = {
} }
} }
local SUPPORTED_DEVICES = {
"keyboard",
"mouse",
}
-- ##################################################################################################################### -- #####################################################################################################################
-- ##### Local functions ############################################################################################### -- ##### Local functions ###############################################################################################
-- ##################################################################################################################### -- #####################################################################################################################
-- @TODO: Link this input service to the player's input service and find some way to see if it's blocked
local function is_dmf_input_service_active() local function is_dmf_input_service_active()
-- @TODO: Implement check for active DMF input service
return true return true
end end
@ -239,115 +74,261 @@ local function perform_keybind_action(data, is_pressed)
end end
end end
-- #####################################################################################################################
-- ##### Input service functions #######################################################################################
-- #####################################################################################################################
local function is_modifier_pressed(modifier)
return (Keyboard.button(MODIFIER_KEYS[modifier][1]) + Keyboard.button(MODIFIER_KEYS[modifier][4])) > 0
end
local function default_boolean()
return false
end
local function default_vector3()
return Vector3(0, 0, 0)
end
local function default_float()
return 0
end
local function boolean_combine(value_one, value_two)
return value_one or value_two
end
local function vector3_combine(value_one, value_two)
if Vector3.length(value_two) < Vector3.length(value_one) then
return value_one
else
return value_two
end
end
local function float_combine(value_one, value_two)
return math.max(value_one, value_two)
end
local ACTION_TYPES = {
pressed = {
device_func = "pressed",
type = "boolean",
default_device_func = default_boolean,
combine_func = boolean_combine
},
held = {
device_func = "held",
type = "boolean",
default_device_func = default_boolean,
combine_func = boolean_combine
},
released = {
device_func = "released",
type = "boolean",
default_device_func = default_boolean,
combine_func = boolean_combine
},
axis = {
device_func = "axis",
type = "vector3",
default_device_func = default_vector3,
combine_func = vector3_combine
},
button = {
device_func = "button",
type = "float",
default_device_func = default_float,
combine_func = float_combine
}
}
local function _enabler_func(cb, action_type, enablers)
for _, enabler in ipairs(enablers) do
if not enabler.device:held(enabler.index) then
return ACTION_TYPES[action_type].default_device_func()
end
end
return cb()
end
local function _disabler_func(cb, action_type, disablers)
for _, disabler in ipairs(disablers) do
if disabler.device:held(disabler.index) then
return ACTION_TYPES[action_type].default_device_func()
end
end
return cb()
end
local function get_corresponding_device(key_id)
for _, device_type in pairs(SUPPORTED_DEVICES) do
local device = Managers.input:_find_active_device(device_type)
if device then
local index = device:button_index(key_id)
if index then
return {
device = device,
index = index,
key_id = key_id,
}
end
index = device:axis_index(key_id)
if index then
return {
device = device,
index = index,
key_id = key_id,
}
end
end
end
end
local function get_key_info(keybind)
local main = keybind.main
local e = keybind.enablers
local d = keybind.disablers
local info = get_corresponding_device(main)
if info then
if #e > 0 then
local enablers = {}
for _, key in ipairs(e) do
local enabler = get_corresponding_device(key)
if enabler then
enablers[#enablers + 1] = enabler
end
end
info.enablers = enablers
end
if #d > 0 then
local disablers = {}
for _, key in ipairs(d) do
local disabler = get_corresponding_device(key)
if disabler then
disablers[#disablers + 1] = disabler
end
end
info.disablers = disablers
end
end
return info
end
local function create_eval_func(keybind)
local info = get_key_info(keybind)
if info then
local eval_func
if keybind.trigger == "held" then
eval_func = callback(info.device, keybind.trigger, info.index)
else
local function func(device, trigger, index)
return device[trigger](index)
end
eval_func = callback(func, info.device:raw_device(), keybind.trigger, info.index)
end
if info.enablers then
eval_func = callback(_enabler_func, eval_func, keybind.trigger, info.enablers)
end
if info.disablers then
eval_func = callback(_disabler_func, eval_func, keybind.trigger, info.disablers)
end
return eval_func
end
end
-- ##################################################################################################################### -- #####################################################################################################################
-- ##### DMF internal functions and variables ########################################################################## -- ##### DMF internal functions and variables ##########################################################################
-- ##################################################################################################################### -- #####################################################################################################################
-- Checks for pressed and released keybinds, performs keybind actions. -- Checks for pressed and released keybinds, performs keybind actions.
-- * Checks for both right and left key modifiers (ctrl, alt, shift). -- * Right and left key modifiers (ctrl, alt, shift) are checked separately.
-- * If some keybind is pressed, won't check for other keybinds until this keybind is released. -- * If several mods bound the same keys, keybind action will be performed for all of them when pressed.
-- * If several mods bound the same keys, keybind action will be performed for all of them, when keybind is pressed. -- * Keybind is considered released when it was previously pressed and is no longer.
-- * Keybind is considered released, when its primary key is released.
function dmf.check_keybinds() function dmf.check_keybinds()
local ctrl_pressed = (Keyboard.button(KEYS_INFO["ctrl"][1]) + Keyboard.button(KEYS_INFO["ctrl"][4])) > 0
local alt_pressed = (Keyboard.button(KEYS_INFO["alt"][1]) + Keyboard.button(KEYS_INFO["alt"][4])) > 0
local shift_pressed = (Keyboard.button(KEYS_INFO["shift"][1]) + Keyboard.button(KEYS_INFO["shift"][4])) > 0
if not _pressed_key then -- For every keybind
for primary_key, keybinds_data in pairs(_keybinds) do for _, keybind_data in ipairs(_keybinds) do
if keybinds_data.check_pressed(primary_key) then
for _, keybind_data in ipairs(keybinds_data) do
local all_pressed = true -- If the keybind is pressed
for enabler, _ in pairs(keybind_data.enablers) do if keybind_data.eval_func() then
-- Check that every enabler key is pressed -- Peform the keybind action once
if OTHER_KEYS[enabler] and if (not keybind_data.pressed) and perform_keybind_action(keybind_data, true) then
(
Keyboard.button(KEYS_INFO[enabler][1]) +
Keyboard.button(KEYS_INFO[enabler][4])
) <= 0
or not (Keyboard.button(KEYS_INFO[enabler][1]) > 0)
then
all_pressed = false
break
end
end
-- Check that no modifier keys are pressed that shouldn't be -- Queue the release action if applicable
if all_pressed and
(not keybind_data.ctrl and ctrl_pressed) and
(not keybind_data.alt and alt_pressed) and
(not keybind_data.shift and shift_pressed)
then
all_pressed = false
end
-- Peform the keybind action if everything validates
if all_pressed then
if perform_keybind_action(keybind_data, true) then
if keybind_data.trigger == "held" then if keybind_data.trigger == "held" then
keybind_data.release_action = true keybind_data.release_action = true
end end
_pressed_key = primary_key
end -- Prevent a repeat action
end keybind_data.pressed = true
end
if _pressed_key then
break
end
end
end
end end
if _pressed_key then -- If the keybind was previously but is no longer pressed
if _keybinds[_pressed_key].check_released(_pressed_key) then elseif keybind_data.pressed then
for _, keybind_data in ipairs(_keybinds[_pressed_key]) do
-- Reactivate the keybind
keybind_data.pressed = nil
-- Play the release action if applicable
if keybind_data.release_action then if keybind_data.release_action then
perform_keybind_action(keybind_data, false) perform_keybind_action(keybind_data, false)
keybind_data.release_action = nil keybind_data.release_action = nil
end end
end end
_pressed_key = nil
end
end end
end end
-- Converts managable (raw) table of keybinds data to the table designed for the function checking for pressed and -- Converts manageable (raw) table of keybinds data to a table of callbacks for checking pressed and
-- released keybinds. After initial call requires to be called every time some keybind is added/removed. -- released keybinds. After initial call it must be called every time some keybind is added/removed.
function dmf.generate_keybinds() function dmf.generate_keybinds()
_keybinds = {} _keybinds = {}
for mod, mod_keybinds in pairs(_raw_keybinds_data) do for mod, mod_keybinds in pairs(_raw_keybinds_data) do
for _, raw_keybind_data in pairs(mod_keybinds) do for _, raw_keybind_data in pairs(mod_keybinds) do
local keys = raw_keybind_data.keys
local primary_key = keys[1]
local modifier_keys = {}
for i = 2, #keys do
modifier_keys[keys[i]] = true
end
local keybind_data = { local keybind_data = {
mod = mod, mod = mod,
global = raw_keybind_data.global, global = raw_keybind_data.global,
trigger = raw_keybind_data.trigger, trigger = raw_keybind_data.trigger,
type = raw_keybind_data.type, type = raw_keybind_data.type,
enablers = modifier_keys, eval_func = create_eval_func(raw_keybind_data),
ctrl = modifier_keys["ctrl"] or modifier_keys["left ctrl"] or modifier_keys["right ctrl"],
alt = modifier_keys["alt"] or modifier_keys["left alt"] or modifier_keys["right alt"],
shift = modifier_keys["shift"] or modifier_keys["left shift"] or modifier_keys["right shift"],
function_name = raw_keybind_data.function_name, function_name = raw_keybind_data.function_name,
view_name = raw_keybind_data.view_name view_name = raw_keybind_data.view_name
} }
_keybinds[primary_key] = _keybinds[primary_key] or { if keybind_data.eval_func then
check_pressed = CHECK_INPUT_FUNCTIONS[KEYS_INFO[primary_key][3]].PRESSED, table.insert(_keybinds, keybind_data)
check_released = CHECK_INPUT_FUNCTIONS[KEYS_INFO[primary_key][3]].RELEASED end
}
table.insert(_keybinds[primary_key], keybind_data)
end end
end end
end end
@ -355,7 +336,7 @@ end
-- Adds/removes keybinds. -- Adds/removes keybinds.
function dmf.add_mod_keybind(mod, setting_id, raw_keybind_data) function dmf.add_mod_keybind(mod, setting_id, raw_keybind_data)
if #raw_keybind_data.keys > 0 then if raw_keybind_data.main then
_raw_keybinds_data[mod] = _raw_keybinds_data[mod] or {} _raw_keybinds_data[mod] = _raw_keybinds_data[mod] or {}
_raw_keybinds_data[mod][setting_id] = raw_keybind_data _raw_keybinds_data[mod][setting_id] = raw_keybind_data
elseif _raw_keybinds_data[mod] and _raw_keybinds_data[mod][setting_id] then elseif _raw_keybinds_data[mod] and _raw_keybinds_data[mod][setting_id] then
@ -371,55 +352,32 @@ end
-- Creates DMF input service. It is required to know when non-global keybinds can be triggered. -- Creates DMF 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) -- (Called every time a level is loaded, or on mods reload)
function dmf.create_keybinds_input_service()
-- @TODO: Link this input service to the player's input service and find some way to see if it's blocked -- @TODO: Link this input service to the player's input service and find some way to see if it's blocked
--[[ function dmf.create_keybinds_input_service()
-- To create the DMF input service in Darktide -- -- To create the DMF input service in Darktide
local input_manager = Managers.input -- local input_manager = Managers.input
local service_type = "DMF" -- local service_type = "DMF"
input_manager:add_setting(service_type, aliases, raw_key_table, filter_table, default_devices) -- input_manager:add_setting(service_type, aliases, raw_key_table, filter_table, default_devices)
input_manager:get_input_service(service_type) -- input_manager:get_input_service(service_type)
--]]
end end
-- Converts key_index to readable key_id, which is used by DMF to identify keys. -- @TODO: So far it seems like any key can be a primary keybind, but is this actually true?
-- (Used for capturing keybinds)
function dmf.get_key_id(device, key_index)
local key_info = PRIMARY_BINDABLE_KEYS[device][key_index]
return key_info and key_info[2]
end
-- Simply tells if key with key_id can be binded as primary key.
-- (Used for verifying keybind widgets)
function dmf.can_bind_as_primary_key(key_id) function dmf.can_bind_as_primary_key(key_id)
return KEYS_INFO[key_id] and not OTHER_KEYS[key_id] return true
end end
-- Builds string with readable keys' names to look like "Primary Key + Ctrl + Alt + Shift". -- Translate keywatch result to array of local key names
-- (Used in keybind widget) -- (Used in keybind widget for compatibility with legacy settings)
function dmf.build_keybind_string(keys) function dmf.keywatch_result_to_local_keys(keywatch_result)
local readable_key_names = {}
for _, key_id in ipairs(keys) do
table.insert(readable_key_names, KEYS_INFO[key_id][2])
end
return table.concat(readable_key_names, " + ")
end
-- Translate key watch result to mod options keybind
-- (Used in keybind widget)
function dmf.keybind_result_to_keys(keybind_result)
local keys = {} local keys = {}
-- Get the local name of the main key -- Get the local name of the main key
if keybind_result.main then if keywatch_result.main then
local global_name = keybind_result.main local global_name = keywatch_result.main
local device_type = InputUtils.key_device_type(global_name) local local_name = InputUtils.local_key_name(global_name, InputUtils.key_device_type(global_name))
local local_name = InputUtils.local_key_name(global_name, device_type)
-- Check for a missing or unbindable primary key name -- Check for a missing or unbindable primary key name
if not local_name or not dmf.can_bind_as_primary_key(local_name) then if not local_name or not dmf.can_bind_as_primary_key(local_name) then
@ -430,12 +388,9 @@ function dmf.keybind_result_to_keys(keybind_result)
end end
-- Add the enablers keys as additional keys -- Add the enablers keys as additional keys
if keybind_result.enablers then if keywatch_result.enablers then
for _, global_name in ipairs(keybind_result.enablers) do for _, global_name in ipairs(keywatch_result.enablers) do
local local_name = InputUtils.local_key_name(global_name, InputUtils.key_device_type(global_name))
local device_type = InputUtils.key_device_type(global_name)
local local_name = InputUtils.local_key_name(global_name, device_type)
keys[#keys + 1] = local_name keys[#keys + 1] = local_name
end end
end end
@ -444,41 +399,63 @@ function dmf.keybind_result_to_keys(keybind_result)
end end
-- Translate mod options keybind to key watch result -- Translate array of local key names to keywatch result
-- (Used in keybind widget) -- (Used in keybind widget for compatibility with legacy settings)
function dmf.keys_to_keybind_result(keys) function dmf.local_keys_to_keywatch_result(keys)
local keybind_result = { local keywatch_result = {
enablers = {}, enablers = {},
disablers = {} disablers = {}
} }
if not keys or #keys == 0 then if type(keys) ~= "table" or #keys == 0 then
return nil return nil
end end
if keys[1] then if keys[1] then
local local_name = keys[1] local local_name = keys[1]
local global_name = KEYS_INFO[local_name] and InputUtils.local_to_global_name(local_name, KEYS_INFO[local_name][3]) local global_name
-- Check all supported devices for the global name
for _, device_type in ipairs(SUPPORTED_DEVICES) do
global_name = InputUtils.local_to_global_name(local_name, device_type)
if get_corresponding_device(global_name) then
break
end
end
-- End early if our main key doesn't exist, and return an empty result -- End early if our main key doesn't exist, and return an empty result
if not global_name then if not global_name then
return nil return nil
end end
keybind_result.main = global_name if MODIFIER_KEYS_LOC_ALIAS[global_name] then
global_name = MODIFIER_KEYS_LOC_ALIAS[global_name]
end
keywatch_result.main = global_name
end end
-- Add all remaining keys to the enablers list -- Add all remaining keys to the enablers list
for i = 2, #keys do for i = 2, #keys do
local local_name = keys[i] local local_name = keys[i]
local global_name = KEYS_INFO[local_name] and InputUtils.local_to_global_name(local_name, KEYS_INFO[local_name][3]) local global_name
-- Check all supported devices for the global name
for _, device_type in ipairs(SUPPORTED_DEVICES) do
global_name = InputUtils.local_to_global_name(local_name,device_type)
if get_corresponding_device(global_name) then
break
end
end
if global_name then if global_name then
keybind_result.enablers[#keybind_result.enablers + 1] = global_name if MODIFIER_KEYS_LOC_ALIAS[global_name] then
global_name = MODIFIER_KEYS_LOC_ALIAS[global_name]
end
keywatch_result.enablers[#keywatch_result.enablers + 1] = global_name
end end
end end
return keybind_result return keywatch_result
end end
-- ##################################################################################################################### -- #####################################################################################################################

View file

@ -91,13 +91,12 @@ end
-- ##### DMF internal functions and variables ######################################################################### -- ##### DMF internal functions and variables #########################################################################
-- #################################################################################################################### -- ####################################################################################################################
-- Handles the return of global localize text_ids dmf:hook(LocalizationManager, "localize", function (func, self, text_id, ...)
dmf:hook(_G, "Localize", function (func, text_id, ...)
local text_translations = text_id and _global_localization_database[text_id] local text_translations = text_id and _global_localization_database[text_id]
local message = get_translated_or_english_message(nil, text_translations, ...) local message = get_translated_or_english_message(nil, text_translations, ...)
return message or func(text_id, ...) return message or func(self, text_id, ...)
end) end)
-- #################################################################################################################### -- ####################################################################################################################

View file

@ -122,6 +122,7 @@ local function initialize_generic_widget_data(mod, data, localize)
new_data.title = data.title -- optional, if (localize == true) new_data.title = data.title -- optional, if (localize == true)
new_data.tooltip = data.tooltip -- optional new_data.tooltip = data.tooltip -- optional
new_data.default_value = data.default_value new_data.default_value = data.default_value
new_data.require_restart = data.require_restart -- optional
-- Overwrite global optons localization setting if widget defined it -- Overwrite global optons localization setting if widget defined it
if data.localize == nil then if data.localize == nil then
@ -300,11 +301,6 @@ local allowed_keybind_types = {
view_toggle = true, view_toggle = true,
mod_toggle = true mod_toggle = true
} }
local allowed_modifier_keys = {
ctrl = true,
alt = true,
shift = true
}
local function validate_keybind_data(data) local function validate_keybind_data(data)
if data.keybind_global and type(data.keybind_global) ~= "boolean" then if data.keybind_global and type(data.keybind_global) ~= "boolean" then
dmf.throw_error("[widget \"%s\" (keybind)]: 'keybind_global' field must have 'boolean' type", data.setting_id) dmf.throw_error("[widget \"%s\" (keybind)]: 'keybind_global' field must have 'boolean' type", data.setting_id)
@ -343,13 +339,6 @@ local function validate_keybind_data(data)
if default_value[1] and not dmf.can_bind_as_primary_key(default_value[1]) then if default_value[1] and not dmf.can_bind_as_primary_key(default_value[1]) then
dmf.throw_error("[widget \"%s\" (keybind)]: 'default_value[1]' must be a valid key name", data.setting_id) dmf.throw_error("[widget \"%s\" (keybind)]: 'default_value[1]' must be a valid key name", data.setting_id)
end end
if default_value[2] and not allowed_modifier_keys[default_value[2]] or
default_value[3] and not allowed_modifier_keys[default_value[3]] or
default_value[4] and not allowed_modifier_keys[default_value[4]]
then
dmf.throw_error("[widget \"%s\" (keybind)]: 'default_value [2], [3] and [4]' can be only strings: \"ctrl\", " ..
"\"alt\" and \"shift\" (in no particular order)", data.setting_id)
end
local used_keys = {} local used_keys = {}
for _, key in ipairs(default_value) do for _, key in ipairs(default_value) do
@ -545,6 +534,8 @@ local function initialize_default_settings_and_keybinds(mod, initialized_widgets
mod:set(data.setting_id, data.default_value) mod:set(data.setting_id, data.default_value)
end end
if data.type == "keybind" then if data.type == "keybind" then
local keywatch_result = dmf.local_keys_to_keywatch_result(mod:get(data.setting_id))
if keywatch_result and keywatch_result.main then
dmf.add_mod_keybind( dmf.add_mod_keybind(
mod, mod,
data.setting_id, data.setting_id,
@ -552,7 +543,9 @@ local function initialize_default_settings_and_keybinds(mod, initialized_widgets
global = data.keybind_global, global = data.keybind_global,
trigger = data.keybind_trigger, trigger = data.keybind_trigger,
type = data.keybind_type, type = data.keybind_type,
keys = mod:get(data.setting_id), main = keywatch_result.main,
enablers = keywatch_result.enablers,
disablers = keywatch_result.disablers,
function_name = data.function_name, function_name = data.function_name,
view_name = data.view_name view_name = data.view_name
} }
@ -560,6 +553,7 @@ local function initialize_default_settings_and_keybinds(mod, initialized_widgets
end end
end end
end end
end
-- ##################################################################################################################### -- #####################################################################################################################
-- ##### DMF internal functions and variables ########################################################################## -- ##### DMF internal functions and variables ##########################################################################

View file

@ -87,7 +87,7 @@ function dmf.safe_call_dofile(mod, error_prefix_data, file_path)
show_error(mod, error_prefix_data, "file path should be a string.") show_error(mod, error_prefix_data, "file path should be a string.")
return false return false
end end
return dmf.safe_call(mod, error_prefix_data, dofile, mod, file_path) return dmf.safe_call(mod, error_prefix_data, dofile, file_path)
end end

View file

@ -49,8 +49,10 @@ local function close_dev_console()
CommandWindow.close() CommandWindow.close()
-- CommandWindow won't close by itself, so it have to be closed manually -- CommandWindow won't close by itself, so it has to be closed through FFI
if _ffi then
dmf:pcall(function() dmf:pcall(function()
if _ffi then
_ffi.cdef([[ _ffi.cdef([[
void* FindWindowA(const char* lpClassName, const char* lpWindowName); void* FindWindowA(const char* lpClassName, const char* lpWindowName);
int64_t SendMessageA(void* hWnd, unsigned int Msg, uint64_t wParam, int64_t lParam); int64_t SendMessageA(void* hWnd, unsigned int Msg, uint64_t wParam, int64_t lParam);
@ -58,8 +60,14 @@ local function close_dev_console()
local WM_CLOSE = 0x10; local WM_CLOSE = 0x10;
local hwnd = _ffi.C.FindWindowA("ConsoleWindowClass", "Developer console") local hwnd = _ffi.C.FindWindowA("ConsoleWindowClass", "Developer console")
_ffi.C.SendMessageA(hwnd, WM_CLOSE, 0, 0) _ffi.C.SendMessageA(hwnd, WM_CLOSE, 0, 0)
end
end) end)
-- Or manually closed by the user
else
dmf:warning(dmf:localize("dev_console_close_warning"))
end
_console_data.enabled = false _console_data.enabled = false
end end
end end

View file

@ -7,7 +7,7 @@ if not _io.initialized then
_io = dmf.deepcopy(Mods.lua.io) _io = dmf.deepcopy(Mods.lua.io)
end end
-- Local backup of the io library -- Local backup of the os library
local _os = dmf:persistent_table("_os") local _os = dmf:persistent_table("_os")
_os.initialized = _os.initialized or false _os.initialized = _os.initialized or false
if not _os.initialized then if not _os.initialized then
@ -17,6 +17,11 @@ end
-- Global backup of original print() method -- Global backup of original print() method
local print = __print local print = __print
local function log_and_console_print(message, ...)
print(message, ...)
CommandWindow.print(message, ...)
end
local function table_dump(key, value, depth, max_depth) local function table_dump(key, value, depth, max_depth)
if max_depth < depth then if max_depth < depth then
return return
@ -28,7 +33,7 @@ local function table_dump(key, value, depth, max_depth)
if value_type == "table" then if value_type == "table" then
prefix = prefix .. ((key == nil and "") or " = ") prefix = prefix .. ((key == nil and "") or " = ")
print(prefix .. "table") log_and_console_print(prefix .. "table")
for key_, value_ in pairs(value) do for key_, value_ in pairs(value) do
table_dump(key_, value_, depth + 1, max_depth) table_dump(key_, value_, depth + 1, max_depth)
@ -38,9 +43,9 @@ local function table_dump(key, value, depth, max_depth)
if meta then if meta then
if type(meta) == "boolean" then if type(meta) == "boolean" then
print(prefix .. "protected metatable") log_and_console_print(prefix .. "protected metatable")
else else
print(prefix .. "metatable") log_and_console_print(prefix .. "metatable")
for key_, value_ in pairs(meta) do for key_, value_ in pairs(meta) do
if key_ ~= "__index" and key_ ~= "super" then if key_ ~= "__index" and key_ ~= "super" then
table_dump(key_, value_, depth + 1, max_depth) table_dump(key_, value_, depth + 1, max_depth)
@ -49,24 +54,28 @@ local function table_dump(key, value, depth, max_depth)
end end
end end
elseif value_type == "function" or value_type == "thread" or value_type == "userdata" or value == nil then elseif value_type == "function" or value_type == "thread" or value_type == "userdata" or value == nil then
print(prefix .. " = " .. tostring(value)) log_and_console_print(prefix .. " = " .. tostring(value))
else else
print(prefix .. " = " .. tostring(value) .. " (" .. value_type .. ")") log_and_console_print(prefix .. " = " .. tostring(value) .. " (" .. value_type .. ")")
end end
end end
DMFMod.dump = function (self, dumped_object, dumped_object_name, max_depth) DMFMod.dump = function (self, dumped_object, object_name, max_depth)
if dmf.check_wrong_argument_type(self, "dump", "dumped_object_name", object_name, "string", "nil") or
if dmf.check_wrong_argument_type(self, "dump", "dumped_object_name", dumped_object_name, "string", "nil") or dmf.check_wrong_argument_type(self, "dump", "max_depth", max_depth, "number", "nil")
dmf.check_wrong_argument_type(self, "dump", "max_depth", max_depth, "number")
then then
return return
end end
local object_type = type(dumped_object) local object_type = type(dumped_object)
max_depth = max_depth or 1
if object_type ~= "table" then if object_type ~= "table" then
local error_message = "(dump): \"object_name\" is not a table. It's " .. object_type local error_message = string.format(
'(dump): "%s" is not a table but of type "%s"',
object_name or "Dump object",
object_type
)
if object_type ~= "nil" then if object_type ~= "nil" then
error_message = error_message .. " (" .. tostring(dumped_object) .. ")" error_message = error_message .. " (" .. tostring(dumped_object) .. ")"
@ -76,13 +85,8 @@ DMFMod.dump = function (self, dumped_object, dumped_object_name, max_depth)
return return
end end
if dumped_object_name then if object_name then
print(string.format("<%s>", dumped_object_name)) log_and_console_print(string.format("<%s>", object_name))
end
if not max_depth then
self:error("(dump): maximum depth is not specified")
return
end end
local success, error_message = pcall(function() local success, error_message = pcall(function()
@ -95,17 +99,12 @@ DMFMod.dump = function (self, dumped_object, dumped_object_name, max_depth)
self:error("(dump): %s", tostring(error_message)) self:error("(dump): %s", tostring(error_message))
end end
if dumped_object_name then if object_name then
print(string.format("</%s>", dumped_object_name)) log_and_console_print(string.format("</%s>", object_name))
end end
end end
local function table_dump_to_file(dumped_table, dumped_table_name, max_depth) local function table_dump_to_file(dumped_table, dumped_table_name, max_depth)
-- ##################### -- #####################
@ -343,17 +342,22 @@ end
DMFMod.dump_to_file = function (self, dumped_object, object_name, max_depth) DMFMod.dump_to_file = function (self, dumped_object, object_name, max_depth)
if dmf.check_wrong_argument_type(self, "dump_to_file", "object_name", object_name, "string", "nil") or
if dmf.check_wrong_argument_type(self, "dump_to_file", "object_name", object_name, "string") or dmf.check_wrong_argument_type(self, "dump_to_file", "max_depth", max_depth, "number", "nil")
dmf.check_wrong_argument_type(self, "dump_to_file", "max_depth", max_depth, "number")
then then
return return
end end
local object_type = type(dumped_object) local object_type = type(dumped_object)
object_name = object_name or "mod_dump_to_file"
max_depth = max_depth or 1
if object_type ~= "table" then if object_type ~= "table" then
local error_message = "(dump_to_file): \"object_name\" is not a table. It's " .. object_type local error_message = string.format(
'(dump_to_file): "%s" is not a table but of type "%s"',
object_name or "Dump object",
object_type
)
if object_type ~= "nil" then if object_type ~= "nil" then
error_message = error_message .. " (" .. tostring(dumped_object) .. ")" error_message = error_message .. " (" .. tostring(dumped_object) .. ")"

View file

@ -169,7 +169,7 @@ function dmf.initialize_mod_data(mod, mod_data)
end end
-- Mod's options initialization -- 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 if mod_data.options or (not mod_data.is_mutator and not mod_data.options_widgets) then
local success, error_message = pcall(dmf.initialize_mod_options, mod, mod_data.options) local success, error_message = pcall(dmf.initialize_mod_options, mod, mod_data.options)
if not success then if not success then
mod:error(ERRORS.REGULAR.mod_options_initializing_failed, error_message) mod:error(ERRORS.REGULAR.mod_options_initializing_failed, error_message)

View file

@ -16,7 +16,7 @@ dmf_mod_data.options = {
setting_id = "dmf_options_scrolling_speed", setting_id = "dmf_options_scrolling_speed",
type = "numeric", type = "numeric",
default_value = 100, default_value = 100,
range = {1, 1000}, range = {50, 500},
unit_text = "percent" unit_text = "percent"
}, },
{ {
@ -38,16 +38,16 @@ dmf_mod_data.options = {
keybind_type = "function_call", keybind_type = "function_call",
function_name = "toggle_developer_console" function_name = "toggle_developer_console"
}, },
{ -- {
setting_id = "show_network_debug_info", -- setting_id = "show_network_debug_info",
type = "checkbox", -- type = "checkbox",
default_value = false -- default_value = false
}, -- },
{ -- {
setting_id = "log_ui_renderers_info", -- setting_id = "log_ui_renderers_info",
type = "checkbox", -- type = "checkbox",
default_value = false -- default_value = false
} -- }
} }
}, },
{ {

View file

@ -7,6 +7,8 @@ local _custom_views_data = {}
local _ingame_ui local _ingame_ui
local _loaded_views = {} local _loaded_views = {}
local _key_watch = false
local ERRORS = { local ERRORS = {
THROWABLE = { THROWABLE = {
-- inject_view: -- inject_view:
@ -312,13 +314,13 @@ end
-- Track the creation of the view loader -- Track the creation of the view loader
dmf:hook_safe(ViewLoader, "init", function() dmf:hook_safe(CLASS.ViewLoader, "init", function()
_custom_view_persistent_data.loader_initialized = true _custom_view_persistent_data.loader_initialized = true
end) end)
-- Track the loading of views, set the loader flag if class selection is reached -- Track the loading of views, set the loader flag if class selection is reached
dmf:hook_safe(UIManager, "load_view", function(self, view_name) dmf:hook_safe(CLASS.UIManager, "load_view", function(self, view_name)
if view_name == "class_selection_view" then if view_name == "class_selection_view" then
_custom_view_persistent_data.loader_initialized = true _custom_view_persistent_data.loader_initialized = true
end end
@ -326,13 +328,13 @@ dmf:hook_safe(UIManager, "load_view", function(self, view_name)
end) end)
-- Track the unloading of views -- Track the unloading of views
dmf:hook_safe(UIManager, "unload_view", function(self, view_name) dmf:hook_safe(CLASS.UIManager, "unload_view", function(self, view_name)
_loaded_views[view_name] = nil _loaded_views[view_name] = nil
end) end)
-- Store the view handler for later use and inject views -- Store the view handler for later use and inject views
dmf:hook_safe(UIViewHandler, "init", function(self) dmf:hook_safe(CLASS.UIViewHandler, "init", function(self)
_ingame_ui = self _ingame_ui = self
for view_name, _ in pairs(_custom_views_data) do for view_name, _ in pairs(_custom_views_data) do
if not dmf.safe_call_nrc(self, {ERRORS.PREFIX.ingameui_hook_injection, view_name}, inject_view, view_name) then if not dmf.safe_call_nrc(self, {ERRORS.PREFIX.ingameui_hook_injection, view_name}, inject_view, view_name) then
@ -341,6 +343,16 @@ dmf:hook_safe(UIViewHandler, "init", function(self)
end end
end) end)
-- Track the start of key watches
dmf:hook_safe(CLASS.InputManager, "start_key_watch", function(self)
_key_watch = true
end)
-- Track the end of key watches
dmf:hook_safe(CLASS.InputManager, "stop_key_watch", function(self)
_key_watch = false
end)
-- ##################################################################################################################### -- #####################################################################################################################
-- ##### DMF internal functions and variables ########################################################################## -- ##### DMF internal functions and variables ##########################################################################
-- ##################################################################################################################### -- #####################################################################################################################
@ -368,8 +380,8 @@ function dmf.keybind_toggle_view(mod, view_name, keybind_transition_data, can_pe
-- If the view is open, this is a toggle close -- If the view is open, this is a toggle close
if Managers.ui:view_active(view_name) then if Managers.ui:view_active(view_name) then
-- Don't close the view if it's already closing -- Don't close the view if it's already closing or we have an active key watch
if not Managers.ui:is_view_closing(view_name) then if not Managers.ui:is_view_closing(view_name) and not _key_watch then
local force_close = true local force_close = true
Managers.ui:close_view(view_name, force_close) Managers.ui:close_view(view_name, force_close)
end end

View file

@ -80,8 +80,6 @@ end
dmf:hook("ConstantElementChat", "_handle_active_chat_input", function(func, self, input_service, ui_renderer, ...) dmf:hook("ConstantElementChat", "_handle_active_chat_input", function(func, self, input_service, ui_renderer, ...)
initialize_drawing_function() initialize_drawing_function()
local command_executed = false
_chat_message = get_chat_message(self) _chat_message = get_chat_message(self)
_chat_opened = true _chat_opened = true
@ -128,8 +126,6 @@ dmf:hook("ConstantElementChat", "_handle_active_chat_input", function(func, self
set_chat_message(self, "") set_chat_message(self, "")
command_executed = true
elseif string.sub(_chat_message, 1, 1) == "/" then elseif string.sub(_chat_message, 1, 1) == "/" then
dmf:notify(dmf:localize("chat_command_not_recognized") .. ": " .. _chat_message) dmf:notify(dmf:localize("chat_command_not_recognized") .. ": " .. _chat_message)
set_chat_message(self, "") set_chat_message(self, "")

View file

@ -5,13 +5,12 @@ local MULTISTRING_INDICATOR_TEXT = "[...]"
local DEFAULT_HUD_SCALE = 100 local DEFAULT_HUD_SCALE = 100
local FONT_TYPE = "arial" local FONT_TYPE = "arial"
local FONT_MATERIAL = "content/ui/fonts/arial"
local FONT_SIZE = 22 local FONT_SIZE = 22
local MAX_COMMANDS_VISIBLE = 5 local MAX_COMMANDS_VISIBLE = 5
local STRING_HEIGHT = 25 local STRING_HEIGHT = 25
local STRING_Y_OFFSET = 7 local STRING_Y_OFFSET = 2
local STRING_X_MARGIN = 10 local STRING_X_MARGIN = 10
local OFFSET_X = 10 local OFFSET_X = 10
@ -57,8 +56,7 @@ local function get_hud_scale()
end end
local function get_text_size(text, font_type, font_size) local function get_text_size(text, font_data, font_size)
local font_data = Managers.font:data_by_type(font_type)
local font = font_data.path local font = font_data.path
local additional_settings = { local additional_settings = {
flags = font_data.render_flags or 0 flags = font_data.render_flags or 0
@ -81,11 +79,11 @@ local function get_text_width(text, font, font_size)
end end
local function get_scaled_font_size_by_width(text, font_type, font_size, max_width) local function get_scaled_font_size_by_width(text, font_data, font_size, max_width)
local scale = RESOLUTION_LOOKUP.scale local scale = RESOLUTION_LOOKUP.scale
local min_font_size = 1 local min_font_size = 1
local scaled_font_size = math.max(font_size * scale, 1) local scaled_font_size = math.max(font_size * scale, 1)
local text_width = get_text_size(text, font_type, scaled_font_size) local text_width = get_text_size(text, font_data, scaled_font_size)
if max_width < text_width then if max_width < text_width then
repeat repeat
@ -95,7 +93,7 @@ local function get_scaled_font_size_by_width(text, font_type, font_size, max_wid
font_size = math.max(font_size - 1, min_font_size) font_size = math.max(font_size - 1, min_font_size)
scaled_font_size = math.max(font_size * scale, 1) scaled_font_size = math.max(font_size * scale, 1)
text_width = math.floor(get_text_size(text, font_type, scaled_font_size)) text_width = math.floor(get_text_size(text, font_data, scaled_font_size))
until text_width <= max_width until text_width <= max_width
end end
@ -115,6 +113,8 @@ end
local function draw(commands_list, selected_command_index) local function draw(commands_list, selected_command_index)
local font_data = Managers.font:data_by_type(FONT_TYPE)
local font = font_data.path
create_gui() create_gui()
if not _gui then if not _gui then
@ -152,30 +152,30 @@ local function draw(commands_list, selected_command_index)
local font_size = FONT_SIZE local font_size = FONT_SIZE
for i, command in ipairs(displayed_commands) do 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) font_size = get_scaled_font_size_by_width(command.name, font_data, FONT_SIZE, BASE_COMMAND_TEXT_WIDTH)
-- draw "/command_name" text -- draw "/command_name" text
local scaled_offet_x = (OFFSET_X + STRING_X_MARGIN) * scale 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 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) local string_position = Vector3(scaled_offet_x, scaled_offset_y, OFFSET_Z + 2)
Gui.slug_text(_gui, command.name, FONT_MATERIAL, font_size, string_position, nil, Color(255, 100, 255, 100)) Gui.slug_text(_gui, command.name, font, font_size, string_position, nil, Color(255, 100, 255, 100))
local command_text_strings = word_wrap(command.full_text, FONT_MATERIAL, font_size, BASE_COMMAND_TEXT_WIDTH) local command_text_strings = word_wrap(command.full_text, font, font_size, BASE_COMMAND_TEXT_WIDTH)
local multistring = #command_text_strings > 1 local multistring = #command_text_strings > 1
local first_description_string local first_description_string
if multistring then if multistring then
if command.selected then if command.selected then
selected_strings_number = #command_text_strings selected_strings_number = #command_text_strings
else else
local multistring_indicator_width = get_text_width(MULTISTRING_INDICATOR_TEXT, FONT_MATERIAL, font_size) local multistring_indicator_width = get_text_width(MULTISTRING_INDICATOR_TEXT, font, font_size)
local command_text_width = BASE_COMMAND_TEXT_WIDTH - (multistring_indicator_width / scale) 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) command_text_strings = word_wrap(command.full_text, font, font_size, command_text_width)
-- draw that [...] thing -- draw that [...] thing
local multistring_offset_x = (OFFSET_X + WIDTH) * scale - multistring_indicator_width 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) local multistring_indicator_position = Vector3(multistring_offset_x, string_position.y, string_position.z)
Gui.slug_text(_gui, MULTISTRING_INDICATOR_TEXT, FONT_MATERIAL, font_size, Gui.slug_text(_gui, MULTISTRING_INDICATOR_TEXT, font, font_size,
multistring_indicator_position, nil, Color(255, 100, 100, 100)) multistring_indicator_position, nil, Color(255, 100, 100, 100))
end end
first_description_string = string.sub(command_text_strings[1], #command.name + 2) first_description_string = string.sub(command_text_strings[1], #command.name + 2)
@ -184,18 +184,18 @@ local function draw(commands_list, selected_command_index)
end end
-- draw command description text (1st string) -- 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, font_size)
local first_description_pos_x = string_position.x + first_description_string_width 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) local first_description_string_position = Vector3(first_description_pos_x, string_position.y, string_position.z)
Gui.slug_text(_gui, first_description_string, FONT_MATERIAL, font_size, Gui.slug_text(_gui, first_description_string, font, font_size,
first_description_string_position, nil, Color(255, 255, 255, 255)) first_description_string_position, nil, Color(255, 255, 255, 255))
-- draw command description text (2+ strings) -- draw command description text (2+ strings)
if command.selected and multistring then if command.selected and multistring then
for j = 2, selected_strings_number do for j = selected_strings_number, 2, -1 do
string_position.y = string_position.y - STRING_HEIGHT * scale string_position.y = string_position.y - STRING_HEIGHT * scale
Gui.slug_text(_gui, command_text_strings[j], FONT_MATERIAL, font_size, Gui.slug_text(_gui, command_text_strings[j], font, font_size,
string_position, nil, Color(255, 255, 255, 255)) string_position, nil, Color(255, 255, 255, 255))
end end
end end
@ -227,11 +227,11 @@ local function draw(commands_list, selected_command_index)
if selected_command_index > 0 then if selected_command_index > 0 then
total_number_indicator = selected_command_index .. "/" .. total_number_indicator total_number_indicator = selected_command_index .. "/" .. total_number_indicator
end 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, font_size)
local total_number_indicator_x = (WIDTH) * scale - total_number_indicator_width 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_y = (OFFSET_Y + STRING_Y_OFFSET) * scale
local total_number_indicator_position = Vector3(total_number_indicator_x, total_number_indicator_y, OFFSET_Z + 2) local total_number_indicator_position = Vector3(total_number_indicator_x, total_number_indicator_y, OFFSET_Z + 2)
Gui.slug_text(_gui, total_number_indicator, FONT_MATERIAL, font_size, Gui.slug_text(_gui, total_number_indicator, font, font_size,
total_number_indicator_position, nil, Color(255, 100, 100, 100)) total_number_indicator_position, nil, Color(255, 100, 100, 100))
end end

View file

@ -7,8 +7,14 @@ local _widgets_by_name
-- #################################################################################################################### -- ####################################################################################################################
local function load_scrolling_speed_setting() local function load_scrolling_speed_setting()
if dmf:get("dmf_options_scrolling_speed") and _widgets_by_name and _widgets_by_name["scrollbar"] then if _widgets_by_name then
_widgets_by_name["scrollbar"].content.scroll_speed = dmf:get("dmf_options_scrolling_speed") local dmf_scroll_speed = math.clamp((dmf:get("dmf_options_scrolling_speed") or 100) / 1000, 0.05, 0.5)
if _widgets_by_name["scrollbar"] then
_widgets_by_name["scrollbar"].content.scroll_amount = dmf_scroll_speed
end
if _widgets_by_name["settings_scrollbar"] then
_widgets_by_name["settings_scrollbar"].content.scroll_amount = dmf_scroll_speed
end
end end
end end
@ -39,6 +45,9 @@ local ViewElementKeybindPopup = require("scripts/ui/view_elements/view_element_k
local CATEGORIES_GRID = 1 local CATEGORIES_GRID = 1
local SETTINGS_GRID = 2 local SETTINGS_GRID = 2
local _last_selected_category_entry
local _last_selected_category_widget
local DMFOptionsView = class("DMFOptionsView", "BaseView") local DMFOptionsView = class("DMFOptionsView", "BaseView")
DMFOptionsView.init = function (self, settings) DMFOptionsView.init = function (self, settings)
@ -151,6 +160,8 @@ DMFOptionsView.cb_on_back_pressed = function (self)
self._close_selected_setting = true self._close_selected_setting = true
elseif selected_navigation_column == SETTINGS_GRID then elseif selected_navigation_column == SETTINGS_GRID then
self:_change_navigation_column(selected_navigation_column - 1) self:_change_navigation_column(selected_navigation_column - 1)
elseif self._require_restart then
self:_restart_popup_info()
else else
local view_name = "dmf_options_view" local view_name = "dmf_options_view"
Managers.ui:close_view(view_name) Managers.ui:close_view(view_name)
@ -207,6 +218,30 @@ DMFOptionsView.cb_reset_category_to_default = function (self)
end) end)
end end
DMFOptionsView._restart_popup_info = function (self)
local context = {
title_text = "loc_popup_settings_require_restart_header",
description_text = "loc_popup_settings_require_restart_description",
options = {
{
text = "loc_confirm",
close_on_pressed = true,
callback = callback(function ()
self._popup_id = nil
local view_name = "dmf_options_view"
self._require_restart = false
Managers.ui:close_view(view_name)
end)
}
}
}
Managers.event:trigger("event_show_ui_popup", context, function (id)
self._popup_id = id
end)
end
DMFOptionsView._setup_input_legend = function (self) DMFOptionsView._setup_input_legend = function (self)
self._input_legend_element = self:_add_element(ViewElementInputLegend, "input_legend", 10) self._input_legend_element = self:_add_element(ViewElementInputLegend, "input_legend", 10)
local legend_inputs = self._definitions.legend_inputs local legend_inputs = self._definitions.legend_inputs
@ -225,9 +260,18 @@ DMFOptionsView._setup_content_grid_scrollbar = function (self, grid, widget_id,
load_scrolling_speed_setting() load_scrolling_speed_setting()
grid:assign_scrollbar(scrollbar_widget, grid_pivot_scenegraph_id, grid_scenegraph_id) grid:assign_scrollbar(scrollbar_widget, grid_pivot_scenegraph_id, grid_scenegraph_id, true)
-- Scroll the category grid to the default category widget
if widget_id == "scrollbar" and _last_selected_category_widget then
local index = grid:index_by_widget(_last_selected_category_widget)
local scroll_progress = grid:get_scrollbar_percentage_by_index(index)
grid:set_scrollbar_progress(scroll_progress)
else
grid:set_scrollbar_progress(0) grid:set_scrollbar_progress(0)
end end
end
DMFOptionsView._setup_offscreen_gui = function (self) DMFOptionsView._setup_offscreen_gui = function (self)
local ui_manager = Managers.ui local ui_manager = Managers.ui
@ -292,6 +336,11 @@ DMFOptionsView._setup_content_widgets = function (self, content, scenegraph_id,
alignment_list[#alignment_list + 1] = widget or { alignment_list[#alignment_list + 1] = widget or {
size = size size = size
} }
if entry.display_name == self._default_category then
_last_selected_category_entry = entry
_last_selected_category_widget = widget
end
end end
end end
@ -617,9 +666,8 @@ DMFOptionsView._update_grid_navigation_selection = function (self)
end end
elseif navigation_widgets or self._settings_content_widgets then elseif navigation_widgets or self._settings_content_widgets then
self:_set_default_navigation_widget() self:_set_default_navigation_widget()
elseif self._default_category then
self:present_category_widgets(self._default_category)
end end
-- Removed extra condition for default category - moved to on_view_load_complete
end end
end end
@ -629,6 +677,8 @@ DMFOptionsView.present_category_widgets = function (self, category)
local grid_data = settings_category_widgets[category] local grid_data = settings_category_widgets[category]
if grid_data then if grid_data then
dmf:set("options_menu_last_selected", category)
local widgets = {} local widgets = {}
local alignment_widgets = {} local alignment_widgets = {}
@ -644,7 +694,7 @@ DMFOptionsView.present_category_widgets = function (self, category)
local scrollbar_widget_id = "settings_scrollbar" local scrollbar_widget_id = "settings_scrollbar"
local grid_scenegraph_id = "settings_grid_background" local grid_scenegraph_id = "settings_grid_background"
local grid_pivot_scenegraph_id = "settings_grid_content_pivot" local grid_pivot_scenegraph_id = "settings_grid_content_pivot"
local grid_spacing = _view_settings.grid_spacing local grid_spacing = _view_settings.settings_grid_spacing
self._settings_content_grid = self:_setup_grid(self._settings_content_widgets, self._settings_alignment_list, grid_scenegraph_id, grid_spacing, false) self._settings_content_grid = self:_setup_grid(self._settings_content_widgets, self._settings_alignment_list, grid_scenegraph_id, grid_spacing, false)
self:_setup_content_grid_scrollbar(self._settings_content_grid, scrollbar_widget_id, grid_scenegraph_id, grid_pivot_scenegraph_id) self:_setup_content_grid_scrollbar(self._settings_content_grid, scrollbar_widget_id, grid_scenegraph_id, grid_pivot_scenegraph_id)
@ -667,7 +717,7 @@ DMFOptionsView._setup_category_config = function (self, config)
self._category_content_widgets = {} self._category_content_widgets = {}
end end
local config_categories = config.categories local config_categories = config.categories or {}
local entries = {} local entries = {}
local reset_functions_by_category = {} local reset_functions_by_category = {}
local categories_by_display_name = {} local categories_by_display_name = {}
@ -706,14 +756,23 @@ DMFOptionsView._setup_category_config = function (self, config)
end end
end end
self._default_category = config_categories[1].display_name -- Retrieve default category from settings
local category_setting = dmf:get("options_menu_last_selected")
if category_setting and not categories_by_display_name[category_setting] then
category_setting = false
end
self._default_category = category_setting or (
config_categories[1] and config_categories[1].display_name
)
local scenegraph_id = "grid_content_pivot" local scenegraph_id = "grid_content_pivot"
local callback_name = "cb_on_category_pressed" local callback_name = "cb_on_category_pressed"
self._category_content_widgets, self._category_alignment_list = self:_setup_content_widgets(entries, scenegraph_id, callback_name) self._category_content_widgets, self._category_alignment_list = self:_setup_content_widgets(entries, scenegraph_id, callback_name)
local scrollbar_widget_id = "scrollbar" local scrollbar_widget_id = "scrollbar"
local grid_scenegraph_id = "background" local grid_scenegraph_id = "background"
local grid_pivot_scenegraph_id = "grid_content_pivot" local grid_pivot_scenegraph_id = "grid_content_pivot"
local grid_spacing = _view_settings.grid_spacing local grid_spacing = _view_settings.category_grid_spacing
self._category_content_grid = self:_setup_grid(self._category_content_widgets, self._category_alignment_list, grid_scenegraph_id, grid_spacing, true) self._category_content_grid = self:_setup_grid(self._category_content_widgets, self._category_alignment_list, grid_scenegraph_id, grid_spacing, true)
self:_setup_content_grid_scrollbar(self._category_content_grid, scrollbar_widget_id, grid_scenegraph_id, grid_pivot_scenegraph_id) self:_setup_content_grid_scrollbar(self._category_content_grid, scrollbar_widget_id, grid_scenegraph_id, grid_pivot_scenegraph_id)
@ -746,6 +805,7 @@ DMFOptionsView._setup_settings_config = function (self, config)
local settings_default_values = {} local settings_default_values = {}
local aligment_list = {} local aligment_list = {}
local callback_name = "cb_on_settings_pressed" local callback_name = "cb_on_settings_pressed"
local changed_callback_name = "cb_on_settings_changed"
for setting_index, setting in ipairs(config_settings) do for setting_index, setting in ipairs(config_settings) do
local valid = self._validation_mapping[setting.category].settings[setting.display_name].validation_result local valid = self._validation_mapping[setting.category].settings[setting.display_name].validation_result
@ -768,7 +828,7 @@ DMFOptionsView._setup_settings_config = function (self, config)
end end
local widget_suffix = "setting_" .. tostring(setting_index) local widget_suffix = "setting_" .. tostring(setting_index)
local widget, alignment_widget = self:_create_settings_widget_from_config(setting, category, widget_suffix, callback_name) local widget, alignment_widget = self:_create_settings_widget_from_config(setting, category, widget_suffix, callback_name, changed_callback_name)
category_widgets[category][#widgets + 1] = { category_widgets[category][#widgets + 1] = {
widget = widget, widget = widget,
alignment_widget = alignment_widget alignment_widget = alignment_widget
@ -811,19 +871,24 @@ end
DMFOptionsView._set_tooltip_data = function (self, widget) DMFOptionsView._set_tooltip_data = function (self, widget)
local current_widget = self._tooltip_data and self._tooltip_data.widget local current_widget = self._tooltip_data and self._tooltip_data.widget
local display_text = nil local localized_text = nil
local tooltip_text = widget.content.entry.tooltip_text local tooltip_text = widget.content.entry.tooltip_text
local disabled_by_list = widget.content.entry.disabled_by local disabled_by_list = widget.content.entry.disabled_by
if tooltip_text then if tooltip_text then
display_text = tooltip_text if type(tooltip_text) == "function" then
localized_text = tooltip_text()
else
-- Should already be localized in mod option generation
localized_text = tooltip_text
end
end end
if disabled_by_list then if disabled_by_list then
display_text = display_text and string.format("%s\n", display_text) localized_text = localized_text and string.format("%s\n", localized_text)
for _, text in pairs(disabled_by_list) do for _, text in pairs(disabled_by_list) do
display_text = display_text and string.format("%s\n%s", display_text, text) or text localized_text = localized_text and string.format("%s\n%s", localized_text, text) or text
end end
end end
@ -835,14 +900,14 @@ DMFOptionsView._set_tooltip_data = function (self, widget)
if current_widget ~= widget or current_widget == widget and new_y ~= current_y then if current_widget ~= widget or current_widget == widget and new_y ~= current_y then
self._tooltip_data = { self._tooltip_data = {
widget = widget, widget = widget,
text = display_text text = localized_text
} }
self._widgets_by_name.tooltip.content.text = display_text self._widgets_by_name.tooltip.content.text = localized_text
local text_style = self._widgets_by_name.tooltip.style.text local text_style = self._widgets_by_name.tooltip.style.text
local x_pos = starting_point[1] + widget.offset[1] local x_pos = starting_point[1] + widget.offset[1]
local width = widget.content.size[1] * 0.5 local width = widget.content.size[1] * 0.5
local text_options = UIFonts.get_font_options_by_style(text_style) local text_options = UIFonts.get_font_options_by_style(text_style)
local _, text_height = self:_text_size(display_text, text_style.font_type, text_style.font_size, { local _, text_height = self:_text_size(localized_text, text_style.font_type, text_style.font_size, {
width, width,
0 0
}, text_options) }, text_options)
@ -852,7 +917,7 @@ DMFOptionsView._set_tooltip_data = function (self, widget)
height height
} }
self._widgets_by_name.tooltip.offset[1] = x_pos - width * 0.8 self._widgets_by_name.tooltip.offset[1] = x_pos - width * 0.8
self._widgets_by_name.tooltip.offset[2] = new_y - height self._widgets_by_name.tooltip.offset[2] = math.max(new_y - height, 20)
self._widgets_by_name.tooltip.content.visible = true self._widgets_by_name.tooltip.content.visible = true
end end
end end
@ -891,7 +956,7 @@ DMFOptionsView._update_settings_content_widgets = function (self, dt, t, input_s
end end
end end
DMFOptionsView._create_settings_widget_from_config = function (self, config, category, suffix, callback_name) DMFOptionsView._create_settings_widget_from_config = function (self, config, category, suffix, callback_name, changed_callback_name)
local scenegraph_id = "settings_grid_content_pivot" local scenegraph_id = "settings_grid_content_pivot"
local default_value = config.default_value local default_value = config.default_value
local default_value_type = type(default_value) local default_value_type = type(default_value)
@ -945,7 +1010,7 @@ DMFOptionsView._create_settings_widget_from_config = function (self, config, cat
local init = template.init local init = template.init
if init then if init then
init(self, widget, config, callback_name) init(self, widget, config, callback_name, changed_callback_name)
end end
end end
@ -1079,6 +1144,27 @@ DMFOptionsView.cb_on_settings_pressed = function (self, widget, entry)
end end
end end
DMFOptionsView.cb_on_settings_changed = function (self, widget, entry, option_value)
if not self._require_restart then
-- Entry supersedes option
if entry.require_restart then
self._require_restart = true
-- Search by option value
elseif option_value then
for i = 1, #entry.options do
local option = entry.options[i]
if option.value == option_value then
self._require_restart = option.require_restart
break
end
end
end
end
end
DMFOptionsView._enable_settings_overlay = function (self, enable) DMFOptionsView._enable_settings_overlay = function (self, enable)
local widgets_by_name = self._widgets_by_name local widgets_by_name = self._widgets_by_name
local settings_overlay_widget = widgets_by_name.settings_overlay local settings_overlay_widget = widgets_by_name.settings_overlay
@ -1244,4 +1330,13 @@ DMFOptionsView._set_selected_grid_widget = function (self, widgets, widget_name)
return selected_widget, selected_widget_index return selected_widget, selected_widget_index
end end
-- Handles navigation to the last selected category widget
DMFOptionsView._on_view_load_complete = function (self, loaded)
DMFOptionsView.super._on_view_load_complete(self, loaded)
if _last_selected_category_entry and _last_selected_category_widget then
self:cb_on_category_pressed(_last_selected_category_widget, _last_selected_category_entry)
end
end
return DMFOptionsView return DMFOptionsView

View file

@ -23,6 +23,9 @@ local group_header_height = 80
local DEFAULT_NUM_DECIMALS = 0 local DEFAULT_NUM_DECIMALS = 0
local _dropdown_deadzone = 0.25 -- 250ms delay before opening keybind popups
local _last_dropdown_pressed = -1
local value_font_style = table.clone(UIFontSettings.list_button) local value_font_style = table.clone(UIFontSettings.list_button)
value_font_style.offset = { value_font_style.offset = {
settings_grid_width - settings_value_width + 25, settings_grid_width - settings_value_width + 25,
@ -53,7 +56,7 @@ local blueprints = {
settings_value_height settings_value_height
}, },
pass_template = ButtonPassTemplates.list_button, pass_template = ButtonPassTemplates.list_button,
init = function (parent, widget, entry, callback_name) init = function (parent, widget, entry, callback_name, changed_callback_name)
local content = widget.content local content = widget.content
local hotspot = content.hotspot local hotspot = content.hotspot
@ -78,7 +81,7 @@ local blueprints = {
settings_value_height settings_value_height
}, },
pass_template = ButtonPassTemplates.settings_button(settings_grid_width, settings_value_height, settings_value_width, true), pass_template = ButtonPassTemplates.settings_button(settings_grid_width, settings_value_height, settings_value_width, true),
init = function (parent, widget, entry, callback_name) init = function (parent, widget, entry, callback_name, changed_callback_name)
local content = widget.content local content = widget.content
content.hotspot.pressed_callback = function () content.hotspot.pressed_callback = function ()
@ -95,6 +98,10 @@ local blueprints = {
content.text = display_name content.text = display_name
content.button_text = Localize("loc_settings_change") content.button_text = Localize("loc_settings_change")
content.entry = entry content.entry = entry
entry.changed_callback = function (changed_value)
callback(parent, changed_callback_name, widget, entry)()
end
end end
}, },
group_header = { group_header = {
@ -110,7 +117,7 @@ local blueprints = {
value = Localize("loc_settings_option_unavailable") value = Localize("loc_settings_option_unavailable")
} }
}, },
init = function (parent, widget, entry, callback_name) init = function (parent, widget, entry, callback_name, changed_callback_name)
local content = widget.content local content = widget.content
local display_name = entry.display_name local display_name = entry.display_name
content.text = display_name content.text = display_name
@ -124,9 +131,9 @@ local blueprints = {
pass_template_function = function (parent, config, size) pass_template_function = function (parent, config, size)
return CheckboxPassTemplates.settings_checkbox(size[1], settings_value_height, settings_value_width, 2, true) return CheckboxPassTemplates.settings_checkbox(size[1], settings_value_height, settings_value_width, 2, true)
end, end,
init = function (parent, widget, entry, callback_name) init = function (parent, widget, entry, callback_name, changed_callback_name)
local content = widget.content local content = widget.content
local display_name = entry.display_name or Localize("loc_settings_option_unavailable") local display_name = entry.display_name or Managers.localization:localize("loc_settings_option_unavailable")
content.text = display_name content.text = display_name
content.entry = entry content.entry = entry
@ -134,6 +141,11 @@ local blueprints = {
local widget_option_id = "option_" .. i 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") content[widget_option_id] = i == 1 and Managers.localization:localize("loc_setting_checkbox_on") or Managers.localization:localize("loc_setting_checkbox_off")
end end
entry.changed_callback = function (changed_value)
--callback(parent, callback_name, widget, entry)()
callback(parent, changed_callback_name, widget, entry)()
end
end, end,
update = function (parent, widget, input_service, dt, t) update = function (parent, widget, input_service, dt, t)
local content = widget.content local content = widget.content
@ -146,7 +158,7 @@ local blueprints = {
content.disabled = is_disabled content.disabled = is_disabled
local new_value = nil local new_value = nil
if hotspot.on_pressed and not is_disabled then if hotspot.on_pressed and not parent._navigation_column_changed_this_frame and not is_disabled then
new_value = not value new_value = not value
end end
@ -159,14 +171,15 @@ local blueprints = {
if new_value ~= nil and new_value ~= value then if new_value ~= nil and new_value ~= value then
on_activated(new_value, entry) on_activated(new_value, entry)
entry.changed_callback(new_value)
end end
end end
} }
} }
local function slider_init_function(parent, widget, entry, callback_name) local function slider_init_function(parent, widget, entry, callback_name, changed_callback_name)
local content = widget.content local content = widget.content
local display_name = entry.display_name or Localize("loc_settings_option_unavailable") local display_name = entry.display_name or Managers.localization:localize("loc_settings_option_unavailable")
content.text = display_name content.text = display_name
content.entry = entry content.entry = entry
content.area_length = settings_value_width content.area_length = settings_value_width
@ -183,7 +196,7 @@ local function slider_init_function(parent, widget, entry, callback_name)
content.previous_slider_value = value content.previous_slider_value = value
content.slider_value = value content.slider_value = value
content.pressed_callback = function () entry.pressed_callback = function ()
local is_disabled = entry.is_disabled local is_disabled = entry.is_disabled
if is_disabled then if is_disabled then
@ -192,6 +205,10 @@ local function slider_init_function(parent, widget, entry, callback_name)
callback(parent, callback_name, widget, entry)() callback(parent, callback_name, widget, entry)()
end end
entry.changed_callback = function (changed_value)
callback(parent, changed_callback_name, widget, entry)()
end
end end
blueprints.percent_slider = { blueprints.percent_slider = {
@ -202,8 +219,8 @@ blueprints.percent_slider = {
pass_template_function = function (parent, config, size) pass_template_function = function (parent, config, size)
return SliderPassTemplates.settings_percent_slider(size[1], settings_value_height, settings_value_width, true) return SliderPassTemplates.settings_percent_slider(size[1], settings_value_height, settings_value_width, true)
end, end,
init = function (parent, widget, entry, callback_name) init = function (parent, widget, entry, callback_name, changed_callback_name)
slider_init_function(parent, widget, entry, callback_name) slider_init_function(parent, widget, entry, callback_name, changed_callback_name)
end, end,
update = function (parent, widget, input_service, dt, t) update = function (parent, widget, input_service, dt, t)
local content = widget.content local content = widget.content
@ -260,8 +277,8 @@ blueprints.percent_slider = {
if hotspot.on_pressed and not is_disabled then if hotspot.on_pressed and not is_disabled then
if focused then if focused then
new_value = content.slider_value new_value = content.slider_value
elseif using_gamepad then elseif using_gamepad and entry.pressed_callback then
content.pressed_callback() entry.pressed_callback()
end end
end end
@ -275,6 +292,7 @@ blueprints.percent_slider = {
if new_value then if new_value then
on_activated(new_value * 100, entry) on_activated(new_value * 100, entry)
entry.changed_callback(new_value)
content.slider_value = new_value content.slider_value = new_value
content.previous_slider_value = new_value content.previous_slider_value = new_value
@ -293,8 +311,8 @@ blueprints.value_slider = {
pass_template_function = function (parent, config, size) pass_template_function = function (parent, config, size)
return SliderPassTemplates.settings_value_slider(size[1], settings_value_height, settings_value_width, true) return SliderPassTemplates.settings_value_slider(size[1], settings_value_height, settings_value_width, true)
end, end,
init = function (parent, widget, entry, callback_name) init = function (parent, widget, entry, callback_name, changed_callback_name)
slider_init_function(parent, widget, entry, callback_name) slider_init_function(parent, widget, entry, callback_name, changed_callback_name)
end, end,
update = function (parent, widget, input_service, dt, t) update = function (parent, widget, input_service, dt, t)
local content = widget.content local content = widget.content
@ -356,8 +374,8 @@ blueprints.value_slider = {
if hotspot.on_pressed then if hotspot.on_pressed then
if focused then if focused then
new_normalized_value = content.slider_value new_normalized_value = content.slider_value
elseif using_gamepad then elseif using_gamepad and entry.pressed_callback then
content.pressed_callback() entry.pressed_callback()
end end
end end
@ -373,6 +391,7 @@ blueprints.value_slider = {
local new_value = explode_function(new_normalized_value, entry) local new_value = explode_function(new_normalized_value, entry)
on_activated(new_value, entry) on_activated(new_value, entry)
entry.changed_callback(new_value)
content.slider_value = new_normalized_value content.slider_value = new_normalized_value
content.previous_slider_value = new_normalized_value content.previous_slider_value = new_normalized_value
@ -391,9 +410,9 @@ blueprints.slider = {
pass_template_function = function (parent, config, size) pass_template_function = function (parent, config, size)
return SliderPassTemplates.settings_value_slider(size[1], settings_value_height, settings_value_width, true) return SliderPassTemplates.settings_value_slider(size[1], settings_value_height, settings_value_width, true)
end, end,
init = function (parent, widget, entry, callback_name) init = function (parent, widget, entry, callback_name, changed_callback_name)
local content = widget.content local content = widget.content
local display_name = entry.display_name or Localize("loc_settings_option_unavailable") local display_name = entry.display_name or Managers.localization:localize("loc_settings_option_unavailable")
content.text = display_name content.text = display_name
content.entry = entry content.entry = entry
content.area_length = settings_value_width content.area_length = settings_value_width
@ -403,7 +422,11 @@ blueprints.slider = {
local value, value_fraction = get_function(entry) local value, value_fraction = get_function(entry)
content.previous_slider_value = value_fraction content.previous_slider_value = value_fraction
content.slider_value = value_fraction content.slider_value = value_fraction
content.pressed_callback = callback(parent, callback_name, widget, entry) entry.pressed_callback = callback(parent, callback_name, widget, entry)
entry.changed_callback = function (changed_value)
callback(parent, changed_callback_name, widget, entry)()
end
end, end,
update = function (parent, widget, input_service, dt, t) update = function (parent, widget, input_service, dt, t)
local content = widget.content local content = widget.content
@ -463,7 +486,7 @@ blueprints.slider = {
if focused then if focused then
new_value_fraction = content.slider_value new_value_fraction = content.slider_value
elseif not hotspot.is_hover then elseif not hotspot.is_hover then
content.pressed_callback() entry.pressed_callback()
end end
end end
@ -479,6 +502,7 @@ blueprints.slider = {
local new_value = math.lerp(entry.min_value, entry.max_value, new_value_fraction) local new_value = math.lerp(entry.min_value, entry.max_value, new_value_fraction)
on_activated(new_value, entry) on_activated(new_value, entry)
entry.changed_callback(new_value)
content.slider_value = new_value_fraction content.slider_value = new_value_fraction
content.previous_slider_value = new_value_fraction content.previous_slider_value = new_value_fraction
@ -504,9 +528,9 @@ blueprints.dropdown = {
return DropdownPassTemplates.settings_dropdown(size[1], settings_value_height, settings_value_width, num_visible_options, true) return DropdownPassTemplates.settings_dropdown(size[1], settings_value_height, settings_value_width, num_visible_options, true)
end, end,
init = function (parent, widget, entry, callback_name) init = function (parent, widget, entry, callback_name, changed_callback_name)
local content = widget.content local content = widget.content
local display_name = entry.display_name or Localize("loc_settings_option_unavailable") local display_name = entry.display_name or Managers.localization:localize("loc_settings_option_unavailable")
content.text = display_name content.text = display_name
content.entry = entry content.entry = entry
local has_options_function = entry.options_function ~= nil local has_options_function = entry.options_function ~= nil
@ -548,6 +572,10 @@ blueprints.dropdown = {
local scroll_amount = scroll_length > 0 and (size[2] + spacing) / scroll_length or 0 local scroll_amount = scroll_length > 0 and (size[2] + spacing) / scroll_length or 0
content.scroll_amount = scroll_amount content.scroll_amount = scroll_amount
local value = entry.get_function and entry:get_function() or entry.default_value local value = entry.get_function and entry:get_function() or entry.default_value
entry.changed_callback = function (changed_value)
callback(parent, changed_callback_name, widget, entry, changed_value)()
end
end, end,
update = function (parent, widget, input_service, dt, t) update = function (parent, widget, input_service, dt, t)
local content = widget.content local content = widget.content
@ -577,7 +605,7 @@ blueprints.dropdown = {
if selected_index and focused then if selected_index and focused then
if using_gamepad and hotspot.on_pressed then if using_gamepad and hotspot.on_pressed then
new_value = options[selected_index].id new_value = options[selected_index].value
end end
hotspot_style.on_pressed_sound = hotspot_style.on_pressed_fold_in_sound hotspot_style.on_pressed_sound = hotspot_style.on_pressed_fold_in_sound
@ -588,8 +616,8 @@ blueprints.dropdown = {
value = entry.get_function and entry:get_function() or content.internal_value or "<not selected>" value = entry.get_function and entry:get_function() or content.internal_value or "<not selected>"
local preview_option = options_by_value[value] local preview_option = options_by_value[value]
local preview_option_id = preview_option and preview_option.id local preview_option_value = preview_option and preview_option.value
local preview_value = preview_option and preview_option.display_name or Localize("loc_settings_option_unavailable") local preview_value = preview_option and preview_option.display_name or Managers.localization:localize("loc_settings_option_unavailable")
content.value_text = preview_value content.value_text = preview_value
@ -600,6 +628,7 @@ blueprints.dropdown = {
local scroll_area_height = parent:settings_grid_length() local scroll_area_height = parent:settings_grid_length()
local dropdown_length = size[2] * (num_visible_options + 1) local dropdown_length = size[2] * (num_visible_options + 1)
local grow_downwards = true local grow_downwards = true
local always_keep_order = true
if scroll_area_height <= offset[2] - scroll_amount + dropdown_length then if scroll_area_height <= offset[2] - scroll_amount + dropdown_length then
grow_downwards = false grow_downwards = false
@ -612,7 +641,7 @@ blueprints.dropdown = {
for i = 1, #options do for i = 1, #options do
local option = options[i] local option = options[i]
if option.id == preview_option_id then if option.value == preview_option_value then
selected_index = i selected_index = i
break break
@ -624,13 +653,13 @@ blueprints.dropdown = {
if selected_index and focused then if selected_index and focused then
if input_service:get("navigate_up_continuous") then if input_service:get("navigate_up_continuous") then
if grow_downwards then if grow_downwards or not grow_downwards and always_keep_order then
new_selection_index = math.max(selected_index - 1, 1) new_selection_index = math.max(selected_index - 1, 1)
else else
new_selection_index = math.min(selected_index + 1, num_options) new_selection_index = math.min(selected_index + 1, num_options)
end end
elseif input_service:get("navigate_down_continuous") then elseif input_service:get("navigate_down_continuous") then
if grow_downwards then if grow_downwards or not grow_downwards and always_keep_order then
new_selection_index = math.min(selected_index + 1, num_options) new_selection_index = math.min(selected_index + 1, num_options)
else else
new_selection_index = math.max(selected_index - 1, 1) new_selection_index = math.max(selected_index - 1, 1)
@ -667,18 +696,24 @@ blueprints.dropdown = {
local using_scrollbar = num_visible_options < num_options local using_scrollbar = num_visible_options < num_options
for i = start_index, end_index do for i = start_index, end_index do
local actual_i = i
if not grow_downwards and always_keep_order then
actual_i = end_index - i + start_index
end
local option_text_id = "option_text_" .. option_index local option_text_id = "option_text_" .. option_index
local option_hotspot_id = "option_hotspot_" .. option_index local option_hotspot_id = "option_hotspot_" .. option_index
local outline_style_id = "outline_" .. option_index local outline_style_id = "outline_" .. option_index
local option_hotspot = content[option_hotspot_id] local option_hotspot = content[option_hotspot_id]
option_hovered = option_hovered or option_hotspot.is_hover option_hovered = option_hovered or option_hotspot.is_hover
option_hotspot.is_selected = i == selected_index option_hotspot.is_selected = actual_i == selected_index
local option = options[i] local option = options[actual_i]
if not new_value and focused and not using_gamepad and option_hotspot.on_pressed then if not new_value and focused and not using_gamepad and option_hotspot.on_pressed then
option_hotspot.on_pressed = nil option_hotspot.on_pressed = nil
new_value = option.value new_value = option.value
content.selected_index = i content.selected_index = actual_i
end end
local option_display_name = option.display_name local option_display_name = option.display_name
@ -694,10 +729,13 @@ blueprints.dropdown = {
local value_changed = new_value ~= nil local value_changed = new_value ~= nil
if value_changed and new_value ~= value then if value_changed then
_last_dropdown_pressed = t
if new_value ~= value then
local on_activated = entry.on_activated local on_activated = entry.on_activated
on_activated(new_value, entry) on_activated(new_value, entry)
entry.changed_callback(new_value)
end
end end
local scrollbar_hotspot = content.scrollbar_hotspot local scrollbar_hotspot = content.scrollbar_hotspot
@ -714,9 +752,9 @@ blueprints.keybind = {
settings_value_height settings_value_height
}, },
pass_template = KeybindPassTemplates.settings_keybind(settings_grid_width, settings_value_height, settings_value_width), pass_template = KeybindPassTemplates.settings_keybind(settings_grid_width, settings_value_height, settings_value_width),
init = function (parent, widget, entry, callback_name) init = function (parent, widget, entry, callback_name, changed_callback_name)
local content = widget.content local content = widget.content
local display_name = entry.display_name or Localize("loc_settings_option_unavailable") local display_name = entry.display_name or parent:_localize("loc_settings_option_unavailable")
content.text = display_name content.text = display_name
content.entry = entry content.entry = entry
content.key_unassigned_string = Managers.localization:localize("loc_keybind_unassigned") content.key_unassigned_string = Managers.localization:localize("loc_keybind_unassigned")
@ -730,7 +768,11 @@ blueprints.keybind = {
local hotspot = content.hotspot local hotspot = content.hotspot
if hotspot.on_released then if hotspot.on_released then
if (t - _last_dropdown_pressed) > _dropdown_deadzone then
parent:show_keybind_popup(widget, entry, content.entry.cancel_keys) parent:show_keybind_popup(widget, entry, content.entry.cancel_keys)
else
_last_dropdown_pressed = -1
end
end end
end end
} }
@ -763,8 +805,7 @@ blueprints.description = {
local content = widget.content local content = widget.content
local style = widget.style local style = widget.style
local text_style = style.text local text_style = style.text
local display_name = entry.display_name local display_text = entry.display_name
local display_text = display_name
local ui_renderer = parent._ui_renderer local ui_renderer = parent._ui_renderer
local size = content.size local size = content.size
local text_options = UIFonts.get_font_options_by_style(text_style) local text_options = UIFonts.get_font_options_by_style(text_style)

View file

@ -25,6 +25,7 @@ local settings_mask_size = {
} }
local settings_grid_height = grid_height + mask_offset_y local settings_grid_height = grid_height + mask_offset_y
local settings_grid_scroll_amount = math.clamp((dmf:get("dmf_options_scrolling_speed") or 100) / 1000, 0.05, 0.5)
local tooltip_text_style = table.clone(UIFontSettings.body) local tooltip_text_style = table.clone(UIFontSettings.body)
tooltip_text_style.text_horizontal_alignment = "left" tooltip_text_style.text_horizontal_alignment = "left"
@ -62,9 +63,10 @@ local scenegraph_definition = {
grid_width, grid_width,
grid_height grid_height
}, },
-- Move the categories up and left to compensate for removed icons
position = { position = {
180, 140,
240, 190,
1 1
} }
}, },
@ -385,7 +387,10 @@ local widget_definitions = {
}, "tooltip", { }, "tooltip", {
visible = false visible = false
}), }),
scrollbar = UIWidget.create_definition(ScrollbarPassTemplates.default_scrollbar, "scrollbar"), scrollbar = UIWidget.create_definition(ScrollbarPassTemplates.default_scrollbar, "scrollbar", {
scroll_speed = 10,
scroll_amount = settings_grid_scroll_amount,
}),
grid_mask = UIWidget.create_definition({ grid_mask = UIWidget.create_definition({
{ {
value = "content/ui/materials/offscreen_masks/ui_overlay_offscreen_vertical_blur", value = "content/ui/materials/offscreen_masks/ui_overlay_offscreen_vertical_blur",
@ -406,7 +411,10 @@ local widget_definitions = {
content_id = "hotspot" content_id = "hotspot"
} }
}, "grid_interaction"), }, "grid_interaction"),
settings_scrollbar = UIWidget.create_definition(ScrollbarPassTemplates.default_scrollbar, "settings_scrollbar"), settings_scrollbar = UIWidget.create_definition(ScrollbarPassTemplates.default_scrollbar, "settings_scrollbar", {
scroll_speed = 10,
scroll_amount = settings_grid_scroll_amount,
}),
settings_grid_mask = UIWidget.create_definition({ settings_grid_mask = UIWidget.create_definition({
{ {
value = "content/ui/materials/offscreen_masks/ui_overlay_offscreen_vertical_blur", value = "content/ui/materials/offscreen_masks/ui_overlay_offscreen_vertical_blur",

View file

@ -5,9 +5,13 @@ local dmf_options_view_settings = {
shading_environment = "content/shading_environments/ui/system_menu", shading_environment = "content/shading_environments/ui/system_menu",
grid_size = { grid_size = {
500, 500,
800 820 -- Increased to compensate for upshifted category grid
}, },
grid_spacing = { category_grid_spacing = {
0,
3
},
settings_grid_spacing = {
0, 0,
10 10
}, },

View file

@ -6,13 +6,22 @@ local _type_template_map = {}
local _devices = { local _devices = {
"keyboard", "keyboard",
"mouse" "mouse",
} }
local _cancel_keys = { local _cancel_keys = {
"keyboard_esc" "keyboard_esc"
} }
local _reserved_keys = {} local _reserved_keys = {}
local ERRORS = {
REGULAR = {
invalid_widget_type = "[DMF Mod Options] (%s): \"%s\" is not a valid widget type " ..
"in this version of Darktide Mod Framework.",
},
}
-- #################################################################################################################### -- ####################################################################################################################
-- ##### Local functions ############################################################################################## -- ##### Local functions ##############################################################################################
-- #################################################################################################################### -- ####################################################################################################################
@ -53,6 +62,21 @@ local create_description_template = function (self, params)
end end
_type_template_map["description"] = create_description_template _type_template_map["description"] = create_description_template
-- ##########################
-- ###### Group #############
-- ##########################
-- Create group template
local create_group_template = function(self, params)
local template = {
display_name = params.title,
widget_type = "group_header",
after = params.parent_index
}
return template
end
_type_template_map["group"] = create_group_template
-- ########################### -- ###########################
-- ###### Percent Slider ##### -- ###### Percent Slider #####
-- ########################### -- ###########################
@ -136,6 +160,7 @@ local create_checkbox_template = function (self, params)
default_value = params.default_value, default_value = params.default_value,
display_name = params.title, display_name = params.title,
indentation_level = params.depth, indentation_level = params.depth,
require_restart = params.require_restart,
tooltip_text = params.tooltip, tooltip_text = params.tooltip,
value_type = "boolean", value_type = "boolean",
} }
@ -163,9 +188,11 @@ local create_mod_toggle_template = function (self, params)
after = params.after, after = params.after,
category = params.category, category = params.category,
default_value = true, default_value = true,
display_name = dmf:localize("toggle_mod"), disabled = params.disabled,
display_name = params.readable_mod_name or params.mod_name,
indentation_level = 0, indentation_level = 0,
tooltip_text = dmf:localize("toggle_mod_description"), require_restart = params.require_restart,
tooltip_text = params.description,
value_type = "boolean", value_type = "boolean",
} }
@ -203,6 +230,7 @@ local create_dropdown_template = function (self, params)
indentation_level = params.depth, indentation_level = params.depth,
options = params.options, options = params.options,
tooltip_text = params.tooltip, tooltip_text = params.tooltip,
require_restart = params.require_restart,
widget_type = "dropdown", widget_type = "dropdown",
} }
template.on_activated = function(new_value) template.on_activated = function(new_value)
@ -223,7 +251,9 @@ _type_template_map["dropdown"] = create_dropdown_template
-- ######### Keybind ######### -- ######### Keybind #########
-- ########################### -- ###########################
local set_new_keybind = function (self, keybind_data) local set_keybind = function (self, keybind_data, keywatch_result)
keybind_data.keys = keywatch_result
local mod = get_mod(keybind_data.mod_name) local mod = get_mod(keybind_data.mod_name)
dmf.add_mod_keybind( dmf.add_mod_keybind(
mod, mod,
@ -232,18 +262,19 @@ local set_new_keybind = function (self, keybind_data)
global = keybind_data.keybind_global, global = keybind_data.keybind_global,
trigger = keybind_data.keybind_trigger, trigger = keybind_data.keybind_trigger,
type = keybind_data.keybind_type, type = keybind_data.keybind_type,
keys = keybind_data.keys, main = keywatch_result.main,
enablers = keywatch_result.enablers,
disablers = keywatch_result.disablers,
function_name = keybind_data.function_name, function_name = keybind_data.function_name,
view_name = keybind_data.view_name, view_name = keybind_data.view_name,
} }
) )
mod:set(keybind_data.setting_id, keybind_data.keys, true) mod:set(keybind_data.setting_id, dmf.keywatch_result_to_local_keys(keywatch_result), true)
end end
-- Create keybind template -- Create keybind template
local create_keybind_template = function (self, params) local create_keybind_template = function (self, params)
local template = { local template = {
widget_type = "keybind", widget_type = "keybind",
service_type = "Ingame", service_type = "Ingame",
@ -259,26 +290,31 @@ local create_keybind_template = function (self, params)
indentation_level = params.depth, indentation_level = params.depth,
mod_name = params.mod_name, mod_name = params.mod_name,
setting_id = params.setting_id, setting_id = params.setting_id,
keys = dmf.keys_to_keybind_result(params.keys), keys = dmf.local_keys_to_keywatch_result(params.keys),
default_value = dmf.local_keys_to_keywatch_result(params.default_value) or {},
on_activated = function (new_value, old_value) 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 -- Prevent unbinding the mod options menu
if params.setting_id ~= "open_dmf_options" then if params.setting_id ~= "open_dmf_options" then
-- Unbind the keybind -- Unbind the keybind if the new value is empty
params.keys = {} if not (new_value and new_value.main) then
set_new_keybind(self, params) set_keybind(self, params, {})
return true
end end
-- Unbind the keybind if the new value matches a cancel key
for i = 1, #_cancel_keys do
local cancel_key = _cancel_keys[i]
if cancel_key == new_value.main then
set_keybind(self, params, {})
return true return true
end end
end end
end
-- Don't modify the keybind if the new value is a reserved key
for i = 1, #_reserved_keys do for i = 1, #_reserved_keys do
local reserved_key = _reserved_keys[i] local reserved_key = _reserved_keys[i]
if reserved_key == new_value.main then if reserved_key == new_value.main then
@ -286,23 +322,23 @@ local create_keybind_template = function (self, params)
end end
end end
-- Get the new keybind -- Get the keys of the new value
local keys = dmf.keybind_result_to_keys(new_value) local keys = dmf.keywatch_result_to_local_keys(new_value)
-- Bind the new key and prevent unbinding the mod options menu -- Set the new keybind unless it would unbind the mod options menu
if keys and #keys > 0 or params.setting_id ~= "open_dmf_options" then if keys and #keys > 0 or params.setting_id ~= "open_dmf_options" then
params.keys = keys set_keybind(self, params, new_value)
set_new_keybind(self, params) return true
end end
return true return false
end, end,
get_function = function (template) get_function = function (template)
local keys = get_mod(template.mod_name):get(template.setting_id) local saved_keys = get_mod(template.mod_name):get(template.setting_id)
local keybind_result = dmf.keys_to_keybind_result(keys) local keywatch_result = dmf.local_keys_to_keywatch_result(saved_keys)
return keybind_result return keywatch_result
end, end,
} }
@ -321,11 +357,23 @@ local function widget_data_to_template(self, data)
return _type_template_map[data.type](self, data) return _type_template_map[data.type](self, data)
else else
dmf:dump(data, "widget", 1) dmf:dump(data, "widget", 1)
dmf.throw_error("[widget \"%s\"]: 'type' field must contain valid widget type name.", data.setting_id) dmf:error(ERRORS.REGULAR.invalid_widget_type, tostring(data.mod_name), tostring(data.type))
end end
end end
-- Add a category for toggling mods
local function create_toggle_category(self, categories)
local category = {
can_be_reset = false,
display_name = dmf:localize("toggle_mods"),
custom = true
}
categories[#categories + 1] = category
return category
end
-- Add a mod category to the options view categories -- Add a mod category to the options view categories
local function create_mod_category(self, categories, widget_data) local function create_mod_category(self, categories, widget_data)
local category = { local category = {
@ -350,10 +398,63 @@ local function create_option_template(self, widget_data, category_name, index_of
end end
end end
-- Insert a new item into a table before any items that pass the item_tester function
local function insert_before(tbl, item_tester, new_item)
local copy = {}
for _, item in ipairs(tbl) do
if item_tester(item) then
table.insert(copy, new_item)
end
table.insert(copy, item)
end
return copy
end
-- #################################################################################################################### -- ####################################################################################################################
-- ##### Hooks ######################################################################################################## -- ##### Hooks ########################################################################################################
-- #################################################################################################################### -- ####################################################################################################################
-- Add Mods Options title to global localization table
-- so that the SystemView options menu can localize it
dmf:add_global_localize_strings({
-- TODO: copied from dmf/localization/dmf.lua, figure out a better way
mods_options = {
en = "Mod Options",
es = "Configuración de mods",
ru = "Настройки модов",
["zh-cn"] = "模组选项",
}
})
local dmf_option_definition = {
text = "mods_options",
type = "button",
icon = "content/ui/materials/icons/system/escape/settings",
trigger_function = function()
local context = {
can_exit = true,
}
local view_name = "dmf_options_view"
Managers.ui:open_view(view_name, nil, nil, nil, nil, context)
end,
}
local function is_options_button(item)
return item.text == "loc_options_view_display_name"
end
-- Inject DMF Options button into the Esc menu
dmf:hook_require("scripts/ui/views/system_view/system_view_content_list", function(instance)
-- Don't re-inject if it's already there
if table.find_by_key(instance.default, "text", dmf_option_definition.text) then
return
end
instance.default = insert_before(instance.default, is_options_button, dmf_option_definition)
instance.StateMainMenu = insert_before(instance.StateMainMenu, is_options_button, dmf_option_definition)
end)
-- #################################################################################################################### -- ####################################################################################################################
-- ##### DMF internal functions and variables ######################################################################### -- ##### DMF internal functions and variables #########################################################################
-- #################################################################################################################### -- ####################################################################################################################
@ -363,8 +464,61 @@ dmf.create_mod_options_settings = function (self, options_templates)
local categories = options_templates.categories local categories = options_templates.categories
local settings = options_templates.settings local settings = options_templates.settings
-- Create a category for every mod -- Create the toggle category
local toggle_category = create_toggle_category(self, categories)
local toggle_index_offset = 0
-- Create the toggle category header
local toggle_header_data = {
type = "header",
category = toggle_category,
title = dmf:localize("toggle_mods"),
mod_name = "dmf",
tooltip = dmf:localize("toggle_mods"),
}
local toggle_header = create_option_template(self, toggle_header_data, toggle_category.display_name, toggle_index_offset)
if toggle_header then
settings[#settings + 1] = toggle_header
end
-- Create the toggle category description
local desc_widget_data = {
mod_name = "dmf",
description = dmf:localize("toggle_mods_description"),
category = toggle_category.display_name,
display_name = toggle_category.display_name,
after = #settings,
type = "description"
}
local desc_template = create_option_template(self, desc_widget_data, toggle_category.display_name, toggle_index_offset)
if desc_template then
settings[#settings + 1] = desc_template
toggle_index_offset = toggle_index_offset + 1
end
-- Create a toggle for each mod; non-toggleable mods' toggles are disabled
for _, mod_data in ipairs(dmf.options_widgets_data) do for _, mod_data in ipairs(dmf.options_widgets_data) do
local toggle_widget_data = {
mod_name = mod_data[1].mod_name,
readable_mod_name = mod_data[1].readable_mod_name or mod_data[1].title,
description = mod_data[1].description,
disabled = not mod_data[1].is_togglable,
category = toggle_category.display_name,
after = #settings,
type = "mod_toggle"
}
local toggle_template = create_option_template(self, toggle_widget_data, toggle_category.display_name, toggle_index_offset)
if toggle_template then
settings[#settings + 1] = toggle_template
toggle_index_offset = toggle_index_offset + 1
end
end
-- Create a category for every mod that has additional settings
for _, mod_data in ipairs(dmf.options_widgets_data) do
if #mod_data > 1 then
local category = create_mod_category(self, categories, mod_data[1]) local category = create_mod_category(self, categories, mod_data[1])
local index_offset = 0 local index_offset = 0
@ -393,22 +547,6 @@ dmf.create_mod_options_settings = function (self, options_templates)
end end
end end
-- Create a top-level toggle option if the mod is togglable
if mod_data[1].is_togglable then
local toggle_widget_data = {
mod_name = mod_data[1].mod_name,
category = category.display_name,
after = #settings,
type = "mod_toggle"
}
local toggle_template = create_option_template(self, toggle_widget_data, category.display_name, index_offset)
if toggle_template then
settings[#settings + 1] = toggle_template
index_offset = index_offset + 1
end
end
-- Populate the category with options taken from the remaining options data -- Populate the category with options taken from the remaining options data
for i = 2, #mod_data do for i = 2, #mod_data do
local widget_data = mod_data[i] local widget_data = mod_data[i]
@ -423,6 +561,7 @@ dmf.create_mod_options_settings = function (self, options_templates)
end end
end end
end end
end
return options_templates return options_templates
end end

0
docs/.nojekyll Normal file
View file

1
docs/CNAME Normal file
View file

@ -0,0 +1 @@
dmf-docs.darkti.de

48
docs/index.html Normal file
View file

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>DMF Docs</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta
name="description"
content="The Darktide Mod Framework is an open-source, community-run framework of modules that provides enhanced modding capabilities and support."
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0"
/>
<link rel="stylesheet" media="(prefers-color-scheme: light)" href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css">
<link rel="stylesheet" media="(prefers-color-scheme: dark)" href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple-dark.css">
<style>
h1.app-name {
font-weight: 900;
text-transform: uppercase;
font-family: system-ui, "Helvetica Neue", Arial, sans-serif;
}
</style>
</head>
<body>
<div id="app"></div>
<script>
const repo = "Darktide-Mod-Framework/Darktide-Mod-Framework";
window.$docsify = {
name: "Darktide Mod Framework",
repo,
basePath: `https://raw.githubusercontent.com/wiki/${repo}/`,
coverpage: false,
alias: {
"/.*/_sidebar.md": "/_sidebar.md",
},
loadSidebar: true,
auto2top: true,
search: "auto",
};
</script>
<!-- Docsify v4 -->
<script src="https://cdn.jsdelivr.net/npm/docsify@4"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/js/docsify-themeable.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-lua.min.js"></script>
</body>
</html>

2024
docs/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

10
docs/package.json Normal file
View file

@ -0,0 +1,10 @@
{
"name": "dmf-docs",
"private": true,
"scripts": {
"start": "docsify serve ."
},
"dependencies": {
"docsify-cli": "^4.4.4"
}
}