From 9d56ca2e7aeed82c3a2760c926db1a8d2cf7c24d Mon Sep 17 00:00:00 2001 From: Zoltan Timar Date: Thu, 9 Apr 2026 16:36:19 +0200 Subject: [PATCH] chore: re-added have_a_coffee discussion, fixed text readability, fixed centering of texts, new system.ui functions for drawing in contour, fixed ddr types, fixed meter drawing --- impostor.inc | 1 + inc/decision/decision.have_a_coffee.lua | 14 ++ inc/decision/decision.manager.lua | 10 +- inc/decision/decision.play_button_mash.lua | 3 + inc/discussion/discussion.coworker.lua | 2 +- inc/init/init.ascension.lua | 2 +- inc/init/init.config.lua | 1 + inc/init/init.context.lua | 12 +- inc/logic/logic.meter.lua | 26 +-- inc/logic/logic.minigame.lua | 5 +- inc/screen/screen.mysterious_man.lua | 42 ++--- inc/screen/screen.toilet.lua | 20 +-- inc/system/system.print.lua | 38 +++++ inc/system/system.ui.lua | 48 +++++- inc/window/window.discussion.lua | 12 +- inc/window/window.minigame.ddr.lua | 175 ++++++++++++++------- inc/window/window.minigame.mash.lua | 3 +- inc/window/window.minigame.rhythm.lua | 9 +- 18 files changed, 296 insertions(+), 127 deletions(-) create mode 100644 inc/decision/decision.have_a_coffee.lua diff --git a/impostor.inc b/impostor.inc index 8b09814..4f7d075 100644 --- a/impostor.inc +++ b/impostor.inc @@ -48,6 +48,7 @@ decision/decision.go_to_end.lua decision/decision.go_to_walking_to_home.lua decision/decision.go_to_sleep.lua decision/decision.do_work.lua +decision/decision.have_a_coffee.lua decision/decision.sumphore_discussion.lua discussion/discussion.sumphore.lua discussion/discussion.coworker.lua diff --git a/inc/decision/decision.have_a_coffee.lua b/inc/decision/decision.have_a_coffee.lua new file mode 100644 index 0000000..985f3c9 --- /dev/null +++ b/inc/decision/decision.have_a_coffee.lua @@ -0,0 +1,14 @@ +Decision.register({ + id = "have_a_coffee", + label = "Have a Coffee", + handle = function() + local level = Ascension.get_level() + local disc_id = "coworker_disc_0" + -- TODO: Add more discussions for levels above 3 + if level >= 1 and level <= 3 then + local suffix = Context.have_done_work_today and ("_asc_" .. level) or ("_" .. level) + disc_id = "coworker_disc" .. suffix + end + Discussion.start(disc_id, "game") + end, +}) \ No newline at end of file diff --git a/inc/decision/decision.manager.lua b/inc/decision/decision.manager.lua index b0ce2c1..3dcbb86 100644 --- a/inc/decision/decision.manager.lua +++ b/inc/decision/decision.manager.lua @@ -123,9 +123,13 @@ function Decision.draw(decisions, selected_decision_index) local selected_decision = decisions[selected_decision_index] local decision_label = Decision.get_label(selected_decision) local text_y = bar_y + 4 - Print.text("<", 2, text_y, Config.colors.light_blue) - Print.text_center(decision_label, Config.screen.width / 2, text_y, Config.colors.item) - Print.text(">", Config.screen.width - 6, text_y, Config.colors.light_blue) + local left_arrow_color = Input.left() and Config.colors.white or Config.colors.orange + local right_arrow_color = Input.right() and Config.colors.white or Config.colors.orange + local left_arrow_contour_color = Input.left() and Config.colors.white or Config.colors.black + local right_arrow_contour_color = Input.right() and Config.colors.white or Config.colors.black + Print.text_center_contour("<", 6, text_y, left_arrow_color, false, 1, left_arrow_contour_color) + Print.text_center_contour(decision_label, Config.screen.width / 2, text_y, Config.colors.orange) + Print.text_center_contour(">", Config.screen.width - 6, text_y, right_arrow_color, false, 1, right_arrow_contour_color) end end diff --git a/inc/decision/decision.play_button_mash.lua b/inc/decision/decision.play_button_mash.lua index 3ac8995..22d5f9c 100644 --- a/inc/decision/decision.play_button_mash.lua +++ b/inc/decision/decision.play_button_mash.lua @@ -7,6 +7,9 @@ Decision.register({ focus_center_x = (Config.screen.width / 2) - 22, focus_center_y = (Config.screen.height / 2) - 18, focus_initial_radius = 0, + on_win = function() + Audio.music_play_room_work() + end }) end, }) diff --git a/inc/discussion/discussion.coworker.lua b/inc/discussion/discussion.coworker.lua index 61529a8..344e04c 100644 --- a/inc/discussion/discussion.coworker.lua +++ b/inc/discussion/discussion.coworker.lua @@ -40,7 +40,7 @@ Discussion.register({ { question = "Normann you look weird and unfocused. You are usually locked in and not like this, what's up?", answers = { - { label = "Nothing it's just, I noticed some bugs in the simulation, maybe.", next_step = 2 }, + { label = "Some bugs I noticed, maybe...", next_step = 2 }, }, }, { diff --git a/inc/init/init.ascension.lua b/inc/init/init.ascension.lua index 4dae906..e343b90 100644 --- a/inc/init/init.ascension.lua +++ b/inc/init/init.ascension.lua @@ -93,7 +93,7 @@ function Ascension.draw(x, y, options) else color = lit_color end - print(ch, x + (i - 1) * spacing, y, color, false, 1, true) + Print.text_contour(ch, x + (i - 1) * spacing, y, color, false, 1) end end diff --git a/inc/init/init.config.lua b/inc/init/init.config.lua index 626cd7b..f372368 100644 --- a/inc/init/init.config.lua +++ b/inc/init/init.config.lua @@ -17,6 +17,7 @@ function Config.initial_data() blue = 3, white = 4, item = 7, + orange = 7, meter_bg = 1 }, timing = { diff --git a/inc/init/init.context.lua b/inc/init/init.context.lua index 728ab68..6c095ae 100644 --- a/inc/init/init.context.lua +++ b/inc/init/init.context.lua @@ -99,15 +99,15 @@ function Context.new_game() MysteriousManScreen.start({ text = [[ Norman was never a bad - ... + simulation engineer, - ... + but - ... + we need to be careful - ... + letting him improve. - ... + We need to distract him. ]], on_text_complete = function() @@ -122,7 +122,7 @@ function Context.new_game() instruction_text = "Wake up Norman!", show_progress_text = false, on_win = function() - Audio.music_play_wakingup() + Audio.music_play_room_work() Meter.show() Window.set_current("game") end, diff --git a/inc/logic/logic.meter.lua b/inc/logic/logic.meter.lua index 001ddaa..2607582 100644 --- a/inc/logic/logic.meter.lua +++ b/inc/logic/logic.meter.lua @@ -8,10 +8,11 @@ local COMBO_MAX_BONUS = 0.16 local COMBO_TIMEOUT_FRAMES = 600 -- Internal meters for tracking game progress and player stats. -Meter.COLOR_ISM = Config.colors.red +Meter.COLOR_ISM = Config.colors.orange Meter.COLOR_WPM = Config.colors.blue -Meter.COLOR_BM = Config.colors.black +Meter.COLOR_BM = Config.colors.red Meter.COLOR_BG = Config.colors.meter_bg +Meter.COLOR_CONTOUR = Config.colors.white --- Gets initial meter values. --- @within Meter @@ -126,16 +127,16 @@ function Meter.draw() local m = Context.meters local max = Meter.get_max() - local bar_w = 44 + local screen_w = Config.screen.width + local screen_h = Config.screen.height + local bar_w = screen_w * 0.25 local bar_h = 2 - local bar_x = 182 - local label_x = 228 - local line_h = 5 - local start_y = 1 + local edge = math.max(2, math.floor(screen_w * 0.03)) + local bar_x = screen_w - bar_w - edge + local line_h = 3 + local start_y = screen_h * 0.05 - local bar_offset = math.floor((line_h - bar_h) / 2) - local meter_list = { { key = "wpm", label = "WPM", color = Meter.COLOR_WPM, row = 0 }, { key = "ism", label = "ISM", color = Meter.COLOR_ISM, row = 1 }, @@ -144,15 +145,16 @@ function Meter.draw() for _, meter in ipairs(meter_list) do local label_y = start_y + meter.row * line_h - local bar_y = label_y + bar_offset + local bar_y = label_y local fill_w = math.max(0, math.floor((m[meter.key] / max) * bar_w)) + rect(bar_x - 1, bar_y - 1, bar_w + 2, bar_h + 2, Meter.COLOR_CONTOUR) rect(bar_x, bar_y, bar_w, bar_h, Meter.COLOR_BG) if fill_w > 0 then rect(bar_x, bar_y, fill_w, bar_h, meter.color) end - print(meter.label, label_x, label_y, meter.color, false, 1, true) + ---print(meter.label, label_x, label_y, meter.color, false, 1, true) end local ascension_y = start_y + 3 * line_h + 1 - Ascension.draw(bar_x, ascension_y, { spacing = 5 }) + Ascension.draw(bar_x, ascension_y, { spacing = 8 }) end diff --git a/inc/logic/logic.minigame.lua b/inc/logic/logic.minigame.lua index 47a687c..13ccb99 100644 --- a/inc/logic/logic.minigame.lua +++ b/inc/logic/logic.minigame.lua @@ -12,8 +12,9 @@ function Minigame.draw_win_overlay(win_text) local box_h = th + padding * 2 local box_x = (Config.screen.width - box_w) / 2 local box_y = (Config.screen.height - box_h) / 2 - + local text_x = Config.screen.width / 2 rect(box_x, box_y, box_w, box_h, Config.colors.dark_grey) rectb(box_x, box_y, box_w, box_h, Config.colors.white) - Print.text_center(text, Config.screen.width / 2, box_y + padding, Config.colors.white) + Print.text_center_contour(text, text_x, box_y + padding, Config.colors.black, false, 1, Config.colors.white) end + diff --git a/inc/screen/screen.mysterious_man.lua b/inc/screen/screen.mysterious_man.lua index a0b6a05..5a07a4c 100644 --- a/inc/screen/screen.mysterious_man.lua +++ b/inc/screen/screen.mysterious_man.lua @@ -6,52 +6,52 @@ local STATE_CHOICE = "choice" local ASC_01_TEXT = [[ Normann seems to be in line, - ... + and stays seeking for oxes - ... + within the confines. - ... + Very good. ]] local ASC_12_TEXT = [[ We have a problem! - ... + Normann formed his first thought. - ... + He saw the tracks. ]] local ASC_23_TEXT = [[ Not good, not terrible. - ... + Normann caught his glimpse - ... + of another way - ... + - quite literally - - ... + if this continues, - ... + we will lose control. ]] local ASC_34_TEXT = [[ There is no turning back now for Norman. - ... + He caught on. - ... + I hoped it would never come to this... ]] --[[ Norman speaks for the first time during MM screen ]] local ASC_45_TEXT = [[ Wait, who are you? - ... + *silence* - ... + Why am I seeing this? - ... + *silence* - ... + ]] local ascension_texts = { @@ -261,16 +261,18 @@ Screen.register({ end if state == STATE_TEXT then - local cx = Config.screen.width / 2 + local screen_w = Config.screen.width local line_y = text_y for line in (text .. "\n"):gmatch("(.-)\n") do - Print.text_center(line, cx, line_y, Config.colors.light_grey) + local line_w = print(line, 0, -6, 0, false, 1) + local line_x = math.floor((screen_w - line_w) / 2) + Print.text_contour(line, (line_x - 8), line_y, Config.colors.black, false, 1) line_y = line_y + 8 end elseif state == STATE_DAY then MysteriousManScreen.draw_day_switch_background() - local day_text = day_text_override or ("Day " .. Context.day_count) - Print.text_center( + local day_text = day_text_override or ("day " .. Context.day_count) + Print.text_center_contour( day_text, Config.screen.width / 2, Config.screen.height / 2 - 3, diff --git a/inc/screen/screen.toilet.lua b/inc/screen/screen.toilet.lua index 082267d..908787f 100644 --- a/inc/screen/screen.toilet.lua +++ b/inc/screen/screen.toilet.lua @@ -36,13 +36,13 @@ Screen.register({ Sprite.draw_at("norman", norman_x, norman_y) - Print.text_center("day " .. Context.day_count, cx, 10, Config.colors.white) + Print.text_center_contour("day " .. Context.day_count, cx, 10, Config.colors.black) - local narrative = "reflecting on my past and present\n...\nboth eventually flushed." + local narrative = "reflecting on my past and present...\nboth eventually flushed..." local wrapped = UI.word_wrap(narrative, 38) local text_y = 24 for _, line in ipairs(wrapped) do - Print.text_center(line, cx, text_y, Config.colors.light_grey) + Print.text_center_contour(line, cx, text_y, Config.colors.black) text_y = text_y + 8 end @@ -56,26 +56,26 @@ Screen.register({ local meter_start_y = text_y + 10 local meter_list = { - { key = "wpm", label = "Work Productivity Meter" }, - { key = "ism", label = "Impostor Syndrome Meter" }, - { key = "bm", label = "Burnout Meter" }, + { key = "wpm", label = "Work Productivity Meter", color = Meter.COLOR_WPM }, + { key = "ism", label = "Impostor Syndrome Meter", color = Meter.COLOR_ISM }, + { key = "bm", label = "Burnout Meter", color = Meter.COLOR_BM }, } for i, meter in ipairs(meter_list) do local y = meter_start_y + (i - 1) * 20 - Print.text_center(meter.label, cx, y, Config.colors.white) + Print.text_center_contour(meter.label, cx, y, meter.color, false, 1, Config.colors.white) local bar_y = y + 8 local fill_w = math.max(0, math.floor((m[meter.key] / max_val) * bar_w)) rect(bar_x, bar_y, bar_w, bar_h, Meter.COLOR_BG) if fill_w > 0 then - rect(bar_x, bar_y, fill_w, bar_h, Config.colors.blue) + rect(bar_x, bar_y, fill_w, bar_h, meter.color) end local decay_w = print(decay_text, 0, -6, 0, false, 1) - Print.text(decay_text, bar_x - decay_w - 4, bar_y, Config.colors.light_blue) - Print.text(mult_text, bar_x + bar_w + 4, bar_y, Config.colors.light_blue) + Print.text_contour(decay_text, bar_x - decay_w - 4, bar_y, Config.colors.light_blue, false, 1, Config.colors.white) + Print.text_contour(mult_text, bar_x + bar_w + 4, bar_y, Config.colors.light_blue, false, 1, Config.colors.white) end if Ascension.get_level() > 0 then diff --git a/inc/system/system.print.lua b/inc/system/system.print.lua index ffbeba1..fdfdab8 100644 --- a/inc/system/system.print.lua +++ b/inc/system/system.print.lua @@ -14,6 +14,28 @@ function Print.text(text, x, y, color, fixed, scale) print(text, x, y, color, fixed, scale) end +--- Prints text with a contour (outline) instead of shadow. +--- @within Print +--- @param text string The text to print.
+--- @param x number The x-coordinate.
+--- @param y number The y-coordinate.
+--- @param color number The color of the text.
+--- @param[opt] fixed boolean If true, uses fixed-width font.
+--- @param[opt] scale number The scaling factor (also used for outline thickness).
+--- @param[opt] contour_color number Outline color; defaults to black; if equal to text color, uses white.
+function Print.text_contour(text, x, y, color, fixed, scale, contour_color) + scale = scale or 1 + local cc = contour_color + if cc == nil then cc = Config.colors.black end + if color == cc then cc = Config.colors.white end + local ox = { -scale, scale, 0, 0, -scale, scale, -scale, scale } + local oy = { 0, 0, -scale, scale, -scale, -scale, scale, scale } + for i = 1, 8 do + print(text, x + ox[i], y + oy[i], cc, fixed, scale) + end + print(text, x, y, color, fixed, scale) +end + --- Prints centered text with shadow. --- @within Print --- @param text string The text to print.
@@ -28,3 +50,19 @@ function Print.text_center(text, x, y, color, fixed, scale) local centered_x = x - (text_width / 2) Print.text(text, centered_x, y, color, fixed, scale) end + +--- Prints centered text with contour instead of shadow. +--- @within Print +--- @param text string The text to print.
+--- @param x number The x-coordinate for centering.
+--- @param y number The y-coordinate.
+--- @param color number The color of the text.
+--- @param[opt] fixed boolean If true, uses fixed-width font.
+--- @param[opt] scale number The scaling factor.
+--- @param[opt] contour_color number Outline color; defaults to black; if equal to text color, uses white.
+function Print.text_center_contour(text, x, y, color, fixed, scale, contour_color) + scale = scale or 1 + local text_width = print(text, 0, -6 * scale, 0, fixed, scale) + local centered_x = x - (text_width / 2) + Print.text_contour(text, centered_x, y, color, fixed, scale, contour_color) +end diff --git a/inc/system/system.ui.lua b/inc/system/system.ui.lua index 4fb219b..9626b52 100644 --- a/inc/system/system.ui.lua +++ b/inc/system/system.ui.lua @@ -90,12 +90,54 @@ end --- @param[opt] bg_color number The background fill color (default: Config.colors.dark_grey).
--- @param[opt] border_color number The border color (default: Config.colors.white).
--- @param[opt] center_text boolean Whether to center each line inside the box. Defaults to false.
+local function draw_rounded_rect_fill(x, y, w, h, radius, color) + local inner_w = w - radius * 2 + local inner_h = h - radius * 2 + if inner_w > 0 and inner_h > 0 then + rect(x + radius, y + radius, inner_w, inner_h, color) + end + if inner_w > 0 and radius > 0 then + rect(x + radius, y, inner_w, radius, color) + rect(x + radius, y + h - radius, inner_w, radius, color) + end + if radius > 0 and inner_h > 0 then + rect(x, y + radius, radius, inner_h, color) + rect(x + w - radius, y + radius, radius, inner_h, color) + end + for row = 0, radius - 1 do + for col = 0, radius - 1 do + if col + row >= radius - 1 then + pix(x + radius - 1 - col, y + radius - 1 - row, color) + pix(x + w - radius + col, y + radius - 1 - row, color) + pix(x + radius - 1 - col, y + h - radius + row, color) + pix(x + w - radius + col, y + h - radius + row, color) + end + end + end +end + +local function draw_rounded_rect_border(x, y, w, h, radius, color) + line(x + radius, y, x + w - radius - 1, y, color) + line(x + radius, y + h - 1, x + w - radius - 1, y + h - 1, color) + line(x, y + radius, x, y + h - radius - 1, color) + line(x + w - 1, y + radius, x + w - 1, y + h - radius - 1, color) + pix(x + radius - 1, y + 1, color) + pix(x + 1, y + radius - 1, color) + pix(x + w - radius, y + 1, color) + pix(x + w - 2, y + radius - 1, color) + pix(x + radius - 1, y + h - 2, color) + pix(x + 1, y + h - radius, color) + pix(x + w - radius, y + h - 2, color) + pix(x + w - 2, y + h - radius, color) +end + function UI.draw_textbox(text, box_x, box_y, box_w, box_h, scroll_y, color, bg_color, border_color, center_text) color = color or Config.colors.white - bg_color = bg_color or Config.colors.dark_grey + bg_color = bg_color or Config.colors.black border_color = border_color or Config.colors.white center_text = center_text or false + local r = 3 local padding = 4 local line_height = 8 local inner_x = box_x + padding @@ -110,7 +152,7 @@ function UI.draw_textbox(text, box_x, box_y, box_w, box_h, scroll_y, color, bg_c base_y = inner_y + math.floor((visible_height - text_height) / 2) end - rect(box_x, box_y, box_w, box_h, bg_color) + draw_rounded_rect_fill(box_x, box_y, box_w, box_h, r, bg_color) for i, line in ipairs(lines) do local ly = base_y + (i - 1) * line_height - scroll_y @@ -123,7 +165,7 @@ function UI.draw_textbox(text, box_x, box_y, box_w, box_h, scroll_y, color, bg_c end end - rectb(box_x, box_y, box_w, box_h, border_color) + draw_rounded_rect_border(box_x, box_y, box_w, box_h, r, border_color) end --- Wraps text. diff --git a/inc/window/window.discussion.lua b/inc/window/window.discussion.lua index 69dd7ca..e51db29 100644 --- a/inc/window/window.discussion.lua +++ b/inc/window/window.discussion.lua @@ -24,7 +24,7 @@ function DiscussionWindow.draw() TEXTBOX_W, TEXTBOX_H, Context.discussion.scroll_y, Config.colors.white, - Config.colors.dark_grey, + Config.colors.black, Config.colors.light_blue, true ) @@ -37,9 +37,13 @@ function DiscussionWindow.draw() local selected = answers[Context.discussion.selected_answer] local label = selected.label local answer_text_y = bar_y + 4 - Print.text("<", 2, answer_text_y, Config.colors.light_blue) - Print.text_center(label, Config.screen.width / 2, answer_text_y, Config.colors.item) - Print.text(">", Config.screen.width - 6, answer_text_y, Config.colors.light_blue) + local left_arrow_color = Input.left() and Config.colors.white or Config.colors.orange + local right_arrow_color = Input.right() and Config.colors.white or Config.colors.orange + local left_arrow_contour_color = Input.left() and Config.colors.white or Config.colors.black + local right_arrow_contour_color = Input.right() and Config.colors.white or Config.colors.black + Print.text_center_contour("<", 6, answer_text_y, left_arrow_color, false, 1, left_arrow_contour_color) + Print.text_center(label, Config.screen.width / 2, answer_text_y, Config.colors.orange) + Print.text_center_contour(">", Config.screen.width - 6, answer_text_y, right_arrow_color, false, 1, right_arrow_contour_color) end end diff --git a/inc/window/window.minigame.ddr.lua b/inc/window/window.minigame.ddr.lua index 5c92d1e..fdb9cff 100644 --- a/inc/window/window.minigame.ddr.lua +++ b/inc/window/window.minigame.ddr.lua @@ -1,7 +1,44 @@ --- @section MinigameDDRWindow +---@class MinigameDDRState +---@field special_mode string +---@field bar_fill number +---@field max_fill number +---@field fill_per_hit number +---@field miss_penalty number +---@field bar_x number +---@field bar_y number +---@field bar_width number +---@field bar_height number +---@field arrow_size number +---@field arrow_spawn_timer number +---@field arrow_spawn_interval number +---@field arrow_fall_speed number +---@field arrows table +---@field target_y number +---@field target_arrows table +---@field hit_threshold number +---@field button_pressed_timers table +---@field button_press_duration number +---@field input_cooldowns table +---@field input_cooldown_duration number +---@field frame_counter number +---@field current_song table? +---@field pattern_index number +---@field use_pattern boolean +---@field generated_length number +---@field return_window string? +---@field win_timer number +---@field on_win fun(ctx: MinigameDDRState)|nil +---@field total_misses number +---@field total_hits number +---@field special_mode_condition boolean +---@field special_mode_counter number +---@field debug_song_key string|nil +---@field debug_status string|nil + --- Background drawing for DDR minigame. ---- @witin MinigameDDRWindow +--- @within MinigameDDRWindow function MinigameDDRWindow.draw_background() local img_values = {1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1} local img_runs = {809,40,5,26,178,42,4,7,30,10,127,103,11,1,116,124,116,124,115,18,60,47,115,10,105,10,115,9,108,9,114,9,108,9,114,9,108,9,114,9,33,31,44,9,114,9,34,28,46,9,114,9,108,9,114,9,108,9,114,9,108,9,114,9,108,9,114,9,109,8,114,9,109,8,114,9,109,9,112,10,105,1,3,9,111,11,104,2,3,9,111,11,101,5,3,9,111,11,101,5,3,9,111,9,103,5,3,9,111,9,99,1,2,6,3,9,111,9,99,1,2,8,1,9,111,9,3,1,88,1,3,1,2,11,1,9,111,9,3,1,88,1,3,14,1,9,111,9,3,1,88,1,3,1,2,11,1,9,111,9,3,1,88,1,5,12,1,9,111,9,3,1,88,1,3,14,1,9,111,9,3,1,88,3,1,1,2,11,1,9,111,9,3,1,88,3,1,14,1,9,111,9,3,1,88,3,2,13,2,8,111,9,3,1,88,3,3,12,3,8,110,9,3,1,88,3,3,12,3,9,109,9,3,1,88,3,1,14,3,9,109,9,3,1,90,1,1,7,3,4,3,9,109,9,3,1,90,1,1,5,6,3,3,9,108,10,3,1,44,1,4,1,40,1,1,5,7,2,3,9,108,10,3,1,44,1,4,1,38,3,1,5,7,3,2,9,108,10,3,1,48,1,39,3,1,5,7,2,3,9,108,10,3,1,88,3,1,5,6,3,3,9,108,10,3,1,32,5,4,4,3,2,38,3,1,15,2,9,108,10,3,1,41,5,2,2,2,4,32,3,1,15,2,9,108,10,2,2,41,9,2,5,3,1,3,1,23,3,1,9,1,5,2,9,108,10,2,2,41,12,35,3,1,7,4,4,2,9,108,10,2,2,32,26,30,3,1,5,7,3,2,9,108,10,2,2,36,21,31,9,7,3,2,9,108,10,2,2,35,23,30,10,6,3,2,9,108,10,2,2,35,23,30,11,4,4,2,9,108,10,2,2,34,24,30,19,2,9,108,10,2,2,33,25,30,19,2,9,108,10,2,2,32,28,28,19,2,9,108,10,2,2,32,30,26,19,2,9,108,10,2,2,33,31,24,19,2,9,108,10,2,4,37,17,32,19,2,10,107,10,2,5,85,19,2,10,107,10,2,109,2,10,107,12,1,107,3,10,107,133,107,133,107,133,107,133,107,133,107,133,107,133,118,111,129,6,63,3,28,10,121,13,98,6,142,7,76,6,129,10,13,5,77,15,109,4,31,4,78,4,24,7,75,173,66,176,64,177,62,178,62,178,62,56,31,2,7,2,2,3,2,1,2,1,61,8,62,56,114,8,62,56,114,8,62,56,114,8,62,56,114,8,62,9,8,39,114,9,61,8,9,39,114,9,61,8,9,39,114,9,61,8,9,39,114,9,61,8,9,39,114,9,61,8,9,39,114,9,61,8,9,39,115,8,61,8,8,40,115,8,61,56,115,8,61,13,2,1,1,1,1,16,1,1,2,17,115,8,61,13,2,1,1,1,2,1,1,10,2,1,1,1,2,1,2,7,1,6,115,8,60,14,2,1,1,1,5,9,2,1,1,1,2,1,2,6,2,6,115,8,59,15,2,1,1,1,5,9,2,1,1,1,2,1,2,6,2,6,115,8,59,15,2,1,1,1,6,8,2,1,1,1,2,1,2,14,115,8,59,6,1,11,1,1,7,10,1,1,2,1,2,14,115,8,59,6,1,11,1,1,7,10,1,1,2,1,2,1,1,12,115,9,58,6,1,1,2,10,9,8,1,1,2,1,2,1,1,12,115,9,58,6,1,1,2,8,1,1,9,8,1,1,2,1,2,1,1,11,116,9,58,6,1,1,2,1,2,7,9,8,1,1,2,1,2,1,1,11,116,9,58,6,1,1,2,1,2,7,9,10,2,1,2,1,1,11,116,9,58,57,116,9,58,58,115,9,102,13,116,9,58,48,2,10,1,3,22,84,5,7,58,158,7,5,6,6,75,3,7,2,7,1,7,2,6,1,6,2,6,2,4,4,6,2,6,1,6,2,6,3,6,1,6,5,7,8,7,9,11,1,9,2,50,1,22,1,14,5,3,5,3,4,3,6,3,5,2,16,2,5,3,14,2,6,2,6,5,5,11,4,30,1,50,1,37,6,3,5,3,5,2,6,3,5,3,6,1,7,2,6,2,15,2,6,2,5,6,5,12,1,15,1,106,93,2,8,10,5,2,6,2,5,4,4,100,7,3,13,2,65,3,8,3,5,2,5,4,4,3,5,4,4,100,94,2,5,3,5,4,4,6,5,2,5,105,86,2,13,3,5,4,4,6,5,2,1,1,3,102,4,2,24,2,62,1,1,2,7,3,5,4,5,4,5,2,8,4,2,92,5,2,24,2,61,5,5,7,2,5,6,3,4,3,7,102,88,6,7,12,8,6,1,3,7,103,87,6,7,11,9,6,1,3,7,110,1,9,9,5,1,2,1,3,47,162,1,10,6,27,34,162,1,10,6,27,34,97,3,57,14,2,7,26,66,9,33,5,2,17,223,17,223,18,222,47,193,53,1,3,163,19,1,85,109,14,29,36,151,784} @@ -12,7 +49,7 @@ end --- Gets initial DDR minigame configuration. --- @within MinigameDDRWindow ---- @return result table The default DDR minigame configuration. +---@return MinigameDDRState function MinigameDDRWindow.init_context() local arrow_size = 12 local arrow_spacing = 30 @@ -33,7 +70,7 @@ function MinigameDDRWindow.init_context() arrow_spawn_interval = 45, arrow_fall_speed = 1.5, arrows = {}, - target_y = 115, + target_y = 120, target_arrows = { { dir = "left", x = start_x }, { dir = "down", x = start_x + arrow_size + arrow_spacing }, @@ -60,25 +97,29 @@ function MinigameDDRWindow.init_context() } end +--- Builds song data (and optional generated pattern) for the minigame. +--- @within MinigameDDRWindow function MinigameDDRWindow.prepareSong(song, generated_length, special_mode) - local current_song = Util.deepcopy(song) + local current_song = Util.deepcopy(song) + if current_song.generated then + local pattern = musicator_generate_pattern(generated_length, current_song.bpm, current_song.spd * 4) + current_song.pattern = pattern + current_song.end_frame = pattern[#pattern].frame - if current_song.generated then - local pattern = musicator_generate_pattern(generated_length, current_song.bpm, current_song.spd * 4) - current_song.pattern = pattern - current_song.end_frame = pattern[#pattern].frame - - if special_mode == "only_special" then - for i, _ in ipairs(current_song.pattern) do - current_song.pattern[i].special = (i % 5 == 0) - end + if special_mode == "only_special" then + for i, _ in ipairs(current_song.pattern) do + current_song.pattern[i].special = (i % 5 == 0) end end + end - return current_song + return current_song end +--- Handles hit feedback and special-mode scoring for one arrow. +--- @within MinigameDDRWindow +---@param game_context MinigameDDRState function MinigameDDRWindow.on_arrow_hit_special(arrow, game_context) local special_mode = game_context.special_mode @@ -109,13 +150,15 @@ function MinigameDDRWindow.on_arrow_hit_special(arrow, game_context) end end +--- Ends the minigame: win timer, sfx, and special-mode pass/fail bookkeeping. +--- @within MinigameDDRWindow function MinigameDDRWindow.on_end(game_context) Audio.sfx_select() game_context.win_timer = Config.timing.minigame_win_duration local num_special = 0 - for _,v in ipairs(game_context.current_song.pattern) do + for _, v in ipairs(game_context.current_song.pattern) do if game_context.special_mode == "only_left" then num_special = num_special + ((v.dir == "left" and 1) or 0) else @@ -123,12 +166,9 @@ function MinigameDDRWindow.on_end(game_context) end end + local sm = game_context.special_mode local was_ok = true - if game_context.special_mode == "normal" then - was_ok = game_context.special_mode_counter == num_special - elseif game_context.special_mode == "only_special" then - was_ok = game_context.special_mode_counter == num_special - elseif game_context.special_mode == "only_left" then + if sm == "normal" or sm == "only_special" or sm == "only_left" then was_ok = game_context.special_mode_counter == num_special end @@ -192,12 +232,14 @@ end local function spawn_arrow() trace("random arrow") + ---@type MinigameDDRState local mg = Context.minigame_ddr + local y0 = mg.bar_y + mg.bar_height + 10 local target = mg.target_arrows[math.random(1, 4)] table.insert(mg.arrows, { dir = target.dir, x = target.x, - y = mg.bar_y + mg.bar_height + 10 + y = y0 }) end @@ -205,13 +247,15 @@ end --- @within MinigameDDRWindow --- @param direction string The direction of the arrow ("left", "down", "up", "right"). local function spawn_arrow_dir(direction, note, special) + ---@type MinigameDDRState local mg = Context.minigame_ddr + local y0 = mg.bar_y + mg.bar_height + 10 for _, target in ipairs(mg.target_arrows) do if target.dir == direction then table.insert(mg.arrows, { dir = direction, x = target.x, - y = mg.bar_y + mg.bar_height + 10, + y = y0, note = note, special = special }) @@ -225,6 +269,7 @@ end --- @param arrow table The arrow data. --- @return boolean True if the arrow is hit, false otherwise. local function check_hit(arrow) + ---@type MinigameDDRState local mg = Context.minigame_ddr local distance = math.abs(arrow.y - mg.target_y) return distance <= mg.hit_threshold @@ -235,34 +280,53 @@ end --- @param arrow table The arrow data. --- @return boolean True if the arrow is missed, false otherwise. local function check_miss(arrow) + ---@type MinigameDDRState local mg = Context.minigame_ddr return arrow.y > mg.target_y + mg.hit_threshold end ---- Draws an arrow. +--- Rotates a point (px, py) around (cx, cy) by a number of 90-degree CW steps. +--- @within MinigameDDRWindow +local function rotate(px, py, cx, cy, steps) + local dx, dy = px - cx, py - cy + for _ = 1, steps % 4 do + dx, dy = dy, -dx + end + return cx + dx, cy + dy +end + +local arrow_rotations = { down = 0, right = 1, up = 2, left = 3 } + +--- Draws an arrow by rotating the "down" arrow shape. --- @within MinigameDDRWindow --- @param x number The x-coordinate. --- @param y number The y-coordinate. --- @param direction string The direction of the arrow. --- @param color number The color of the arrow. local function draw_arrow(x, y, direction, color) - local size = 12 + local size = 14 local half = size / 2 - if direction == "left" then - tri(x + half, y, x, y + half, x + half, y + size, color) - rectb(x + half, y + half - 2, half, 4, color) - elseif direction == "right" then - tri(x + half, y, x + size, y + half, x + half, y + size, color) - rectb(x, y + half - 2, half, 4, color) - elseif direction == "up" then - tri(x, y + half, x + half, y, x + size, y + half, color) - rectb(x + half - 2, y + half, 4, half, color) - elseif direction == "down" then - tri(x, y + half, x + half, y + size, x + size, y + half, color) - rectb(x + half - 2, y, 4, half, color) - end + local center_x, center_y = x + half, y + half + local steps = arrow_rotations[direction] or 0 + + local head_left_x, head_left_y = rotate(x, y + half, center_x, center_y, steps) + local head_tip_x, head_tip_y = rotate(x + half, y + size, center_x, center_y, steps) + local head_right_x, head_right_y = rotate(x + size, y + half, center_x, center_y, steps) + + tri(head_left_x, head_left_y, + head_tip_x, head_tip_y, + head_right_x, head_right_y, color) + + local stem_top_x, stem_top_y = rotate(x + half - 3, y, center_x, center_y, steps) + local stem_bot_x, stem_bot_y = rotate(x + half + 3, y + half, center_x, center_y, steps) + local stem_x = math.min(stem_top_x, stem_bot_x) + local stem_y = math.min(stem_top_y, stem_bot_y) + local stem_w = math.abs(stem_bot_x - stem_top_x) + local stem_h = math.abs(stem_bot_y - stem_top_y) + rectb(stem_x, stem_y, stem_w, stem_h, color) end + --- Updates DDR minigame logic. --- @within MinigameDDRWindow function MinigameDDRWindow.update() @@ -323,10 +387,7 @@ function MinigameDDRWindow.update() arrow.y = arrow.y + mg.arrow_fall_speed if check_miss(arrow) then table.insert(arrows_to_remove, i) - mg.bar_fill = mg.bar_fill - mg.miss_penalty - if mg.bar_fill < 0 then - mg.bar_fill = 0 - end + mg.bar_fill = math.max(0, mg.bar_fill - mg.miss_penalty) mg.total_misses = mg.total_misses + 1 end end @@ -370,20 +431,14 @@ function MinigameDDRWindow.update() if arrow.dir == dir and check_hit(arrow) then MinigameDDRWindow.on_arrow_hit_special(arrow, mg) - mg.bar_fill = mg.bar_fill + mg.fill_per_hit - if mg.bar_fill > mg.max_fill then - mg.bar_fill = mg.max_fill - end + mg.bar_fill = math.min(mg.max_fill, mg.bar_fill + mg.fill_per_hit) table.remove(mg.arrows, i) hit = true break end end if not hit then - mg.bar_fill = mg.bar_fill - 2 - if mg.bar_fill < 0 then - mg.bar_fill = 0 - end + mg.bar_fill = math.max(0, mg.bar_fill - 2) mg.total_misses = mg.total_misses + 1 end end @@ -393,6 +448,7 @@ end --- Draws DDR minigame. --- @within MinigameDDRWindow function MinigameDDRWindow.draw() + ---@type MinigameDDRState|nil local mg = Context.minigame_ddr if not mg then cls(0) @@ -420,8 +476,6 @@ function MinigameDDRWindow.draw() end rect(mg.bar_x, mg.bar_y, fill_width, mg.bar_height, bar_color) end - local percentage = math.floor((mg.bar_fill / mg.max_fill) * 100) - Print.text_center(percentage .. "%", mg.bar_x + mg.bar_width / 2, mg.bar_y + 2, Config.colors.black) if mg.target_arrows then for _, target in ipairs(mg.target_arrows) do local is_pressed = mg.button_pressed_timers[target.dir] and mg.button_pressed_timers[target.dir] > 0 @@ -435,7 +489,7 @@ function MinigameDDRWindow.draw() draw_arrow(arrow.x, arrow.y, arrow.dir, arrow_color) end end - Print.text_center("Hit the arrows!", Config.screen.width / 2, mg.bar_y + mg.bar_height + 10, Config.colors.light_grey) + Print.text_center_contour("Hit the arrows!", Config.screen.width / 2, mg.bar_y + mg.bar_height + 10, Config.colors.light_blue) local debug_y = 60 if mg.debug_status then Print.text_center(mg.debug_status, Config.screen.width / 2, debug_y, Config.colors.item) @@ -459,13 +513,14 @@ function MinigameDDRWindow.draw() elseif Context.test_mode then Print.text_center("RANDOM MODE", Config.screen.width / 2, debug_y, Config.colors.blue) end -if mg.win_timer > 0 then - if mg.special_mode_condition then - Minigame.draw_win_overlay("SUCCESS...?") - elseif mg.total_hits < 10 then - Minigame.draw_win_overlay("MEH...") - else - Minigame.draw_win_overlay() + + if mg.win_timer > 0 then + if mg.special_mode_condition then + Minigame.draw_win_overlay("SUCCESS...?") + elseif mg.total_hits < 10 then + Minigame.draw_win_overlay("MEH...") + else + Minigame.draw_win_overlay() + end end end -end diff --git a/inc/window/window.minigame.mash.lua b/inc/window/window.minigame.mash.lua index ec52b9b..467eaf1 100644 --- a/inc/window/window.minigame.mash.lua +++ b/inc/window/window.minigame.mash.lua @@ -49,6 +49,7 @@ end --- @param return_window string The window ID to return to after the minigame.
--- @param[opt] params table Optional parameters for minigame configuration.
function MinigameButtonMashWindow.start(return_window, params) + Audio.music_stop() MinigameButtonMashWindow.init(params) local mg = Context.minigame_button_mash mg.return_window = return_window or "game" @@ -145,7 +146,7 @@ function MinigameButtonMashWindow.draw() circ(mg.button_x, mg.button_y, mg.button_size - 2, button_color) end Print.text_center("Z", mg.button_x, mg.button_y - 3, button_color) - Print.text_center(mg.instruction_text, Config.screen.width / 2, mg.bar_y + mg.bar_height + 10, Config.colors.light_grey) + Print.text_center_contour(mg.instruction_text, Config.screen.width / 2, mg.bar_y + mg.bar_height + 10, Config.colors.light_blue) if mg.show_progress_text then local points_text = math.floor(mg.bar_fill) .. "/" .. mg.target_points Print.text_center(points_text, mg.bar_x + mg.bar_width / 2, mg.bar_y + 2, Config.colors.black) diff --git a/inc/window/window.minigame.rhythm.lua b/inc/window/window.minigame.rhythm.lua index 15a893c..d3ba312 100644 --- a/inc/window/window.minigame.rhythm.lua +++ b/inc/window/window.minigame.rhythm.lua @@ -53,6 +53,7 @@ end --- @param return_window string The window ID to return to after the minigame.
--- @param[opt] params table Optional parameters for minigame configuration.
function MinigameRhythmWindow.start(return_window, params) + Audio.music_stop() MinigameRhythmWindow.init(params) local mg = Context.minigame_rhythm mg.return_window = return_window or "game" @@ -146,14 +147,14 @@ function MinigameRhythmWindow.draw() local target_left = mg.target_center - (mg.target_width / 2) local target_x = mg.bar_x + (target_left * mg.bar_width) local target_width_pixels = mg.target_width * mg.bar_width - rect(target_x, mg.bar_y, target_width_pixels, mg.bar_height, Config.colors.light_blue) + rect(target_x, mg.bar_y, target_width_pixels, mg.bar_height, Config.colors.orange) local line_x = mg.bar_x + (mg.line_position * mg.bar_width) - rect(line_x - 1, mg.bar_y, 2, mg.bar_height, Config.colors.item) - Print.text_center( + rect(line_x - 1, mg.bar_y, 2, mg.bar_height, Config.colors.light_blue) + Print.text_center_contour( "Sleep Norman ... Sleep!", Config.screen.width / 2, mg.bar_y + mg.bar_height + 14, - Config.colors.light_grey + Config.colors.light_blue ) local button_color = Config.colors.light_grey if mg.button_pressed_timer > 0 then