From 66af47c483e37739fb88f8dd337abb58e213d4c6 Mon Sep 17 00:00:00 2001 From: Zoltan Timar Date: Thu, 26 Feb 2026 15:43:39 +0100 Subject: [PATCH] feat: ring timer drawn at top-left of screen, Meter.set_timer_duration(f) controls speed, Meter.set_timer_decay(a) controls decay amount, all decay pauses during any minigame window --- .luacheckrc | 2 + inc/init/init.config.lua | 2 +- inc/init/init.meter.lua | 46 +++++++++++++++---- inc/system/system.main.lua | 1 + inc/system/system.ui.lua | 65 ++++++++++++++++++++++++--- inc/window/window.configuration.lua | 2 +- inc/window/window.intro.lua | 2 +- inc/window/window.minigame.ddr.lua | 8 ++-- inc/window/window.minigame.mash.lua | 4 +- inc/window/window.minigame.rhythm.lua | 4 +- inc/window/window.popup.lua | 4 +- 11 files changed, 114 insertions(+), 26 deletions(-) diff --git a/.luacheckrc b/.luacheckrc index 370bbd2..277703e 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -39,6 +39,8 @@ globals = { "circb", "cls", "tri", + "pix", + "line", "Songs", "frame_from_beat", "beats_to_pattern", diff --git a/inc/init/init.config.lua b/inc/init/init.config.lua index 34ca206..336714c 100644 --- a/inc/init/init.config.lua +++ b/inc/init/init.config.lua @@ -13,7 +13,7 @@ function Config.initial_data() light_grey = 13, dark_grey = 14, red = 0, - green = 7, + light_blue = 7, blue = 9, white = 12, item = 12, diff --git a/inc/init/init.meter.lua b/inc/init/init.meter.lua index f64111d..3a890a6 100644 --- a/inc/init/init.meter.lua +++ b/inc/init/init.meter.lua @@ -7,12 +7,30 @@ local COMBO_BASE_BONUS = 0.02 local COMBO_MAX_BONUS = 0.16 local COMBO_TIMEOUT_FRAMES = 600 +-- 1800 frames = 30 seconds (1800 รท 60 = 30) +local meter_timer_duration = 1800 +local meter_timer_decay_per_revolution = 20 + -- Internal meters for tracking game progress and player stats. Meter.COLOR_ISM = Config.colors.red Meter.COLOR_WPM = Config.colors.blue Meter.COLOR_BM = Config.colors.black Meter.COLOR_BG = Config.colors.meter_bg +--- Sets the number of frames for one full timer revolution. +--- @within Meter +--- @param frames number Frames per revolution (controls degradation speed). +function Meter.set_timer_duration(frames) + meter_timer_duration = frames +end + +--- Sets the degradation amount applied to all meters per revolution. +--- @within Meter +--- @param amount number Amount to subtract from each meter per revolution. +function Meter.set_timer_decay(amount) + meter_timer_decay_per_revolution = amount +end + --- Gets initial meter values. --- @within Meter --- @return result table A table of initial meter values. @@ -22,6 +40,7 @@ Meter.COLOR_BG = Config.colors.meter_bg --- @return result.combo number Current combo count. --- @return result.combo_timer number Frames since last combo action. --- @return result.hidden boolean Whether meters are hidden. +--- @return result.timer_progress number Clock timer revolution progress (0 to 1). function Meter.get_initial() return { ism = METER_DEFAULT, @@ -30,6 +49,7 @@ function Meter.get_initial() combo = 0, combo_timer = 0, hidden = false, + timer_progress = 0, } end @@ -67,14 +87,24 @@ end function Meter.update() if not Context or not Context.game_in_progress or not Context.meters then return end local m = Context.meters - m.ism = math.max(0, m.ism - METER_DECAY_PER_FRAME) - m.wpm = math.max(0, m.wpm - METER_DECAY_PER_FRAME) - m.bm = math.max(0, m.bm - METER_DECAY_PER_FRAME) - if m.combo > 0 then - m.combo_timer = m.combo_timer + 1 - if m.combo_timer >= COMBO_TIMEOUT_FRAMES then - m.combo = 0 - m.combo_timer = 0 + local in_minigame = string.find(Window.get_current_id(), "^minigame_") ~= nil + if not in_minigame then + m.ism = math.max(0, m.ism - METER_DECAY_PER_FRAME) + m.wpm = math.max(0, m.wpm - METER_DECAY_PER_FRAME) + m.bm = math.max(0, m.bm - METER_DECAY_PER_FRAME) + if m.combo > 0 then + m.combo_timer = m.combo_timer + 1 + if m.combo_timer >= COMBO_TIMEOUT_FRAMES then + m.combo = 0 + m.combo_timer = 0 + end + end + m.timer_progress = m.timer_progress + (1 / meter_timer_duration) + if m.timer_progress >= 1 then + m.timer_progress = m.timer_progress - 1 + m.ism = math.max(0, m.ism - meter_timer_decay_per_revolution) + m.wpm = math.max(0, m.wpm - meter_timer_decay_per_revolution) + m.bm = math.max(0, m.bm - meter_timer_decay_per_revolution) end end end diff --git a/inc/system/system.main.lua b/inc/system/system.main.lua index aa63c1f..df3534a 100644 --- a/inc/system/system.main.lua +++ b/inc/system/system.main.lua @@ -23,5 +23,6 @@ function TIC() Meter.update() if Context.game_in_progress then UI.draw_meters() + UI.draw_timer() end end diff --git a/inc/system/system.ui.lua b/inc/system/system.ui.lua index bd9682b..fea3aaf 100644 --- a/inc/system/system.ui.lua +++ b/inc/system/system.ui.lua @@ -5,7 +5,7 @@ --- @param title string The title text to display. function UI.draw_top_bar(title) rect(0, 0, Config.screen.width, 10, Config.colors.dark_grey) - Print.text(title, 3, 2, Config.colors.green) + Print.text(title, 3, 2, Config.colors.light_blue) end --- Draws dialog window. @@ -24,9 +24,9 @@ function UI.draw_menu(items, selected_item, x, y) 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.green) + Print.text(">", x - 8, current_y, Config.colors.light_blue) end - Print.text(item.label, x, current_y, Config.colors.green) + Print.text(item.label, x, current_y, Config.colors.light_blue) end end @@ -147,12 +147,67 @@ function UI.draw_decision_selector(decisions, selected_decision_index) local text_width = #decision_label * 4 local text_y = bar_y + 4 local text_x = (Config.screen.width - text_width) / 2 - Print.text("<", 2, text_y, Config.colors.green) + Print.text("<", 2, text_y, Config.colors.light_blue) Print.text(decision_label, text_x, text_y, Config.colors.item) - Print.text(">", Config.screen.width - 6, text_y, Config.colors.green) + Print.text(">", Config.screen.width - 6, text_y, Config.colors.light_blue) end end +--- Draws the clock timer indicator as a circular progress bar in the top-left area. +--- Color transitions: white (0-50%), yellow (50-75%), red (75-100%). +--- Only visible when meters are visible. +--- @within UI +function UI.draw_timer() + if not Context or not Context.game_in_progress or not Context.meters then return end + if Context.meters.hidden then return end + + local m = Context.meters + local cx = 10 + local cy = 20 + local r_outer = 5 + local r_inner = 3 + local progress = m.timer_progress + + local fg_color + if progress <= 0.25 then + fg_color = Config.colors.white + elseif progress <= 0.5 then + fg_color = Config.colors.light_blue + elseif progress <= 0.75 then + fg_color = Config.colors.blue + elseif progress <= 1 then + fg_color = Config.colors.red + end + + + local bg_color = Config.colors.dark_grey + local start_angle = -math.pi * 0.5 + local progress_angle = progress * 2 * math.pi + local r_outer_sq = r_outer * r_outer + local r_inner_sq = r_inner * r_inner + + for dy = -r_outer, r_outer do + for dx = -r_outer, r_outer do + local dist_sq = dx * dx + dy * dy + if dist_sq <= r_outer_sq and dist_sq > r_inner_sq then + local angle = math.atan(dy, dx) + local relative = angle - start_angle + if relative < 0 then relative = relative + 2 * math.pi end + if relative <= progress_angle then + pix(cx + dx, cy + dy, fg_color) + else + pix(cx + dx, cy + dy, bg_color) + end + end + end + end + + local hand_angle = start_angle + progress_angle + local hand_x = math.floor(cx + math.cos(hand_angle) * (r_inner - 1) + 0.5) + local hand_y = math.floor(cy + math.sin(hand_angle) * (r_inner - 1) + 0.5) + line(cx, cy, hand_x, hand_y, Config.colors.black) +end + --- Draws meters. --- @within UI function UI.draw_meters() diff --git a/inc/window/window.configuration.lua b/inc/window/window.configuration.lua index 2680429..3a843d8 100644 --- a/inc/window/window.configuration.lua +++ b/inc/window/window.configuration.lua @@ -30,7 +30,7 @@ function ConfigurationWindow.draw() local char_width = 4 for i, control in ipairs(ConfigurationWindow.controls) do local current_y = y_start + (i - 1) * 12 - local color = Config.colors.green + local color = Config.colors.light_blue if control.type == "numeric_stepper" then local value = control.get() local label_text = control.label diff --git a/inc/window/window.intro.lua b/inc/window/window.intro.lua index ff7796e..0364eb8 100644 --- a/inc/window/window.intro.lua +++ b/inc/window/window.intro.lua @@ -18,7 +18,7 @@ on than meets the eye. --- @within IntroWindow function IntroWindow.draw() local x = (Config.screen.width - 132) / 2 - Print.text(IntroWindow.text, x, IntroWindow.y, Config.colors.green) + Print.text(IntroWindow.text, x, IntroWindow.y, Config.colors.light_blue) end --- Updates the intro window logic. diff --git a/inc/window/window.minigame.ddr.lua b/inc/window/window.minigame.ddr.lua index 1d505c2..39d58fb 100644 --- a/inc/window/window.minigame.ddr.lua +++ b/inc/window/window.minigame.ddr.lua @@ -219,7 +219,7 @@ function MinigameDDRWindow.draw() rectb(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.dark_grey) local fill_width = (mg.bar_fill / mg.max_fill) * mg.bar_width if fill_width > 0 then - local bar_color = Config.colors.green + local bar_color = Config.colors.light_blue if mg.bar_fill > 66 then bar_color = Config.colors.item elseif mg.bar_fill > 33 then @@ -232,7 +232,7 @@ function MinigameDDRWindow.draw() 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 - local color = is_pressed and Config.colors.green or Config.colors.light_grey + local color = is_pressed and Config.colors.light_blue or Config.colors.light_grey draw_arrow(target.x, mg.target_y, target.dir, color) end end @@ -252,14 +252,14 @@ function MinigameDDRWindow.draw() "PATTERN MODE - Frame:" .. mg.frame_counter, Config.screen.width / 2, debug_y, - Config.colors.green + Config.colors.light_blue ) if mg.current_song and mg.current_song.pattern then Print.text_center( "Pattern Len:" .. #mg.current_song.pattern .. " Index:" .. mg.pattern_index, Config.screen.width / 2, debug_y + 10, - Config.colors.green + Config.colors.light_blue ) end else diff --git a/inc/window/window.minigame.mash.lua b/inc/window/window.minigame.mash.lua index d2c02b4..e170dc8 100644 --- a/inc/window/window.minigame.mash.lua +++ b/inc/window/window.minigame.mash.lua @@ -68,7 +68,7 @@ function MinigameButtonMashWindow.draw() rectb(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.dark_grey) local fill_width = (mg.bar_fill / mg.max_fill) * mg.bar_width if fill_width > 0 then - local bar_color = Config.colors.green + local bar_color = Config.colors.light_blue if mg.bar_fill > 66 then bar_color = Config.colors.item elseif mg.bar_fill > 33 then @@ -78,7 +78,7 @@ function MinigameButtonMashWindow.draw() end local button_color = Config.colors.light_grey if mg.button_pressed_timer > 0 then - button_color = Config.colors.green + button_color = Config.colors.light_blue end circb(mg.button_x, mg.button_y, mg.button_size, button_color) if mg.button_pressed_timer > 0 then diff --git a/inc/window/window.minigame.rhythm.lua b/inc/window/window.minigame.rhythm.lua index 039e313..c40607e 100644 --- a/inc/window/window.minigame.rhythm.lua +++ b/inc/window/window.minigame.rhythm.lua @@ -87,7 +87,7 @@ 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.green) + rect(target_x, mg.bar_y, target_width_pixels, mg.bar_height, Config.colors.light_blue) 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) local score_text = "SCORE: " .. mg.score .. " / " .. mg.max_score @@ -100,7 +100,7 @@ function MinigameRhythmWindow.draw() ) local button_color = Config.colors.light_grey if mg.button_pressed_timer > 0 then - button_color = Config.colors.green + button_color = Config.colors.light_blue end circb(mg.button_x, mg.button_y, mg.button_size, button_color) if mg.button_pressed_timer > 0 then diff --git a/inc/window/window.popup.lua b/inc/window/window.popup.lua index c9e1515..d330de1 100644 --- a/inc/window/window.popup.lua +++ b/inc/window/window.popup.lua @@ -39,7 +39,7 @@ end function PopupWindow.draw() if Context.popup.show then rect(POPUP_X, POPUP_Y, POPUP_WIDTH, POPUP_HEIGHT, Config.colors.black) - rectb(POPUP_X, POPUP_Y, POPUP_WIDTH, POPUP_HEIGHT, Config.colors.green) + rectb(POPUP_X, POPUP_Y, POPUP_WIDTH, POPUP_HEIGHT, Config.colors.light_blue) local current_y = TEXT_MARGIN_Y for _, line in ipairs(Context.popup.content) do @@ -47,6 +47,6 @@ function PopupWindow.draw() current_y = current_y + LINE_HEIGHT end - Print.text("[A] Close", TEXT_MARGIN_X, POPUP_Y + POPUP_HEIGHT - LINE_HEIGHT - 2, Config.colors.green) + Print.text("[A] Close", TEXT_MARGIN_X, POPUP_Y + POPUP_HEIGHT - LINE_HEIGHT - 2, Config.colors.light_blue) end end -- 2.49.1