-
-
Notifications
You must be signed in to change notification settings - Fork 8
/
settings.lua
443 lines (416 loc) · 17.7 KB
/
settings.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
-- Copyright (c) 2022-2024 David Vogel
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
-----------------------
-- Load global stuff --
-----------------------
-- Emulate and override some functions and tables to make everything conform more to standard lua.
-- This will make `require` work, even in sandboxes with restricted Noita API.
local libPath = "mods/noita-mapcap/files/libraries/"
dofile(libPath .. "noita-api/compatibility.lua")(libPath)
-- TODO: Replace Noita's mod settings lib with something better. Or at least wrap it: https://stackoverflow.com/questions/9540732/loadfile-without-polluting-global-environment
require("mod_settings") -- Loads Noita's mod settings library from `data/scripts/lib/mod_settings.lua`.
--------------------------
-- Load library modules --
--------------------------
local DebugAPI = require("noita-api.debug")
-------------------------------
-- Load and run script files --
-------------------------------
----------
-- Code --
----------
---Custom button gadget for the settings menu.
---@param modID string
---@param gui any
---@param inMainMenu boolean
---@param imID any
---@param setting table
local function customSettingButton(modID, gui, inMainMenu, imID, setting)
local text = setting.ui_name
local clicked, right_clicked = GuiButton(gui, imID, mod_setting_group_x_offset, 0, text)
if clicked then
mod_setting_handle_change_callback(modID, gui, inMainMenu, setting, true, true)
end
mod_setting_tooltip(modID, gui, inMainMenu, setting)
end
---Round changed value to the closest integer.
---@param modID string
---@param gui any
---@param inMainMenu boolean
---@param setting table
---@param oldValue number
---@param newValue number
local function roundChange(modID, gui, inMainMenu, setting, oldValue, newValue)
ModSettingSetNextValue(mod_setting_get_id(modID, setting), math.floor(newValue + 0.5), false)
end
-- This is a magic global (eww) that can be used to migrate settings to new mod versions.
-- Call `mod_settings_get_version()` before `mod_settings_update()` to get the old value.
mod_settings_version = 1
local modID = "noita-mapcap"
local modSettings
modSettings = {
{
id = "capture-mode",
ui_name = "Mode",
ui_description = "How the mod captures:\n- Live: Capture as you play along.\n- Area: Capture a defined area of the world.\n- Spiral: Capture in a spiral around a starting point indefinitely.\n- Animation: Capture the screen frame by frame.",
value_default = "live",
values = { { "live", "Live" }, { "area", "Area" }, { "spiral", "Spiral" }, { "animation", "Animation"} },
scope = MOD_SETTING_SCOPE_RUNTIME,
},
{
id = "capture-mode-spiral-origin",
ui_name = " Origin",
ui_description = "The starting point or center of the spiral.\n- Current position: Your in-game position.\n- World origin: Near the cave entrance.\n- Custom position: Enter your own coordinates.",
value_default = "current",
values = { { "current", "Current position" }, { "0", "World origin" }, { "custom", "Custom position" } },
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return modSettings:GetNextValue("capture-mode") == "spiral" end,
},
{
id = "capture-mode-spiral-origin-vector",
ui_name = " Origin",
ui_description = "",
value_default = "0,0",
allowed_characters = "-0123456789,",
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return not modSettings:Get("capture-mode-spiral-origin.hidden") and modSettings:GetNextValue("capture-mode-spiral-origin") == "custom" end,
},
{
id = "area",
ui_name = " Rectangle",
ui_description = "The area to be captured.\nSee documentation for more information.",
value_default = "1x1",
values = { { "1x1", "Base layout" }, { "1x3", "Main World" }, { "1x3 -1", "-1 Parallel World" }, { "1x3 +1", "+1 Parallel World" }, { "1.5x3", "Extended" }, { "3x3", "3 Worlds" }, { "custom", "Custom" } },
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return modSettings:GetNextValue("capture-mode") == "area" end,
},
{
id = "area-top-left",
ui_name = " Top left corner",
ui_description = "The top left corner of the to be captured rectangle.\n \nDefault: -512,-512", -- TODO: Fix "right click for default" for text inputs
value_default = "-512,-512",
allowed_characters = "-0123456789,",
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return not modSettings:Get("area.hidden") and modSettings:GetNextValue("area") == "custom" end,
},
{
id = "area-bottom-right",
ui_name = " Bottom right corner",
ui_description = "The bottom right corner of the to be captured rectangle.\n \nDefault: 512,512",
value_default = "512,512",
allowed_characters = "-0123456789,",
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return not modSettings:Get("area.hidden") and modSettings:GetNextValue("area") == "custom" end,
},
{
ui_fn = mod_setting_vertical_spacing,
not_setting = true,
},
{
category_id = "advanced",
ui_name = "ADVANCED",
ui_description = "- A D V A N C E D -",
foldable = true,
_folded = true,
settings = {
{
id = "seed",
ui_name = "World seed",
ui_description = "Lock the world to the given seed\n \nClear field to use a random seed.",
value_default = DebugAPI.IsDevBuild() and "123" or "",
allowed_characters = "0123456789",
scope = MOD_SETTING_SCOPE_NEW_GAME,
},
{
id = "grid-size",
ui_name = "Grid size",
ui_description = "How many world pixels the viewport will move between screenshots.\n \nDefault: 512",
value_default = "512",
allowed_characters = "0123456789",
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return modSettings:GetNextValue("capture-mode") == "area" or modSettings:GetNextValue("capture-mode") == "spiral" end,
},
{
id = "pixel-scale",
ui_name = "Pixel scale",
ui_description = "How big a single resulting pixel will be.\nThis is the ratio of image to world pixels.\nA setting of 0 disables any scaling.\n \nDon't change while capturing,\nOr you will get unstitchable results.",
value_default = 1,
value_min = 0,
value_max = 8,
value_display_multiplier = 1,
value_display_formatting = " $0 pixels/pixel",
scope = MOD_SETTING_SCOPE_RUNTIME,
change_fn = roundChange,
},
{
id = "capture-delay",
ui_name = "Capture delay",
ui_description = "Additional delay before a screen capture is taken.\nThis can help the world to be populated, and the physics simulation to settle down.\nA setting of 0 means that the screenshot is taken as soon as possible.\n \nUse a value of 10 for a good result without slowing the capture process down too much.",
value_default = 0,
value_min = 0,
value_max = 60,
value_display_multiplier = 1,
value_display_formatting = " $0 frames",
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return modSettings:GetNextValue("capture-mode") == "area" or modSettings:GetNextValue("capture-mode") == "spiral" end,
},
{
id = "custom-resolution-live",
ui_name = "Use custom resolution",
ui_description = "If enabled, the mod will change the game resolutions to custom values.",
value_default = false,
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return modSettings:GetNextValue("capture-mode") == "live" end,
},
{
id = "custom-resolution-other",
ui_name = "Use custom resolution",
ui_description = "If enabled, the mod will change the game resolutions to custom values.",
value_default = true,
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return modSettings:GetNextValue("capture-mode") ~= "live" end,
},
{
id = "window-resolution",
ui_name = " Window resolution",
ui_description = "Size of the window in screen pixels.\n \nDefault: 1024,1024",
value_default = "1024,1024",
allowed_characters = "0123456789,",
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function()
return (not modSettings:Get("advanced.settings.custom-resolution-live.hidden") and modSettings:GetNextValue("advanced.settings.custom-resolution-live"))
or (not modSettings:Get("advanced.settings.custom-resolution-other.hidden") and modSettings:GetNextValue("advanced.settings.custom-resolution-other"))
end,
},
{
id = "internal-resolution",
ui_name = " Internal resolution",
ui_description = "Size of the viewport in screen pixels.\nIdeally set to the window resolution.\n \nDefault: 1024,1024",
value_default = "1024,1024",
allowed_characters = "0123456789,",
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function()
return (not modSettings:Get("advanced.settings.custom-resolution-live.hidden") and modSettings:GetNextValue("advanced.settings.custom-resolution-live"))
or (not modSettings:Get("advanced.settings.custom-resolution-other.hidden") and modSettings:GetNextValue("advanced.settings.custom-resolution-other"))
end,
},
{
id = "virtual-resolution",
ui_name = " Virtual resolution",
ui_description = "Size of the viewport in world pixels.\nIdeally set to the window resolution.\n \nDefault: 1024,1024",
value_default = "1024,1024",
allowed_characters = "0123456789,",
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function()
return (not modSettings:Get("advanced.settings.custom-resolution-live.hidden") and modSettings:GetNextValue("advanced.settings.custom-resolution-live"))
or (not modSettings:Get("advanced.settings.custom-resolution-other.hidden") and modSettings:GetNextValue("advanced.settings.custom-resolution-other"))
end,
},
{
ui_fn = mod_setting_vertical_spacing,
not_setting = true,
show_fn = function() return modSettings:GetNextValue("capture-mode") == "live" end,
},
{
id = "live-interval",
ui_name = "Capture interval",
ui_description = "Capturing interval in frames.",
value_default = 30,
value_min = 5,
value_max = 240,
value_display_multiplier = 1,
value_display_formatting = " $0 frames",
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return modSettings:GetNextValue("capture-mode") == "live" end,
},
{
id = "live-min-distance",
ui_name = "Min. capture distance",
ui_description = "The distance the viewport has to move to allow another screenshot.\nIn world pixels.",
value_default = 10,
value_min = 0,
value_max = 200,
value_display_multiplier = 1,
value_display_formatting = " $0 pixels",
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return modSettings:GetNextValue("capture-mode") == "live" end,
},
{
id = "live-max-distance",
ui_name = "Max. capture distance",
ui_description = "The distance the viewport has to move to force another screenshot.\nIn world pixels.",
value_default = 50,
value_min = 0,
value_max = 200,
value_display_multiplier = 1,
value_display_formatting = " $0 pixels",
scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return modSettings:GetNextValue("capture-mode") == "live" end,
},
{
ui_fn = mod_setting_vertical_spacing,
not_setting = true,
},
{
id = "capture-entities",
ui_name = "Capture entities",
ui_description = "If enabled, the mod will create a JSON file with all encountered entities.\n \nThis may slow down things a bit.\nAnd it may make Noita more likely to crash.\nUse at your own risk.",
value_default = false,
scope = MOD_SETTING_SCOPE_RUNTIME,
},
{
ui_fn = mod_setting_vertical_spacing,
not_setting = true,
},
{
id = "",
ui_name = "Game modifications:",
not_setting = true,
},
{
id = "disable-background",
ui_name = " Disable parallax background",
ui_description = "Turns the world background black.",
value_default = DebugAPI.IsDevBuild(), -- Defaults to true in dev build, false in regular Noita.
scope = MOD_SETTING_SCOPE_RUNTIME_RESTART,
},
{
id = "disable-ui",
ui_name = " Disable UI",
ui_description = "Hides and disables some of the UI.",
value_default = false,
scope = MOD_SETTING_SCOPE_RUNTIME_RESTART,
},
{
id = "disable-physics",
ui_name = " Disable pixel and entity physics",
ui_description = "Will freeze all pixel simulations and rigid body dynamics.",
hidden = not DebugAPI.IsDevBuild(),
value_default = DebugAPI.IsDevBuild(), -- Defaults to true in dev build, false in regular Noita.
scope = MOD_SETTING_SCOPE_RUNTIME_RESTART,
},
{
id = "disable-postfx",
ui_name = " Disable post FX",
ui_description = "Will disable the following postprocessing:\n- Dithering\n- Refraction\n- Lighting\n- Fog of war\n- Glow\n- Gamma correction",
value_default = DebugAPI.IsDevBuild(), -- Defaults to true in dev build, false in regular Noita.
scope = MOD_SETTING_SCOPE_RUNTIME_RESTART,
},
{
id = "disable-shaders-gui-ai",
ui_name = " Disable shaders, GUI and AI",
ui_description = "It has the same effect as pressing F5, F8 and F12 in the Noita dev build.\nDoesn't work outside the dev build.",
hidden = not DebugAPI:IsDevBuild(), -- Hide in anything else than the dev build.
value_default = DebugAPI.IsDevBuild(), -- Defaults to true in dev build, false in regular Noita.
scope = MOD_SETTING_SCOPE_RUNTIME,
},
{
id = "modify-entities",
ui_name = " Disable entity logic",
ui_description = "If enabled, the mod will disable some components of all encountered entities.\nThis will:\n- Disable AI\n- Disable falling\n- Disable hovering and rotation animations\n- Reduce explosions\n \nThis may slow down things a bit.\nAnd it may make Noita more likely to crash.\nUse at your own risk.",
value_default = false,
scope = MOD_SETTING_SCOPE_RUNTIME,
},
{
id = "disable-mod-detection",
ui_name = " Disable mod detection",
ui_description = "If enabled, Noita will behave as if no mods are enabled.\nTherefore secrets like the cauldron will be generated.",
hidden = DebugAPI.IsDevBuild(),
value_default = false,
scope = MOD_SETTING_SCOPE_RUNTIME,
},
}
},
}
---Hide/unhide some settings based on other settings.
function modSettings:AutoHide()
for i, v in ipairs(self) do
if v.show_fn then
v.hidden = not v.show_fn()
end
if type(v.settings) == "table" then
modSettings.AutoHide(v.settings)
end
end
end
---Returns the element at the given path of the settings hierarchy.
---@param path string -- Example: "category1.settings.foo.ui_name"
---@return any -- Result can be a settings table, a category table, or any value inside these tables.
function modSettings:Get(path)
-- Return whatever we have found.
if path == "" then
return self
end
-- Split path into first element and rest.
local first, rest = path, ""
for v in path:gmatch("[^%.]+") do
first, rest = v, path:sub(v:len() + 2, -1)
break
end
-- Abort if element is not a table/array, as we expect a sub element.
if type(self) ~= "table" then return nil end
-- Search array of settings/categories.
for _, v in ipairs(self) do
if type(v) == "table" then
if v.id == first then
return modSettings.Get(v, rest) -- Found settings table.
elseif v.category_id == first then
return modSettings.Get(v, rest) -- Found category table.
end
end
end
-- Search in field of table.
return modSettings.Get(self[first], rest)
end
---Returns combination of modID and settings ID of the given settings element at `path`.
---@param path string
---@return string|nil
function modSettings:GetID(path)
local setting = modSettings:Get(path)
if type(setting) == "table" and type(setting.id) == "string" then
return string.format("%s.%s", modID, setting.id)
end
return nil
end
---Returns the latest value set by the user, which might not be equal to the value that is used in the game (depending on the 'scope' value selected for the setting).
---@param path string
---@return boolean|string|number|nil
function modSettings:GetNextValue(path)
local id = modSettings:GetID(path)
if not id then return nil end
return ModSettingGetNextValue(id)
end
--------------------
-- Hook callbacks --
--------------------
---This function is called to ensure the correct setting values are visible to the game via `ModSettingGet()`.
---Your mod's settings don't work if you don't have a function like this defined in settings.lua.
---This function is called:
--- - when entering the mod settings menu (initScope will be `MOD_SETTINGS_SCOPE_ONLY_SET_DEFAULT`)
--- - before mod initialization when starting a new game (initScope will be `MOD_SETTING_SCOPE_NEW_GAME`)
--- - when entering the game after a restart (initScope will be `MOD_SETTING_SCOPE_RESTART`)
--- - at the end of an update when mod settings have been changed via `ModSettingsSetNextValue()` and the game is unpaused (initScope will be `MOD_SETTINGS_SCOPE_RUNTIME`)
---@param initScope number
function ModSettingsUpdate(initScope)
local oldVersion = mod_settings_get_version(modID)
mod_settings_update(modID, modSettings, initScope)
end
---This function should return the number of visible setting UI elements.
---Your mod's settings wont be visible in the mod settings menu if this function isn't defined correctly.
---If your mod changes the displayed settings dynamically, you might need to implement custom logic.
---The value will be used to determine whether or not to display various UI elements that link to mod settings.
---At the moment it is fine to simply return 0 or 1 in a custom implementation, but we don't guarantee that will be the case in the future.
---This function is called every frame when in the settings menu.
function ModSettingsGuiCount()
return mod_settings_gui_count(modID, modSettings)
end
---This function is called to display the settings UI for this mod.
---Your mod's settings wont be visible in the mod settings menu if this function isn't defined correctly.
---@param gui any
---@param inMainMenu boolean
function ModSettingsGui(gui, inMainMenu)
modSettings:AutoHide()
mod_settings_gui(modID, modSettings, gui, inMainMenu)
end