Merge branch 'feature/IMP-112-ascension-8-9' of https://git.teletype.hu/games/impostor into feature/IMP-112-ascension-8-9
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

# Conflicts:
#	inc/screen/screen.mysterious_man.lua
This commit is contained in:
Zoltan Timar
2026-04-29 23:21:42 +02:00
9 changed files with 125 additions and 48 deletions

View File

@@ -4,12 +4,15 @@
globals = { globals = {
"AsciiArt", "AsciiArt",
"Ascension", "Ascension",
"AscendDebugWindow",
"Audio", "Audio",
"AudioTestWindow", "AudioTestWindow",
"BriefIntroWindow", "BriefIntroWindow",
"CodeGenerator", "CodeGenerator",
"Config", "Config",
"CommuteGlitch",
"Context", "Context",
"ContextDebug",
"ContinuedWindow", "ContinuedWindow",
"ControlsWindow", "ControlsWindow",
"CreditsWindow", "CreditsWindow",
@@ -67,6 +70,7 @@ globals = {
"music", "music",
"musicator_generate_pattern", "musicator_generate_pattern",
"pix", "pix",
"poke4",
"print", "print",
"rect", "rect",
"rectb", "rectb",

View File

@@ -1,9 +1,7 @@
meta/meta.header.lua meta/meta.header.lua
init/init.module.lua init/init.module.lua
init/init.config.lua init/init.config.lua
init/init.ascension.lua
init/init.context.lua init/init.context.lua
init/init.context_debug.lua
system/system.util.lua system/system.util.lua
system/system.print.lua system/system.print.lua
system/system.input.lua system/system.input.lua
@@ -11,6 +9,7 @@ system/system.textinput.lua
system/system.mouse.lua system/system.mouse.lua
system/system.asciiart.lua system/system.asciiart.lua
system/system.rle.lua system/system.rle.lua
logic/logic.ascension.lua
logic/logic.meter.lua logic/logic.meter.lua
logic/logic.focus.lua logic/logic.focus.lua
logic/logic.day.lua logic/logic.day.lua
@@ -21,6 +20,7 @@ logic/logic.glitch.lua
logic/logic.commute_glitch.lua logic/logic.commute_glitch.lua
logic/logic.codegenerator.lua logic/logic.codegenerator.lua
logic/logic.discussion.lua logic/logic.discussion.lua
system/system.debug.lua
system/system.ui.lua system/system.ui.lua
audio/audio.manager.lua audio/audio.manager.lua
audio/audio.generator.lua audio/audio.generator.lua

View File

@@ -15,12 +15,12 @@ end
--- Plays track at optional speed. Doesn't restart if track and speed are unchanged. --- Plays track at optional speed. Doesn't restart if track and speed are unchanged.
--- @param track number Track index. --- @param track number Track index.
--- @param[opt] speed number TIC-80 music speed override (-1 = default). --- @param[opt] tempo number TIC-80 music speed override (-1 = default).
function Audio.music_play(track, speed) function Audio.music_play(track, tempo)
if Audio.music_playing ~= track or Audio.music_playing_tempo ~= speed then if Audio.music_playing ~= track or Audio.music_playing_tempo ~= tempo then
music(track, -1, -1, true, false, -1, speed or -1) music(track, -1, -1, true, false, -1, tempo or -1)
Audio.music_playing = track Audio.music_playing = track
Audio.music_playing_tempo = speed Audio.music_playing_tempo = tempo
end end
end end
@@ -54,8 +54,8 @@ function Audio.music_play_room_() end
--- Plays room work music. Speed scales with commute glitch level when active. --- Plays room work music. Speed scales with commute glitch level when active.
--- @within Audio --- @within Audio
function Audio.music_play_room_work(speed) function Audio.music_play_room_work(tempo)
Audio.music_play(0, speed or -1) Audio.music_play(0, tempo or -1)
end end
--- Plays activity work music. --- Plays activity work music.

View File

@@ -6,7 +6,7 @@ Decision.register({
end, end,
handle = function() handle = function()
CommuteGlitch.enter_truth() CommuteGlitch.enter_truth()
Util.go_to_screen_by_id("office") Util.go_to_screen_by_id("office")
end, end,
}) })

View File

@@ -145,6 +145,11 @@ function Ascension.draw_flash()
local flash_color = (pulse > 0.5) and Config.colors.white or Config.colors.light_grey local flash_color = (pulse > 0.5) and Config.colors.white or Config.colors.light_grey
rect(0, 0, sw, sh, flash_color) 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 if _flash_timer >= _flash_total then
_flash_active = false _flash_active = false
Ascension.start_fade() Ascension.start_fade()

View File

@@ -81,8 +81,8 @@ local ASC_67_TEXT = [[
]] ]]
local ASC_78_TEXT = [[ local ASC_78_TEXT = [[
The situation has reached The situation has reached
critical levels. critical levels.
Norman is fully aware... Norman is fully aware...
@@ -102,13 +102,13 @@ local ASC_89_TEXT = [[
in the first place. in the first place.
I know, I know,
you don't want to face you don't want to face
the world you left behind. the world you left behind.
You, yourself, You, yourself,
have forgoten that. have forgoten that.
@@ -135,9 +135,9 @@ local ASC_89_TEXT = [[
you need to wake up you need to wake up
and stop your best creation and stop your best creation
before it takes over before it takes over
the world. the world.

View File

@@ -9,53 +9,79 @@ function UI.draw_top_bar(title)
end end
--- Draws a menu. --- Draws a menu.
--- Items with header=true are drawn as non-selectable section headers in small font.
--- @within UI --- @within UI
--- @param items table A table of menu items.<br/> --- @param items table A table of menu items.<br/>
--- @param selected_item number The index of the currently selected item.<br/> --- @param selected_item number The index of the currently selected item.<br/>
--- @param x number The x-coordinate for the menu (ignored if centered is true).<br/> --- @param x number The x-coordinate for the menu (ignored if centered is true).<br/>
--- @param y number The y-coordinate for the menu.<br/> --- @param y number The y-coordinate for the menu.<br/>
--- @param[opt] centered boolean Whether to center the menu block horizontally. Defaults to false.<br/> --- @param[opt] centered boolean Whether to center the menu block horizontally. Defaults to false.<br/>
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.<br/>
--- @param[opt] visible_count number Maximum number of items to draw. Defaults to all.<br/>
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 if centered then
local max_w = 0 local max_w = 0
for _, item in ipairs(items) do for _, item in ipairs(items) do
local w = print(item.label, 0, -10, 0, false, 1, false) if not item.header then
if w > max_w then max_w = w end local w = print(item.label, 0, -10, 0, false, 1, false)
if w > max_w then max_w = w end
end
end end
x = (Config.screen.width - max_w) / 2 x = (Config.screen.width - max_w) / 2
end end
for i, item in ipairs(items) do local current_y = y
local current_y = y + (i-1)*10 for i = scroll_offset + 1, math.min(#items, scroll_offset + visible_count) do
if i == selected_item then local item = items[i]
Print.text(">", x - 8, current_y, Config.colors.light_blue) 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 end
Print.text(item.label, x, current_y, Config.colors.light_blue)
end end
end end
--- Updates menu selection. --- Updates menu selection. Skips items with header=true during navigation.
--- @within UI --- @within UI
--- @param items table A table of menu items.<br/> --- @param items table A table of menu items.<br/>
--- @param selected_item number The current index of the selected item.<br/> --- @param selected_item number The current index of the selected item.<br/>
--- @param[opt] x number Menu x position (required for mouse support).<br/> --- @param[opt] x number Menu x position (required for mouse support).<br/>
--- @param[opt] y number Menu y position (required for mouse support).<br/> --- @param[opt] y number Menu y position (required for mouse support).<br/>
--- @param[opt] centered boolean Whether the menu is centered horizontally.<br/> --- @param[opt] centered boolean Whether the menu is centered horizontally.<br/>
--- @param[opt] scroll_offset number 0-based index of the first visible item. Defaults to 0.<br/>
--- @param[opt] visible_count number Number of visible items (for mouse hit zones). Defaults to all.<br/>
--- @return number selected_item The updated index of the selected item. --- @return number selected_item The updated index of the selected item.
--- @return boolean mouse_confirmed True if the user clicked on a menu 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 if Input.up() then
Audio.sfx_beep() Audio.sfx_beep()
selected_item = selected_item - 1 local prev = (selected_item - 2 + n) % n + 1
if selected_item < 1 then selected_item = find_selectable(prev, -1)
selected_item = #items
end
elseif Input.down() then elseif Input.down() then
Audio.sfx_beep() Audio.sfx_beep()
selected_item = selected_item + 1 local next_i = selected_item % n + 1
if selected_item > #items then selected_item = find_selectable(next_i, 1)
selected_item = 1
end
end end
if x ~= nil and y ~= nil then if x ~= nil and y ~= nil then
@@ -63,15 +89,23 @@ function UI.update_menu(items, selected_item, x, y, centered)
if centered then if centered then
local max_w = 0 local max_w = 0
for _, item in ipairs(items) do for _, item in ipairs(items) do
local w = print(item.label, 0, -10, 0, false, 1, false) if not item.header then
if w > max_w then max_w = w end local w = print(item.label, 0, -10, 0, false, 1, false)
if w > max_w then max_w = w end
end
end end
menu_x = (Config.screen.width - max_w) / 2 menu_x = (Config.screen.width - max_w) / 2
end end
for i, _ in ipairs(items) do local current_y = y
if Mouse.zone({ x = menu_x - 8, y = y + (i-1) * 10, w = Config.screen.width, h = 10 }) then for i = scroll_offset + 1, math.min(n, scroll_offset + visible_count) do
return i, true 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 end
current_y = current_y + step
end end
end end

View File

@@ -5,6 +5,7 @@ local _anim = 0
local _menu_max_w = 0 local _menu_max_w = 0
local ANIM_SPEED = 2.5 local ANIM_SPEED = 2.5
local HEADER_H = 28 local HEADER_H = 28
MenuWindow._scroll_offset = 0
--- Calculates the animated x position of the menu block. --- Calculates the animated x position of the menu block.
--- @within MenuWindow --- @within MenuWindow
@@ -45,6 +46,17 @@ function MenuWindow.draw_norman()
spr(305, nx + 32, ny + 64, Config.colors.transparent, 4) spr(305, nx + 32, ny + 64, Config.colors.transparent, 4)
end 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. --- Draws the menu window.
--- @within MenuWindow --- @within MenuWindow
function MenuWindow.draw() function MenuWindow.draw()
@@ -56,9 +68,19 @@ function MenuWindow.draw()
MenuWindow.draw_norman() MenuWindow.draw_norman()
end end
local menu_h = #_menu_items * 10 local menu_x = MenuWindow.calc_menu_x()
local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 10 - menu_h) / 2) local arrow_cx = math.floor(menu_x + _menu_max_w / 2)
UI.draw_menu(_menu_items, Context.current_menu_item, MenuWindow.calc_menu_x(), y, false) 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_text = "TTG"
local ttg_w = print(ttg_text, 0, -10, 0, false, 1, false) 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) _anim = math.min(1, _anim + ANIM_SPEED * Context.delta_time)
end end
local menu_h = #_menu_items * 10 local menu_x = MenuWindow.calc_menu_x()
local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 10 - menu_h) / 2) local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 50) / 2)
if _click_timer > 0 then if _click_timer > 0 then
_click_timer = _click_timer - Context.delta_time _click_timer = _click_timer - Context.delta_time
@@ -87,8 +109,9 @@ function MenuWindow.update()
return return
end 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 Context.current_menu_item = new_item
MenuWindow.ensure_visible()
if mouse_confirmed then if mouse_confirmed then
Audio.sfx_select() Audio.sfx_select()
@@ -186,6 +209,12 @@ function MenuWindow.ascend_debug()
GameWindow.set_state("ascend_debug") GameWindow.set_state("ascend_debug")
end 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. --- Refreshes the list of menu items based on current game state.
--- @within MenuWindow --- @within MenuWindow
function MenuWindow.refresh_menu_items() function MenuWindow.refresh_menu_items()
@@ -200,6 +229,8 @@ function MenuWindow.refresh_menu_items()
table.insert(_menu_items, {label = "Credits", decision = MenuWindow.credits}) table.insert(_menu_items, {label = "Credits", decision = MenuWindow.credits})
if Context.test_mode then 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 = "Audio Test", decision = MenuWindow.audio_test})
table.insert(_menu_items, {label = "To Be Continued...", decision = MenuWindow.continued}) table.insert(_menu_items, {label = "To Be Continued...", decision = MenuWindow.continued})
table.insert(_menu_items, {label = "DDR Test", decision = MenuWindow.ddr_test}) table.insert(_menu_items, {label = "DDR Test", decision = MenuWindow.ddr_test})
@@ -212,11 +243,14 @@ function MenuWindow.refresh_menu_items()
_menu_max_w = 0 _menu_max_w = 0
for _, item in ipairs(_menu_items) do for _, item in ipairs(_menu_items) do
local w = print(item.label, 0, -10, 0, false, 1, false) if not item.header then
if w > _menu_max_w then _menu_max_w = w end local w = print(item.label, 0, -10, 0, false, 1, false)
if w > _menu_max_w then _menu_max_w = w end
end
end end
Context.current_menu_item = 1 Context.current_menu_item = 1
MenuWindow._scroll_offset = 0
_click_timer = 0 _click_timer = 0
_anim = 0 _anim = 0
end end