feature/imp-42-time-indicator #18
@@ -39,6 +39,8 @@ globals = {
|
|||||||
"circb",
|
"circb",
|
||||||
"cls",
|
"cls",
|
||||||
"tri",
|
"tri",
|
||||||
|
"pix",
|
||||||
|
"line",
|
||||||
"Songs",
|
"Songs",
|
||||||
"frame_from_beat",
|
"frame_from_beat",
|
||||||
"beats_to_pattern",
|
"beats_to_pattern",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function Config.initial_data()
|
|||||||
light_grey = 13,
|
light_grey = 13,
|
||||||
dark_grey = 14,
|
dark_grey = 14,
|
||||||
red = 0,
|
red = 0,
|
||||||
green = 7,
|
light_blue = 7,
|
||||||
blue = 9,
|
blue = 9,
|
||||||
white = 12,
|
white = 12,
|
||||||
item = 12,
|
item = 12,
|
||||||
|
|||||||
@@ -7,12 +7,30 @@ local COMBO_BASE_BONUS = 0.02
|
|||||||
local COMBO_MAX_BONUS = 0.16
|
local COMBO_MAX_BONUS = 0.16
|
||||||
local COMBO_TIMEOUT_FRAMES = 600
|
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.
|
-- Internal meters for tracking game progress and player stats.
|
||||||
Meter.COLOR_ISM = Config.colors.red
|
Meter.COLOR_ISM = Config.colors.red
|
||||||
Meter.COLOR_WPM = Config.colors.blue
|
Meter.COLOR_WPM = Config.colors.blue
|
||||||
Meter.COLOR_BM = Config.colors.black
|
Meter.COLOR_BM = Config.colors.black
|
||||||
Meter.COLOR_BG = Config.colors.meter_bg
|
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.
|
--- Gets initial meter values.
|
||||||
--- @within Meter
|
--- @within Meter
|
||||||
--- @return result table A table of initial meter values.
|
--- @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 number Current combo count.
|
||||||
--- @return result.combo_timer number Frames since last combo action.
|
--- @return result.combo_timer number Frames since last combo action.
|
||||||
--- @return result.hidden boolean Whether meters are hidden.
|
--- @return result.hidden boolean Whether meters are hidden.
|
||||||
|
--- @return result.timer_progress number Clock timer revolution progress (0 to 1).
|
||||||
function Meter.get_initial()
|
function Meter.get_initial()
|
||||||
return {
|
return {
|
||||||
ism = METER_DEFAULT,
|
ism = METER_DEFAULT,
|
||||||
@@ -30,6 +49,7 @@ function Meter.get_initial()
|
|||||||
combo = 0,
|
combo = 0,
|
||||||
combo_timer = 0,
|
combo_timer = 0,
|
||||||
hidden = false,
|
hidden = false,
|
||||||
|
timer_progress = 0,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -67,6 +87,8 @@ end
|
|||||||
function Meter.update()
|
function Meter.update()
|
||||||
if not Context or not Context.game_in_progress or not Context.meters then return end
|
if not Context or not Context.game_in_progress or not Context.meters then return end
|
||||||
local m = Context.meters
|
local m = Context.meters
|
||||||
|
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.ism = math.max(0, m.ism - METER_DECAY_PER_FRAME)
|
||||||
m.wpm = math.max(0, m.wpm - 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)
|
m.bm = math.max(0, m.bm - METER_DECAY_PER_FRAME)
|
||||||
@@ -77,6 +99,14 @@ function Meter.update()
|
|||||||
m.combo_timer = 0
|
m.combo_timer = 0
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
--- Adds amount to a meter.
|
--- Adds amount to a meter.
|
||||||
|
|||||||
@@ -23,5 +23,6 @@ function TIC()
|
|||||||
Meter.update()
|
Meter.update()
|
||||||
if Context.game_in_progress then
|
if Context.game_in_progress then
|
||||||
UI.draw_meters()
|
UI.draw_meters()
|
||||||
|
UI.draw_timer()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
--- @param title string The title text to display.
|
--- @param title string The title text to display.
|
||||||
function UI.draw_top_bar(title)
|
function UI.draw_top_bar(title)
|
||||||
rect(0, 0, Config.screen.width, 10, Config.colors.dark_grey)
|
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
|
end
|
||||||
|
|
||||||
--- Draws dialog window.
|
--- Draws dialog window.
|
||||||
@@ -24,9 +24,9 @@ function UI.draw_menu(items, selected_item, x, y)
|
|||||||
for i, item in ipairs(items) do
|
for i, item in ipairs(items) do
|
||||||
local current_y = y + (i-1)*10
|
local current_y = y + (i-1)*10
|
||||||
if i == selected_item then
|
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
|
end
|
||||||
Print.text(item.label, x, current_y, Config.colors.green)
|
Print.text(item.label, x, current_y, Config.colors.light_blue)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -147,12 +147,67 @@ function UI.draw_decision_selector(decisions, selected_decision_index)
|
|||||||
local text_width = #decision_label * 4
|
local text_width = #decision_label * 4
|
||||||
local text_y = bar_y + 4
|
local text_y = bar_y + 4
|
||||||
local text_x = (Config.screen.width - text_width) / 2
|
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(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
|
||||||
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.
|
--- Draws meters.
|
||||||
--- @within UI
|
--- @within UI
|
||||||
function UI.draw_meters()
|
function UI.draw_meters()
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ function ConfigurationWindow.draw()
|
|||||||
local char_width = 4
|
local char_width = 4
|
||||||
for i, control in ipairs(ConfigurationWindow.controls) do
|
for i, control in ipairs(ConfigurationWindow.controls) do
|
||||||
local current_y = y_start + (i - 1) * 12
|
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
|
if control.type == "numeric_stepper" then
|
||||||
local value = control.get()
|
local value = control.get()
|
||||||
local label_text = control.label
|
local label_text = control.label
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ on than meets the eye.
|
|||||||
--- @within IntroWindow
|
--- @within IntroWindow
|
||||||
function IntroWindow.draw()
|
function IntroWindow.draw()
|
||||||
local x = (Config.screen.width - 132) / 2
|
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
|
end
|
||||||
|
|
||||||
--- Updates the intro window logic.
|
--- Updates the intro window logic.
|
||||||
|
|||||||
@@ -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)
|
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
|
local fill_width = (mg.bar_fill / mg.max_fill) * mg.bar_width
|
||||||
if fill_width > 0 then
|
if fill_width > 0 then
|
||||||
local bar_color = Config.colors.green
|
local bar_color = Config.colors.light_blue
|
||||||
if mg.bar_fill > 66 then
|
if mg.bar_fill > 66 then
|
||||||
bar_color = Config.colors.item
|
bar_color = Config.colors.item
|
||||||
elseif mg.bar_fill > 33 then
|
elseif mg.bar_fill > 33 then
|
||||||
@@ -232,7 +232,7 @@ function MinigameDDRWindow.draw()
|
|||||||
if mg.target_arrows then
|
if mg.target_arrows then
|
||||||
for _, target in ipairs(mg.target_arrows) do
|
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 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)
|
draw_arrow(target.x, mg.target_y, target.dir, color)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -252,14 +252,14 @@ function MinigameDDRWindow.draw()
|
|||||||
"PATTERN MODE - Frame:" .. mg.frame_counter,
|
"PATTERN MODE - Frame:" .. mg.frame_counter,
|
||||||
Config.screen.width / 2,
|
Config.screen.width / 2,
|
||||||
debug_y,
|
debug_y,
|
||||||
Config.colors.green
|
Config.colors.light_blue
|
||||||
)
|
)
|
||||||
if mg.current_song and mg.current_song.pattern then
|
if mg.current_song and mg.current_song.pattern then
|
||||||
Print.text_center(
|
Print.text_center(
|
||||||
"Pattern Len:" .. #mg.current_song.pattern .. " Index:" .. mg.pattern_index,
|
"Pattern Len:" .. #mg.current_song.pattern .. " Index:" .. mg.pattern_index,
|
||||||
Config.screen.width / 2,
|
Config.screen.width / 2,
|
||||||
debug_y + 10,
|
debug_y + 10,
|
||||||
Config.colors.green
|
Config.colors.light_blue
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -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)
|
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
|
local fill_width = (mg.bar_fill / mg.max_fill) * mg.bar_width
|
||||||
if fill_width > 0 then
|
if fill_width > 0 then
|
||||||
local bar_color = Config.colors.green
|
local bar_color = Config.colors.light_blue
|
||||||
if mg.bar_fill > 66 then
|
if mg.bar_fill > 66 then
|
||||||
bar_color = Config.colors.item
|
bar_color = Config.colors.item
|
||||||
elseif mg.bar_fill > 33 then
|
elseif mg.bar_fill > 33 then
|
||||||
@@ -78,7 +78,7 @@ function MinigameButtonMashWindow.draw()
|
|||||||
end
|
end
|
||||||
local button_color = Config.colors.light_grey
|
local button_color = Config.colors.light_grey
|
||||||
if mg.button_pressed_timer > 0 then
|
if mg.button_pressed_timer > 0 then
|
||||||
button_color = Config.colors.green
|
button_color = Config.colors.light_blue
|
||||||
end
|
end
|
||||||
circb(mg.button_x, mg.button_y, mg.button_size, button_color)
|
circb(mg.button_x, mg.button_y, mg.button_size, button_color)
|
||||||
if mg.button_pressed_timer > 0 then
|
if mg.button_pressed_timer > 0 then
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ function MinigameRhythmWindow.draw()
|
|||||||
local target_left = mg.target_center - (mg.target_width / 2)
|
local target_left = mg.target_center - (mg.target_width / 2)
|
||||||
local target_x = mg.bar_x + (target_left * mg.bar_width)
|
local target_x = mg.bar_x + (target_left * mg.bar_width)
|
||||||
local target_width_pixels = mg.target_width * 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)
|
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)
|
rect(line_x - 1, mg.bar_y, 2, mg.bar_height, Config.colors.item)
|
||||||
local score_text = "SCORE: " .. mg.score .. " / " .. mg.max_score
|
local score_text = "SCORE: " .. mg.score .. " / " .. mg.max_score
|
||||||
@@ -100,7 +100,7 @@ function MinigameRhythmWindow.draw()
|
|||||||
)
|
)
|
||||||
local button_color = Config.colors.light_grey
|
local button_color = Config.colors.light_grey
|
||||||
if mg.button_pressed_timer > 0 then
|
if mg.button_pressed_timer > 0 then
|
||||||
button_color = Config.colors.green
|
button_color = Config.colors.light_blue
|
||||||
end
|
end
|
||||||
circb(mg.button_x, mg.button_y, mg.button_size, button_color)
|
circb(mg.button_x, mg.button_y, mg.button_size, button_color)
|
||||||
if mg.button_pressed_timer > 0 then
|
if mg.button_pressed_timer > 0 then
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ end
|
|||||||
function PopupWindow.draw()
|
function PopupWindow.draw()
|
||||||
if Context.popup.show then
|
if Context.popup.show then
|
||||||
rect(POPUP_X, POPUP_Y, POPUP_WIDTH, POPUP_HEIGHT, Config.colors.black)
|
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
|
local current_y = TEXT_MARGIN_Y
|
||||||
for _, line in ipairs(Context.popup.content) do
|
for _, line in ipairs(Context.popup.content) do
|
||||||
@@ -47,6 +47,6 @@ function PopupWindow.draw()
|
|||||||
current_y = current_y + LINE_HEIGHT
|
current_y = current_y + LINE_HEIGHT
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user