All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
257 lines
8.0 KiB
Lua
257 lines
8.0 KiB
Lua
--- @section MenuWindow
|
|
local _menu_items = {}
|
|
local _click_timer = 0
|
|
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
|
|
--- @return number x The left edge x coordinate for the menu.
|
|
function MenuWindow.calc_menu_x()
|
|
local center_start = Config.screen.width / 2
|
|
local center_end = Config.screen.width * 0.72
|
|
local center = center_start + _anim * (center_end - center_start)
|
|
return math.floor(center - _menu_max_w / 2)
|
|
end
|
|
|
|
--- Draws the header with title and separator.
|
|
--- @within MenuWindow
|
|
function MenuWindow.draw_header()
|
|
rect(0, 0, Config.screen.width, HEADER_H, Config.colors.black)
|
|
rect(0, HEADER_H - 2, Config.screen.width, 2, Config.colors.light_blue)
|
|
|
|
local cx = Config.screen.width / 2
|
|
local subtitle = "Definitely not an"
|
|
if Context.test_mode then subtitle = subtitle .. " [TEST]" end
|
|
local sub_w = print(subtitle, 0, -6, 0, false, 1, true)
|
|
print(subtitle, math.floor(cx - sub_w / 2) + 1, 5, Config.colors.dark_grey, false, 1, true)
|
|
print(subtitle, math.floor(cx - sub_w / 2), 4, Config.colors.light_grey, false, 1, true)
|
|
|
|
Print.text_center("IMPOSTOR", cx, 12, Config.colors.item, false, 2)
|
|
end
|
|
|
|
--- Draws the 4x scaled Norman sprite on the left side of the screen.
|
|
--- @within MenuWindow
|
|
function MenuWindow.draw_norman()
|
|
local nx = math.floor(Config.screen.width * 0.45 / 2) - 32
|
|
local ny = HEADER_H + math.floor((Config.screen.height - HEADER_H - 96) / 2)
|
|
spr(272, nx, ny, Config.colors.transparent, 4)
|
|
spr(273, nx + 32, ny, Config.colors.transparent, 4)
|
|
spr(288, nx, ny + 32, Config.colors.transparent, 4)
|
|
spr(289, nx + 32, ny + 32, Config.colors.transparent, 4)
|
|
spr(304, nx, ny + 64, Config.colors.transparent, 4)
|
|
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()
|
|
cls(Config.colors.blue
|
|
)
|
|
MenuWindow.draw_header()
|
|
|
|
if _anim > 0 then
|
|
MenuWindow.draw_norman()
|
|
end
|
|
|
|
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)
|
|
Print.text(ttg_text, Config.screen.width - ttg_w - 5, Config.screen.height - 10, Config.colors.light_blue)
|
|
end
|
|
|
|
--- Updates the menu window logic.
|
|
--- @within MenuWindow
|
|
function MenuWindow.update()
|
|
if _anim < 1 then
|
|
_anim = math.min(1, _anim + ANIM_SPEED * Context.delta_time)
|
|
end
|
|
|
|
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
|
|
if _click_timer <= 0 then
|
|
_click_timer = 0
|
|
local selected_item = _menu_items[Context.current_menu_item]
|
|
if selected_item and selected_item.decision then
|
|
selected_item.decision()
|
|
end
|
|
end
|
|
return
|
|
end
|
|
|
|
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()
|
|
_click_timer = 0.5
|
|
elseif Input.select() then
|
|
local selected_item = _menu_items[Context.current_menu_item]
|
|
if selected_item and selected_item.decision then
|
|
Audio.sfx_select()
|
|
selected_item.decision()
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Opens player name entry then starts a new game.
|
|
--- @within MenuWindow
|
|
function MenuWindow.new_game()
|
|
PlayerNameWindow.init(function()
|
|
Context.new_game()
|
|
end)
|
|
Window.set_current("player_name")
|
|
end
|
|
|
|
--- Saves the current game from the menu.
|
|
--- @within MenuWindow
|
|
function MenuWindow.save_game()
|
|
Context.save_game()
|
|
end
|
|
|
|
--- Resumes the game from the menu.
|
|
--- @within MenuWindow
|
|
function MenuWindow.resume_game()
|
|
GameWindow.set_state("game")
|
|
end
|
|
|
|
--- Exits the game.
|
|
--- @within MenuWindow
|
|
function MenuWindow.exit()
|
|
exit()
|
|
end
|
|
|
|
--- Opens the controls screen.
|
|
--- @within MenuWindow
|
|
function MenuWindow.controls()
|
|
Window.set_current("controls")
|
|
end
|
|
|
|
--- Opens the player name entry screen (test mode shortcut).
|
|
--- @within MenuWindow
|
|
function MenuWindow.player_name()
|
|
PlayerNameWindow.init()
|
|
Window.set_current("player_name")
|
|
end
|
|
|
|
--- Opens the credits screen.
|
|
--- @within MenuWindow
|
|
function MenuWindow.credits()
|
|
CreditsWindow.init()
|
|
Window.set_current("credits")
|
|
end
|
|
|
|
--- Opens the audio test menu.
|
|
--- @within MenuWindow
|
|
function MenuWindow.audio_test()
|
|
AudioTestWindow.init()
|
|
GameWindow.set_state("audiotest")
|
|
end
|
|
|
|
--- Opens the continued screen.
|
|
--- @within MenuWindow
|
|
function MenuWindow.continued()
|
|
ContinuedWindow.timer = 300
|
|
GameWindow.set_state("continued")
|
|
end
|
|
|
|
--- Opens the end screen for testing.
|
|
--- @within MenuWindow
|
|
function MenuWindow.end_screen()
|
|
Context._end.state = "ending"
|
|
Context._end.selection = 1
|
|
GameWindow.set_state("end")
|
|
end
|
|
|
|
--- Opens the DDR minigame test.
|
|
--- @within MenuWindow
|
|
function MenuWindow.ddr_test()
|
|
AudioTestWindow.init()
|
|
GameWindow.set_state("minigame_ddr")
|
|
MinigameDDRWindow.start("menu", "generated", { special_mode = "only_nothing" })
|
|
end
|
|
|
|
--- Opens the ASCEND debug start window.
|
|
--- @within MenuWindow
|
|
function MenuWindow.ascend_debug()
|
|
AscendDebugWindow.init()
|
|
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()
|
|
_menu_items = {}
|
|
if Context.game_in_progress then
|
|
table.insert(_menu_items, {label = "Resume Game", decision = MenuWindow.resume_game})
|
|
table.insert(_menu_items, {label = "Save Game", decision = MenuWindow.save_game})
|
|
end
|
|
|
|
table.insert(_menu_items, {label = "New Game", decision = MenuWindow.new_game})
|
|
table.insert(_menu_items, {label = "Controls", decision = MenuWindow.controls})
|
|
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})
|
|
table.insert(_menu_items, {label = "Start at ASCEND N", decision = MenuWindow.ascend_debug})
|
|
table.insert(_menu_items, {label = "End Screen", decision = MenuWindow.end_screen})
|
|
table.insert(_menu_items, {label = "Player Name", decision = MenuWindow.player_name})
|
|
end
|
|
|
|
table.insert(_menu_items, {label = "Exit", decision = MenuWindow.exit})
|
|
|
|
_menu_max_w = 0
|
|
for _, item in ipairs(_menu_items) do
|
|
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
|