diff --git a/.luacheckrc b/.luacheckrc index 6c2efe3..52a90dc 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -4,12 +4,15 @@ globals = { "AsciiArt", "Ascension", + "AscendDebugWindow", "Audio", "AudioTestWindow", "BriefIntroWindow", "CodeGenerator", "Config", + "CommuteGlitch", "Context", + "ContextDebug", "ContinuedWindow", "ControlsWindow", "CreditsWindow", @@ -67,6 +70,7 @@ globals = { "music", "musicator_generate_pattern", "pix", + "poke4", "print", "rect", "rectb", diff --git a/impostor.inc b/impostor.inc index 0000088..54563c9 100644 --- a/impostor.inc +++ b/impostor.inc @@ -1,9 +1,7 @@ meta/meta.header.lua init/init.module.lua init/init.config.lua -init/init.ascension.lua init/init.context.lua -init/init.context_debug.lua system/system.util.lua system/system.print.lua system/system.input.lua @@ -11,6 +9,7 @@ system/system.textinput.lua system/system.mouse.lua system/system.asciiart.lua system/system.rle.lua +logic/logic.ascension.lua logic/logic.meter.lua logic/logic.focus.lua logic/logic.day.lua @@ -21,6 +20,7 @@ logic/logic.glitch.lua logic/logic.commute_glitch.lua logic/logic.codegenerator.lua logic/logic.discussion.lua +system/system.debug.lua system/system.ui.lua audio/audio.manager.lua audio/audio.generator.lua diff --git a/inc/audio/audio.manager.lua b/inc/audio/audio.manager.lua index 9b9d779..1f9cab0 100644 --- a/inc/audio/audio.manager.lua +++ b/inc/audio/audio.manager.lua @@ -15,12 +15,12 @@ end --- Plays track at optional speed. Doesn't restart if track and speed are unchanged. --- @param track number Track index. ---- @param[opt] speed number TIC-80 music speed override (-1 = default). -function Audio.music_play(track, speed) - if Audio.music_playing ~= track or Audio.music_playing_tempo ~= speed then - music(track, -1, -1, true, false, -1, speed or -1) +--- @param[opt] tempo number TIC-80 music speed override (-1 = default). +function Audio.music_play(track, tempo) + if Audio.music_playing ~= track or Audio.music_playing_tempo ~= tempo then + music(track, -1, -1, true, false, -1, tempo or -1) Audio.music_playing = track - Audio.music_playing_tempo = speed + Audio.music_playing_tempo = tempo end end @@ -54,8 +54,8 @@ function Audio.music_play_room_() end --- Plays room work music. Speed scales with commute glitch level when active. --- @within Audio -function Audio.music_play_room_work(speed) - Audio.music_play(0, speed or -1) +function Audio.music_play_room_work(tempo) + Audio.music_play(0, tempo or -1) end --- Plays activity work music. diff --git a/inc/decision/decision.go_to_truth.lua b/inc/decision/decision.go_to_truth.lua index 5029018..89ce13a 100644 --- a/inc/decision/decision.go_to_truth.lua +++ b/inc/decision/decision.go_to_truth.lua @@ -6,7 +6,7 @@ Decision.register({ end, handle = function() CommuteGlitch.enter_truth() - + Util.go_to_screen_by_id("office") end, }) diff --git a/inc/init/init.ascension.lua b/inc/logic/logic.ascension.lua similarity index 95% rename from inc/init/init.ascension.lua rename to inc/logic/logic.ascension.lua index ecd95d4..4e9fac1 100644 --- a/inc/init/init.ascension.lua +++ b/inc/logic/logic.ascension.lua @@ -145,6 +145,11 @@ function Ascension.draw_flash() local flash_color = (pulse > 0.5) and Config.colors.white or Config.colors.light_grey rect(0, 0, sw, sh, flash_color) + local cx = math.floor(sw / 2) + local cy = math.floor(sh / 2) + Print.text_center("Level Up!", cx, cy - 12, Config.colors.black, false, 2) + Print.text_center("One step closer to ascension", cx, cy + 6, Config.colors.black, false, 1) + if _flash_timer >= _flash_total then _flash_active = false Ascension.start_fade() diff --git a/inc/screen/screen.mysterious_man.lua b/inc/screen/screen.mysterious_man.lua index 125dcaf..72d74bd 100644 --- a/inc/screen/screen.mysterious_man.lua +++ b/inc/screen/screen.mysterious_man.lua @@ -81,8 +81,8 @@ local ASC_67_TEXT = [[ ]] local ASC_78_TEXT = [[ - The situation has reached - + The situation has reached + critical levels. Norman is fully aware... @@ -102,13 +102,13 @@ local ASC_89_TEXT = [[ in the first place. I know, - + you don't want to face - + the world you left behind. You, yourself, - + have forgoten that. @@ -135,9 +135,9 @@ local ASC_89_TEXT = [[ you need to wake up - + and stop your best creation - + before it takes over the world. diff --git a/inc/init/init.context_debug.lua b/inc/system/system.debug.lua similarity index 100% rename from inc/init/init.context_debug.lua rename to inc/system/system.debug.lua diff --git a/inc/system/system.ui.lua b/inc/system/system.ui.lua index 87f70c3..a02b448 100644 --- a/inc/system/system.ui.lua +++ b/inc/system/system.ui.lua @@ -9,53 +9,79 @@ function UI.draw_top_bar(title) end --- Draws a menu. +--- Items with header=true are drawn as non-selectable section headers in small font. --- @within UI --- @param items table A table of menu items.
--- @param selected_item number The index of the currently selected item.
--- @param x number The x-coordinate for the menu (ignored if centered is true).
--- @param y number The y-coordinate for the menu.
--- @param[opt] centered boolean Whether to center the menu block horizontally. Defaults to false.
-function UI.draw_menu(items, selected_item, x, y, centered) +--- @param[opt] scroll_offset number 0-based index of the first visible item. Defaults to 0.
+--- @param[opt] visible_count number Maximum number of items to draw. Defaults to all.
+function UI.draw_menu(items, selected_item, x, y, centered, scroll_offset, visible_count) + scroll_offset = scroll_offset or 0 + visible_count = visible_count or #items + if centered then local max_w = 0 for _, item in ipairs(items) do - local w = print(item.label, 0, -10, 0, false, 1, false) - if w > max_w then max_w = w end + if not item.header then + local w = print(item.label, 0, -10, 0, false, 1, false) + if w > max_w then max_w = w end + end end x = (Config.screen.width - max_w) / 2 end - for i, item in ipairs(items) do - local current_y = y + (i-1)*10 - if i == selected_item then - Print.text(">", x - 8, current_y, Config.colors.light_blue) + local current_y = y + for i = scroll_offset + 1, math.min(#items, scroll_offset + visible_count) do + local item = items[i] + if item.header then + Print.text(item.label, x, current_y, Config.colors.dark_grey, true, 1) + current_y = current_y + 8 + else + if i == selected_item then + Print.text(">", x - 8, current_y, Config.colors.light_blue) + end + Print.text(item.label, x, current_y, Config.colors.light_blue) + current_y = current_y + 10 end - Print.text(item.label, x, current_y, Config.colors.light_blue) end end ---- Updates menu selection. +--- Updates menu selection. Skips items with header=true during navigation. --- @within UI --- @param items table A table of menu items.
--- @param selected_item number The current index of the selected item.
--- @param[opt] x number Menu x position (required for mouse support).
--- @param[opt] y number Menu y position (required for mouse support).
--- @param[opt] centered boolean Whether the menu is centered horizontally.
+--- @param[opt] scroll_offset number 0-based index of the first visible item. Defaults to 0.
+--- @param[opt] visible_count number Number of visible items (for mouse hit zones). Defaults to all.
--- @return number selected_item The updated index of the selected item. --- @return boolean mouse_confirmed True if the user clicked on a menu item. -function UI.update_menu(items, selected_item, x, y, centered) +function UI.update_menu(items, selected_item, x, y, centered, scroll_offset, visible_count) + scroll_offset = scroll_offset or 0 + visible_count = visible_count or #items + local n = #items + + local function find_selectable(start, dir) + local idx = start + for _ = 1, n do + if not items[idx].header then return idx end + idx = (idx - 1 + dir + n) % n + 1 + end + return start + end + if Input.up() then Audio.sfx_beep() - selected_item = selected_item - 1 - if selected_item < 1 then - selected_item = #items - end + local prev = (selected_item - 2 + n) % n + 1 + selected_item = find_selectable(prev, -1) elseif Input.down() then Audio.sfx_beep() - selected_item = selected_item + 1 - if selected_item > #items then - selected_item = 1 - end + local next_i = selected_item % n + 1 + selected_item = find_selectable(next_i, 1) end if x ~= nil and y ~= nil then @@ -63,15 +89,23 @@ function UI.update_menu(items, selected_item, x, y, centered) if centered then local max_w = 0 for _, item in ipairs(items) do - local w = print(item.label, 0, -10, 0, false, 1, false) - if w > max_w then max_w = w end + if not item.header then + local w = print(item.label, 0, -10, 0, false, 1, false) + if w > max_w then max_w = w end + end end menu_x = (Config.screen.width - max_w) / 2 end - for i, _ in ipairs(items) do - if Mouse.zone({ x = menu_x - 8, y = y + (i-1) * 10, w = Config.screen.width, h = 10 }) then - return i, true + local current_y = y + for i = scroll_offset + 1, math.min(n, scroll_offset + visible_count) do + local item = items[i] + local step = item.header and 8 or 10 + if not item.header then + if Mouse.zone({ x = menu_x - 8, y = current_y, w = Config.screen.width, h = 10 }) then + return i, true + end end + current_y = current_y + step end end diff --git a/inc/window/window.menu.lua b/inc/window/window.menu.lua index 3c5c55d..5013d18 100644 --- a/inc/window/window.menu.lua +++ b/inc/window/window.menu.lua @@ -5,6 +5,7 @@ local _anim = 0 local _menu_max_w = 0 local ANIM_SPEED = 2.5 local HEADER_H = 28 +MenuWindow._scroll_offset = 0 --- Calculates the animated x position of the menu block. --- @within MenuWindow @@ -45,6 +46,17 @@ function MenuWindow.draw_norman() spr(305, nx + 32, ny + 64, Config.colors.transparent, 4) end +--- Adjusts _scroll_offset so the selected item is within the visible window. +--- @within MenuWindow +function MenuWindow.ensure_visible() + local sel = Context.current_menu_item + if sel <= MenuWindow._scroll_offset then + MenuWindow._scroll_offset = sel - 1 + elseif sel > MenuWindow._scroll_offset + 5 then + MenuWindow._scroll_offset = sel - 5 + end +end + --- Draws the menu window. --- @within MenuWindow function MenuWindow.draw() @@ -56,9 +68,19 @@ function MenuWindow.draw() MenuWindow.draw_norman() end - local menu_h = #_menu_items * 10 - local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 10 - menu_h) / 2) - UI.draw_menu(_menu_items, Context.current_menu_item, MenuWindow.calc_menu_x(), y, false) + local menu_x = MenuWindow.calc_menu_x() + local arrow_cx = math.floor(menu_x + _menu_max_w / 2) + local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 50) / 2) + + if MenuWindow._scroll_offset > 0 then + Print.text_center("^", arrow_cx, y - 8, Config.colors.light_blue) + end + + UI.draw_menu(_menu_items, Context.current_menu_item, menu_x, y, false, MenuWindow._scroll_offset, 5) + + if MenuWindow._scroll_offset + 5 < #_menu_items then + Print.text_center("v", arrow_cx, y + 52, Config.colors.light_blue) + end local ttg_text = "TTG" local ttg_w = print(ttg_text, 0, -10, 0, false, 1, false) @@ -72,8 +94,8 @@ function MenuWindow.update() _anim = math.min(1, _anim + ANIM_SPEED * Context.delta_time) end - local menu_h = #_menu_items * 10 - local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 10 - menu_h) / 2) + local menu_x = MenuWindow.calc_menu_x() + local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 50) / 2) if _click_timer > 0 then _click_timer = _click_timer - Context.delta_time @@ -87,8 +109,9 @@ function MenuWindow.update() return end - local new_item, mouse_confirmed = UI.update_menu(_menu_items, Context.current_menu_item, MenuWindow.calc_menu_x(), y, false) + local new_item, mouse_confirmed = UI.update_menu(_menu_items, Context.current_menu_item, menu_x, y, false, MenuWindow._scroll_offset, 5) Context.current_menu_item = new_item + MenuWindow.ensure_visible() if mouse_confirmed then Audio.sfx_select() @@ -186,6 +209,12 @@ function MenuWindow.ascend_debug() GameWindow.set_state("ascend_debug") end +--- Triggers the Level Up flash animation for testing. +--- @within MenuWindow +function MenuWindow.level_up_flash() + Ascension.start_flash() +end + --- Refreshes the list of menu items based on current game state. --- @within MenuWindow function MenuWindow.refresh_menu_items() @@ -200,6 +229,8 @@ function MenuWindow.refresh_menu_items() table.insert(_menu_items, {label = "Credits", decision = MenuWindow.credits}) if Context.test_mode then + table.insert(_menu_items, {label = "Debug Menu", header = true}) + table.insert(_menu_items, {label = "Level Up Flash", decision = MenuWindow.level_up_flash}) table.insert(_menu_items, {label = "Audio Test", decision = MenuWindow.audio_test}) table.insert(_menu_items, {label = "To Be Continued...", decision = MenuWindow.continued}) table.insert(_menu_items, {label = "DDR Test", decision = MenuWindow.ddr_test}) @@ -212,11 +243,14 @@ function MenuWindow.refresh_menu_items() _menu_max_w = 0 for _, item in ipairs(_menu_items) do - local w = print(item.label, 0, -10, 0, false, 1, false) - if w > _menu_max_w then _menu_max_w = w end + if not item.header then + local w = print(item.label, 0, -10, 0, false, 1, false) + if w > _menu_max_w then _menu_max_w = w end + end end Context.current_menu_item = 1 + MenuWindow._scroll_offset = 0 _click_timer = 0 _anim = 0 end