feature/imp-30-stat-screen #19

Merged
mr.two merged 2 commits from feature/imp-30-stat-screen into master 2026-02-26 16:06:54 +00:00
6 changed files with 89 additions and 8 deletions

View File

@@ -17,6 +17,7 @@ Context = {}
--- @return result.minigame_button_mash table Button mash minigame state (see Minigame.get_default_button_mash). --- @return result.minigame_button_mash table Button mash minigame state (see Minigame.get_default_button_mash).
--- @return result.minigame_rhythm table Rhythm minigame state (see Minigame.get_default_rhythm). --- @return result.minigame_rhythm table Rhythm minigame state (see Minigame.get_default_rhythm).
--- @return result.meters table Meter values (see Meter.get_initial). --- @return result.meters table Meter values (see Meter.get_initial).
--- @return result.stat_screen_active boolean Whether the stat screen overlay is currently shown.
--- @return result.game table Current game progress state. Contains: `current_screen` (string) active screen ID, `current_situation` (string|nil) active situation ID. --- @return result.game table Current game progress state. Contains: `current_screen` (string) active screen ID, `current_situation` (string|nil) active situation ID.
function Context.initial_data() function Context.initial_data()
return { return {
@@ -27,6 +28,7 @@ function Context.initial_data()
content = {} content = {}
}, },
game_in_progress = false, game_in_progress = false,
stat_screen_active = false,
minigame_ddr = Minigame.get_default_ddr(), minigame_ddr = Minigame.get_default_ddr(),
minigame_button_mash = Minigame.get_default_button_mash(), minigame_button_mash = Minigame.get_default_button_mash(),
minigame_rhythm = Minigame.get_default_rhythm(), minigame_rhythm = Minigame.get_default_rhythm(),

View File

@@ -89,9 +89,6 @@ function Meter.update()
local m = Context.meters local m = Context.meters
local in_minigame = string.find(Window.get_current_id(), "^minigame_") ~= nil local in_minigame = string.find(Window.get_current_id(), "^minigame_") ~= nil
if not in_minigame then 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 if m.combo > 0 then
m.combo_timer = m.combo_timer + 1 m.combo_timer = m.combo_timer + 1
if m.combo_timer >= COMBO_TIMEOUT_FRAMES then if m.combo_timer >= COMBO_TIMEOUT_FRAMES then
@@ -121,6 +118,13 @@ function Meter.add(key, amount)
end end
end end
--- Gets the timer decay as a percentage of the max meter value.
--- @within Meter
--- @return number The decay percentage per revolution (e.g. 2 means -2%).
function Meter.get_timer_decay_percentage()
return math.floor(meter_timer_decay_per_revolution / METER_MAX * 100)
end
--- Called on minigame completion. --- Called on minigame completion.
--- @within Meter --- @within Meter
function Meter.on_minigame_complete() function Meter.on_minigame_complete()

View File

@@ -11,6 +11,7 @@ local _screens = {}
--- @param[opt] screen_data.situations table Array of situation ID strings. Defaults to {}. --- @param[opt] screen_data.situations table Array of situation ID strings. Defaults to {}.
--- @param[opt] screen_data.init function Called when the screen is entered. Defaults to noop. --- @param[opt] screen_data.init function Called when the screen is entered. Defaults to noop.
--- @param[opt] screen_data.update function Called each frame while screen is active. Defaults to noop. --- @param[opt] screen_data.update function Called each frame while screen is active. Defaults to noop.
--- @param[opt] screen_data.draw function Called after the focus overlay to draw screen-specific overlays. Defaults to noop.
function Screen.register(screen_data) function Screen.register(screen_data)
if _screens[screen_data.id] then if _screens[screen_data.id] then
trace("Warning: Overwriting screen with id: " .. screen_data.id) trace("Warning: Overwriting screen with id: " .. screen_data.id)
@@ -24,6 +25,9 @@ function Screen.register(screen_data)
if not screen_data.update then if not screen_data.update then
screen_data.update = function() end screen_data.update = function() end
end end
if not screen_data.draw then
screen_data.draw = function() end
end
_screens[screen_data.id] = screen_data _screens[screen_data.id] = screen_data
end end

