Repository navigation
Expand file tree
/
Copy pathsettings_help.lua
More file actions
316 lines (312 loc) · 12.1 KB
/
Copy pathsettings_help.lua
File metadata and controls
316 lines (312 loc) · 12.1 KB
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
local Help = {}
local function pack(...) return { n = select("#", ...), ... } end
function Help.start(mod, rows, compatibility, preview)
if not compatibility.check("help") then return end
local Runtime = require("src.mods.Runtime")
local GameVersion = require("src.core.GameVersion")
local Manager = require("src.ui.game3.mod_manager")
local Stack = require("src.ui.game3.stack")
local Window = require("src.ui.game3.window")
local Font = require("src.ui.game3.frlg_font")
local lg = love.graphics
local details, zoomRow, zoomWarned, transitionRow = {}
for _, row in ipairs(rows) do
if row.key == "zoom" and row.type == "number" then zoomRow = row end
if row.key == "transition" and row.type == "choice" then transitionRow = row end
if type(row.key) == "string" and type(row.help) == "string" and row.help ~= "" then
details[row.key] = { label = row.label or row.key, text = row.help }
end
end
details.__reset = { label = "RESET DEFAULTS",
text = "Press A on RESET DEFAULTS to restore every Static Camera setting "
.. "to its default immediately. This does not change other mods. "
.. "Opening this help does not reset anything." }
local attachment, update
local failed = setmetatable({}, { __mode = "k" })
local function alive()
for _, link in ipairs((Runtime.hooks.chains or {})["core.update"] or {}) do
if link.callback == update then return true end
end
return false
end
local function supported()
local version = GameVersion.get()
return version == "firered" or version == "leafgreen" or version == "emerald"
end
local function managerLayer()
for _, layer in ipairs(Stack._layers) do
if layer.id == "mod_manager" then return layer end
end
end
local function pagesFor(text)
local lines, line = {}, ""
local function fits(s) return #s <= 26 and Font.measure(s) <= 208 end
local function flush()
if line ~= "" then lines[#lines + 1], line = line, "" end
end
for word in text:gmatch("%S+") do
if line ~= "" and fits(line .. " " .. word) then
line = line .. " " .. word
else
flush()
-- Split unusually long words rather than silently clipping help.
for i = 1, #word do
local char = word:sub(i, i)
if not fits(line .. char) then flush() end
line = line .. char
end
end
end
flush()
local pages = { {} }
for _, textLine in ipairs(lines) do
if #pages[#pages] == 4 then pages[#pages + 1] = {} end
local page = pages[#pages]
page[#page + 1] = textLine
end
return pages
end
local function bind(game, m, layer)
local oldOptions, rawOptions = m.updateOptions, rawget(m, "updateOptions")
local oldReset, rawReset = game.reset, rawget(game, "reset")
local originalModule = layer.mod
local wrappedOptions, wrappedReset, proxy, panel, detached, previewMod
local function close()
previewMod = nil
if not panel then return end
-- Remove only our actual layer, never somebody else's same-id layer.
for i = #Stack._layers, 1, -1 do
if Stack._layers[i].mod == panel then table.remove(Stack._layers, i) end
end
panel = nil
end
local function detach()
if detached then return end
detached = true
failed[m] = true
close()
if m.updateOptions == wrappedOptions then m.updateOptions = rawOptions end
if game.reset == wrappedReset then game.reset = rawReset end
if layer.mod == proxy then layer.mod = originalModule end
end
local function valid()
if detached then return false end
if not alive() or not supported() or not Manager.open or Manager._mgr ~= m
or Manager._game ~= game or managerLayer() ~= layer then
detach()
return false
end
return true
end
local function scoped()
return m.screen == "options" and m.currentMod and m.currentMod.id == "static_camera"
and not m.overlay and not Manager._prompt
end
local function previewUsable()
if not previewMod then return false end
if not valid() or not scoped() or m.currentMod ~= previewMod or Stack.top() ~= layer then
previewMod = nil
return false
end
return true
end
local function normalizeZoom()
if previewMod or Runtime.safeMode or not zoomRow or not scoped() or Stack.top() ~= layer then return end
local value = m:optionValue("static_camera", zoomRow)
local number = value
if type(number) ~= "number" or number ~= number or number == math.huge or number == -math.huge then
number = zoomRow.default
end
local lo, hi, step = zoomRow.min, zoomRow.max, zoomRow.step
number = math.max(lo, math.min(hi, number))
number = math.max(lo, math.min(hi, lo + math.floor((number - lo) / step + 0.5) * step))
if value ~= number and m:setOption("static_camera", "zoom", number) ~= false and not zoomWarned then
zoomWarned = true
mod.log:warn("Adjusted saved Static Camera zoom to %s%% to match the supported range and step.",
tostring(number))
end
end
local function normalizeTransition()
if previewMod or Runtime.safeMode or not transitionRow or not scoped() or Stack.top() ~= layer then return end
local value = m:optionValue("static_camera", transitionRow)
if value == "horizontal" or value == "vertical" then
m:setOption("static_camera", "transition", "slide")
end
end
local function normalizeSettings()
normalizeZoom()
normalizeTransition()
end
local function selected()
local row = (m.optionRows or {})[m.cursor]
return row and details[row.id]
end
local function guarded(fn, ...)
local r = pack(pcall(fn, ...))
if not r[1] then
failed[m] = true
detach()
error(r[2], 0)
end
return unpack(r, 2, r.n)
end
local function printLine(text, x, y, width)
Window.printPx(text, x, y, { colors = Font.COLOR.NORMAL, maxWidth = width or 208 })
end
local function drawPreview()
local rendered, reason
if Runtime.safeMode then
reason = "Camera preview is disabled in safe mode. Restart with mods enabled."
elseif type(preview) ~= "table" or type(preview.draw) ~= "function" then
reason = "Camera preview renderer is unavailable. Restart after updating Static Camera."
else
-- Restore the UI target and graphics state even if the renderer throws.
local depth, canvas = lg.getStackDepth(), pack(lg.getCanvas())
lg.push("all")
local result = pack(pcall(preview.draw, game))
while lg.getStackDepth() > depth do lg.pop() end
lg.setCanvas(unpack(canvas, 1, canvas.n))
if not result[1] then error(result[2], 0) end
rendered, reason = result[2], result[3]
end
if rendered ~= true then
lg.setColor(0, 0, 0, 1)
lg.rectangle("fill", 0, 0, 240, 160)
lg.setColor(1, 1, 1, 1)
printLine("PREVIEW UNAVAILABLE", 16, 25)
if type(reason) ~= "string" or reason == "" then
reason = "The current game context does not support camera preview. Return to the overworld and try again."
end
for i, text in ipairs(pagesFor(reason)[1]) do printLine(text, 16, 58 + (i - 1) * 18) end
end
lg.setColor(0, 0, 0, 1)
lg.rectangle("fill", 0, 0, 64, 16)
lg.setColor(1, 1, 1, 1)
printLine("B:BACK", 4, 0, 60)
end
local function open(detail)
local pages, page = pagesFor(detail.text), 1
local owned = {}
panel = owned
local function usable()
if not valid() then return false end
if not scoped() or not Stack.top() or Stack.top().mod ~= owned then
close()
return false
end
return true
end
owned.handleInput = function(input)
return guarded(function()
if not usable() then return end
if input:wasPressed("b") or input:wasPressed("select") then close()
elseif input:wasPressed("left") or input:wasPressed("up") then
page = math.max(1, page - 1)
elseif input:wasPressed("right") or input:wasPressed("down") then
page = math.min(#pages, page + 1)
end
end)
end
owned.update = function() return guarded(usable) end
owned.draw = function()
return guarded(function()
if not usable() then return end
lg.setColor(0, 0, 0, 1)
lg.rectangle("fill", 0, 0, 240, 160)
lg.setColor(1, 1, 1, 1)
printLine("B/Select:BACK", 16, 0)
Window.fixedStdFrame(Window.template(2, 3, 26, 2))
Window.userFrame(Window.template(2, 7, 26, 12), 0)
printLine(detail.label:sub(1, 18), 16, 25, 160)
printLine(page .. "/" .. #pages, 184, 25, 40)
for i, text in ipairs(pages[page]) do printLine(text, 16, 58 + (i - 1) * 18) end
printLine("LEFT/RIGHT:PAGE", 16, 136)
end)
end
Stack.push("static_camera_settings_help", owned, { fullscreen = true })
end
wrappedOptions = function(self, input, ...)
if not valid() then return oldOptions(self, input, ...) end
if previewMod then
previewUsable()
return
end
if panel then return end
if scoped() and Stack.top() == layer and selected() and input:wasPressed("select") then
guarded(normalizeSettings)
return guarded(open, selected())
end
if scoped() and Stack.top() == layer and input:wasPressed("start") then
previewMod = m.currentMod
return
end
guarded(normalizeSettings)
return guarded(oldOptions, self, input, ...)
end
proxy = setmetatable({
handleInput = function(input, ...)
if previewMod then
if previewUsable() and input:wasPressed("b") then close() end
return
end
return guarded(originalModule.handleInput, input, ...)
end,
draw = function(...)
if not valid() then return originalModule.draw(...) end
if previewUsable() then return guarded(drawPreview) end
guarded(normalizeTransition)
local r = pack(guarded(originalModule.draw, ...))
if scoped() and Stack.top() == layer then
if selected() then guarded(printLine, "Select:HELP", 32, 119, 176) end
guarded(printLine, "Start:PREVIEW", 32, 132, 176)
end
return unpack(r, 1, r.n)
end,
}, { __index = originalModule })
wrappedReset = function(self, ...)
detach()
return oldReset(self, ...)
end
m.updateOptions, layer.mod, game.reset = wrappedOptions, proxy, wrappedReset
return { game = game, manager = m, layer = layer, dispose = detach,
valid = valid, refresh = function()
previewUsable()
if panel and (not scoped() or not Stack.top() or Stack.top().mod ~= panel) then close() end
guarded(normalizeSettings)
end }
end
local function dispose()
if attachment then attachment.dispose(); attachment = nil end
end
update = function(nextUpdate, game, dt)
local r = pack(pcall(nextUpdate, game, dt))
if not r[1] then dispose(); error(r[2], 0) end
if not compatibility.allowed() or not alive() or not supported() or not game then
dispose()
else
local m, layer = Manager._mgr, managerLayer()
if attachment and (attachment.game ~= game or attachment.manager ~= m
or attachment.layer ~= layer or not attachment.valid()) then dispose() end
if Manager.open and Manager._game == game and m and layer and not failed[m] then
if not attachment then
if type(m.updateOptions) == "function" and type(game.reset) == "function" then
attachment = bind(game, m, layer)
else
compatibility.warn("help-lifecycle", "Static Camera help unavailable: unsupported settings lifecycle.")
failed[m] = true
end
end
if attachment then attachment.refresh() end
else
dispose()
end
end
return unpack(r, 2, r.n)
end
mod.hooks:wrap("core.update", update)
mod.hooks:wrap("core.quit_to_launcher", function(nextQuit, ...)
dispose()
return nextQuit(...)
end)
end
return Help