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