View File

@@ -3,5 +3,74 @@ Screen.register({
name = "Toilet", name = "Toilet",
decisions = { decisions = {
"go_to_home", "go_to_home",
} },
background = "bedroom",
init = function()
Context.stat_screen_active = true
Meter.hide()
local cx = Config.screen.width * 0.75
local cy = Config.screen.height * 0.75
Focus.start_driven(cx, cy)
Focus.set_percentage(0.15)
end,
update = function()
if not Context.stat_screen_active then return end
if Input.select() or Input.player_interact() then
Focus.stop()
Context.stat_screen_active = false
Meter.show()
end
end,
draw = function()
if not Context.stat_screen_active then return end
local sw = Config.screen.width
local cx = sw / 2
local bar_w = math.floor(sw * 0.75)
local bar_x = math.floor((sw - bar_w) / 2)
local bar_h = 4
-- TODO: Add day counter
Print.text_center("day 1", cx, 10, Config.colors.white)
local narrative = "reflecting on my past and present\n...\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)
text_y = text_y + 8
end
local m = Context.meters
local max_val = Meter.get_max()
local decay_pct = Meter.get_timer_decay_percentage()
local decay_text = string.format("-%d%%", decay_pct)
local combo_mult = Meter.get_combo_multiplier()
local combo_pct = math.floor((combo_mult - 1) * 100)
local mult_text = string.format("+%d%%", combo_pct)
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" },
}
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)
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)
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)
end
end,
}) })

View File

@@ -155,11 +155,10 @@ end
--- Draws the clock timer indicator as a circular progress bar in the top-left area. --- 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%). --- Color transitions: white (0-50%), yellow (50-75%), red (75-100%).
--- Only visible when meters are visible.
--- @within UI --- @within UI
function UI.draw_timer() function UI.draw_timer()
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
if Context.meters.hidden then return end if Context.meters.hidden and not Context.stat_screen_active then return end
local m = Context.meters local m = Context.meters
local cx = 10 local cx = 10
@@ -205,7 +204,7 @@ function UI.draw_timer()
local hand_angle = start_angle + progress_angle local hand_angle = start_angle + progress_angle
local hand_x = math.floor(cx + math.cos(hand_angle) * (r_inner - 1) + 0.5) 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) 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) line(cx, cy, hand_x, hand_y, Config.colors.white)
end end
--- Draws meters. --- Draws meters.

View File

@@ -12,11 +12,12 @@ function GameWindow.draw()
rect(0, 0, Config.screen.width, Config.screen.height, screen.background_color) rect(0, 0, Config.screen.width, Config.screen.height, screen.background_color)
end end
UI.draw_top_bar(screen.name) UI.draw_top_bar(screen.name)
if #_available_decisions > 0 then if not Context.stat_screen_active and #_available_decisions > 0 then
UI.draw_decision_selector(_available_decisions, _selected_decision_index) UI.draw_decision_selector(_available_decisions, _selected_decision_index)
end end
Sprite.draw() Sprite.draw()
Focus.draw() Focus.draw()
screen.draw()
end end
--- Updates the game window logic. --- Updates the game window logic.
@@ -40,6 +41,8 @@ function GameWindow.update()
end end
end end
if Context.stat_screen_active then return end
-- Fetch and filter decisions locally -- Fetch and filter decisions locally
local all_decisions_for_screen = Decision.get_for_screen(screen) local all_decisions_for_screen = Decision.get_for_screen(screen)
_available_decisions = Decision.filter_available(all_decisions_for_screen) _available_decisions = Decision.filter_available(all_decisions_for_screen)