diff --git a/.luacheckrc b/.luacheckrc index 3d329af..eae275a 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -3,7 +3,7 @@ globals = { "Util", - "DesitionManager", + "DecisionManager", "ScreenManager", "UI", "Print", diff --git a/Makefile b/Makefile index 0b2ea3b..0625245 100644 --- a/Makefile +++ b/Makefile @@ -85,6 +85,7 @@ lint: @touch $(LINT_TMP_LUA) @line=1; \ while IFS= read -r f || [ -n "$$f" ]; do \ + f=$$(printf '%s' "$$f" | tr -d '\r'); \ [ -z "$$f" ] && continue; \ before=$$(wc -l < $(LINT_TMP_LUA)); \ cat "$(SRC_DIR)/$$f" >> $(LINT_TMP_LUA); \ diff --git a/impostor.inc b/impostor.inc index 19e2ee8..9f86ad7 100644 --- a/impostor.inc +++ b/impostor.inc @@ -1,17 +1,19 @@ meta/meta.header.lua init/init.modules.lua init/init.config.lua +init/init.minigames.lua +init/init.meters.lua system/system.util.lua init/init.windows.lua -desition/desition.manager.lua -desition/desition.go_to_home.lua -desition/desition.go_to_toilet.lua -desition/desition.go_to_walking_to_office.lua -desition/desition.go_to_office.lua -desition/desition.go_to_walking_to_home.lua -desition/desition.play_button_mash.lua -desition/desition.play_rhythm.lua -desition/desition.play_ddr.lua +decision/decision.manager.lua +decision/decision.go_to_home.lua +decision/decision.go_to_toilet.lua +decision/decision.go_to_walking_to_office.lua +decision/decision.go_to_office.lua +decision/decision.go_to_walking_to_home.lua +decision/decision.play_button_mash.lua +decision/decision.play_rhythm.lua +decision/decision.play_ddr.lua map/map.manager.lua map/map.bedroom.lua screen/screen.manager.lua diff --git a/inc/desition/desition.go_to_home.lua b/inc/decision/decision.go_to_home.lua similarity index 85% rename from inc/desition/desition.go_to_home.lua rename to inc/decision/decision.go_to_home.lua index 89ed3a4..f906f5d 100644 --- a/inc/desition/desition.go_to_home.lua +++ b/inc/decision/decision.go_to_home.lua @@ -1,4 +1,4 @@ -DesitionManager.register({ +DecisionManager.register({ id = "go_to_home", label = "Go to Home", handle = function() diff --git a/inc/desition/desition.go_to_office.lua b/inc/decision/decision.go_to_office.lua similarity index 85% rename from inc/desition/desition.go_to_office.lua rename to inc/decision/decision.go_to_office.lua index 75b47ac..72118a7 100644 --- a/inc/desition/desition.go_to_office.lua +++ b/inc/decision/decision.go_to_office.lua @@ -1,4 +1,4 @@ -DesitionManager.register({ +DecisionManager.register({ id = "go_to_office", label = "Go to Office", handle = function() diff --git a/inc/desition/desition.go_to_toilet.lua b/inc/decision/decision.go_to_toilet.lua similarity index 85% rename from inc/desition/desition.go_to_toilet.lua rename to inc/decision/decision.go_to_toilet.lua index 7127b94..6525cbf 100644 --- a/inc/desition/desition.go_to_toilet.lua +++ b/inc/decision/decision.go_to_toilet.lua @@ -1,4 +1,4 @@ -DesitionManager.register({ +DecisionManager.register({ id = "go_to_toilet", label = "Go to Toilet", handle = function() diff --git a/inc/desition/desition.go_to_walking_to_home.lua b/inc/decision/decision.go_to_walking_to_home.lua similarity index 87% rename from inc/desition/desition.go_to_walking_to_home.lua rename to inc/decision/decision.go_to_walking_to_home.lua index d98395b..9accc3d 100644 --- a/inc/desition/desition.go_to_walking_to_home.lua +++ b/inc/decision/decision.go_to_walking_to_home.lua @@ -1,4 +1,4 @@ -DesitionManager.register({ +DecisionManager.register({ id = "go_to_walking_to_home", label = "Walking to home", handle = function() diff --git a/inc/desition/desition.go_to_walking_to_office.lua b/inc/decision/decision.go_to_walking_to_office.lua similarity index 87% rename from inc/desition/desition.go_to_walking_to_office.lua rename to inc/decision/decision.go_to_walking_to_office.lua index 8048f68..265401a 100644 --- a/inc/desition/desition.go_to_walking_to_office.lua +++ b/inc/decision/decision.go_to_walking_to_office.lua @@ -1,4 +1,4 @@ -DesitionManager.register({ +DecisionManager.register({ id = "go_to_walking_to_office", label = "Walking to office", handle = function() diff --git a/inc/decision/decision.manager.lua b/inc/decision/decision.manager.lua new file mode 100644 index 0000000..8a660eb --- /dev/null +++ b/inc/decision/decision.manager.lua @@ -0,0 +1,31 @@ +local _decisions = {} + +function DecisionManager.register(decision) + if not decision or not decision.id then + PopupWindow.show({"Error: Invalid decision object registered (missing id)!"}) + return + end + if not decision.label then + PopupWindow.show({"Error: Invalid decision object registered (missing label)!"}) + return + end + + if not decision.condition then + decision.condition = function() return true end + end + if not decision.handle then + decision.handle = function() end + end + if _decisions[decision.id] then + trace("Warning: Overwriting decision with id: " .. decision.id) + end + _decisions[decision.id] = decision +end + +function DecisionManager.get(id) + return _decisions[id] +end + +function DecisionManager.get_all() + return _decisions +end diff --git a/inc/decision/decision.play_button_mash.lua b/inc/decision/decision.play_button_mash.lua new file mode 100644 index 0000000..1d88a44 --- /dev/null +++ b/inc/decision/decision.play_button_mash.lua @@ -0,0 +1,6 @@ +DecisionManager.register({ + id = "play_button_mash", + label = "Play Button Mash", + handle = function() Meters.hide() MinigameButtonMashWindow.start(WINDOW_GAME) end, + condition = function() return true end +}) diff --git a/inc/decision/decision.play_ddr.lua b/inc/decision/decision.play_ddr.lua new file mode 100644 index 0000000..d4d8cf9 --- /dev/null +++ b/inc/decision/decision.play_ddr.lua @@ -0,0 +1,6 @@ +DecisionManager.register({ + id = "play_ddr", + label = "Play DDR (Random)", + handle = function() Meters.hide() MinigameDDRWindow.start(WINDOW_GAME, nil) end, + condition = function() return true end +}) diff --git a/inc/decision/decision.play_rhythm.lua b/inc/decision/decision.play_rhythm.lua new file mode 100644 index 0000000..ccb465e --- /dev/null +++ b/inc/decision/decision.play_rhythm.lua @@ -0,0 +1,6 @@ +DecisionManager.register({ + id = "play_rhythm", + label = "Play Rhythm Game", + handle = function() Meters.hide() MinigameRhythmWindow.start(WINDOW_GAME) end, + condition = function() return true end +}) diff --git a/inc/desition/desition.manager.lua b/inc/desition/desition.manager.lua deleted file mode 100644 index 3d0ea4f..0000000 --- a/inc/desition/desition.manager.lua +++ /dev/null @@ -1,31 +0,0 @@ -local _desitions = {} - -function DesitionManager.register(desition) - if not desition or not desition.id then - PopupWindow.show({"Error: Invalid desition object registered (missing id)!"}) - return - end - if not desition.label then - PopupWindow.show({"Error: Invalid desition object registered (missing label)!"}) - return - end - - if not desition.condition then - desition.condition = function() return true end - end - if not desition.handle then - desition.handle = function() end - end - if _desitions[desition.id] then - trace("Warning: Overwriting desition with id: " .. desition.id) - end - _desitions[desition.id] = desition -end - -function DesitionManager.get(id) - return _desitions[id] -end - -function DesitionManager.get_all() - return _desitions -end diff --git a/inc/desition/desition.play_button_mash.lua b/inc/desition/desition.play_button_mash.lua deleted file mode 100644 index 754e292..0000000 --- a/inc/desition/desition.play_button_mash.lua +++ /dev/null @@ -1,6 +0,0 @@ -DesitionManager.register({ - id = "play_button_mash", - label = "Play Button Mash", - handle = function() MinigameButtonMashWindow.start(WINDOW_GAME) end, - condition = function() return true end -}) diff --git a/inc/desition/desition.play_ddr.lua b/inc/desition/desition.play_ddr.lua deleted file mode 100644 index f03be37..0000000 --- a/inc/desition/desition.play_ddr.lua +++ /dev/null @@ -1,6 +0,0 @@ -DesitionManager.register({ - id = "play_ddr", - label = "Play DDR (Random)", - handle = function() MinigameDDRWindow.start(WINDOW_GAME, nil) end, - condition = function() return true end -}) diff --git a/inc/desition/desition.play_rhythm.lua b/inc/desition/desition.play_rhythm.lua deleted file mode 100644 index 82688ec..0000000 --- a/inc/desition/desition.play_rhythm.lua +++ /dev/null @@ -1,6 +0,0 @@ -DesitionManager.register({ - id = "play_rhythm", - label = "Play Rhythm Game", - handle = function() MinigameRhythmWindow.start(WINDOW_GAME) end, - condition = function() return true end -}) diff --git a/inc/init/init.config.lua b/inc/init/init.config.lua index 73296b0..aee08cd 100644 --- a/inc/init/init.config.lua +++ b/inc/init/init.config.lua @@ -7,8 +7,12 @@ local DEFAULT_CONFIG = { black = 0, light_grey = 13, dark_grey = 14, + red = 2, green = 6, - item = 12 + blue = 9, + white = 12, + item = 12, + meter_bg = 12 }, player = { sprite_id = 1 diff --git a/inc/init/init.context.lua b/inc/init/init.context.lua index 54f4cb6..36aac59 100644 --- a/inc/init/init.context.lua +++ b/inc/init/init.context.lua @@ -39,9 +39,13 @@ on than meets the eye.]] }, menu_items = {}, selected_menu_item = 1, - selected_desition_index = 1, + selected_decision_index = 1, game_in_progress = false, - screens = {} + screens = {}, + minigame_ddr = Minigames.get_default_ddr(), + minigame_button_mash = Minigames.get_default_button_mash(), + minigame_rhythm = Minigames.get_default_rhythm(), + meters = Meters.get_initial() } end @@ -66,7 +70,8 @@ local function reset_context_to_initial_state() local screen_data = ScreenManager.get_by_id(screen_id) if screen_data then table.insert(Context.screens, screen_data) - Context.screen_indices_by_id[screen_id] = i else + Context.screen_indices_by_id[screen_id] = i + else PopupWindow.show({"Error: Screen '" .. screen_id .. "' not registered!"}) end end diff --git a/inc/init/init.meters.lua b/inc/init/init.meters.lua new file mode 100644 index 0000000..14c83de --- /dev/null +++ b/inc/init/init.meters.lua @@ -0,0 +1,75 @@ +local METER_MAX = 1000 +local METER_DEFAULT = 500 +local METER_DECAY_PER_FRAME = 0.02 +local METER_GAIN_PER_CHORE = 100 +local COMBO_BASE_BONUS = 0.02 +local COMBO_MAX_BONUS = 0.16 +local COMBO_TIMEOUT_FRAMES = 600 + +Meters.COLOR_ISM = Config.colors.red +Meters.COLOR_WPM = Config.colors.blue +Meters.COLOR_BM = Config.colors.black +Meters.COLOR_BG = Config.colors.meter_bg + +function Meters.get_initial() + return { + ism = METER_DEFAULT, + wpm = METER_DEFAULT, + bm = METER_DEFAULT, + combo = 0, + combo_timer = 0, + hidden = false, + } +end + +function Meters.hide() + if Context and Context.meters then Context.meters.hidden = true end +end + +function Meters.show() + if Context and Context.meters then Context.meters.hidden = false end +end + +function Meters.get_max() + return METER_MAX +end + +function Meters.get_combo_multiplier() + if not Context or not Context.meters then return 1 end + local combo = Context.meters.combo + if combo == 0 then return 1 end + return 1 + math.min(COMBO_MAX_BONUS, COMBO_BASE_BONUS * (2 ^ (combo - 1))) +end + +function Meters.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 + end + end +end + +function Meters.add(key, amount) + if not Context or not Context.meters then return end + local m = Context.meters + if m[key] ~= nil then + m[key] = math.min(METER_MAX, m[key] + amount) + end +end + +function Meters.on_minigame_complete() + local m = Context.meters + local gain = math.floor(METER_GAIN_PER_CHORE * Meters.get_combo_multiplier()) + Meters.add("wpm", gain) + Meters.add("ism", gain) + Meters.add("bm", gain) + m.combo = m.combo + 1 + m.combo_timer = 0 +end diff --git a/inc/init/init.minigames.lua b/inc/init/init.minigames.lua new file mode 100644 index 0000000..98316d2 --- /dev/null +++ b/inc/init/init.minigames.lua @@ -0,0 +1,107 @@ +Minigames = {} + +local function apply_params(defaults, params) + if not params then return defaults end + for k, v in pairs(params) do + defaults[k] = v + end + return defaults +end + +function Minigames.get_default_ddr() + local arrow_size = 12 + local arrow_spacing = 30 + local total_width = (4 * arrow_size) + (3 * arrow_spacing) + local start_x = (Config.screen.width - total_width) / 2 + return { + bar_fill = 0, + max_fill = 100, + fill_per_hit = 10, + miss_penalty = 5, + bar_x = 20, + bar_y = 10, + bar_width = 200, + bar_height = 12, + arrow_size = arrow_size, + arrow_spawn_timer = 0, + arrow_spawn_interval = 45, + arrow_fall_speed = 1.5, + arrows = {}, + target_y = 115, + target_arrows = { + { dir = "left", x = start_x }, + { dir = "down", x = start_x + arrow_size + arrow_spacing }, + { dir = "up", x = start_x + (arrow_size + arrow_spacing) * 2 }, + { dir = "right", x = start_x + (arrow_size + arrow_spacing) * 3 } + }, + hit_threshold = 8, + button_pressed_timers = {}, + button_press_duration = 8, + input_cooldowns = { left = 0, down = 0, up = 0, right = 0 }, + input_cooldown_duration = 10, + frame_counter = 0, + current_song = nil, + pattern_index = 1, + use_pattern = false, + return_window = nil + } +end + +function Minigames.get_default_button_mash() + return { + bar_fill = 0, + max_fill = 100, + fill_per_press = 8, + base_degradation = 0.15, + degradation_multiplier = 0.006, + button_pressed_timer = 0, + button_press_duration = 8, + return_window = nil, + bar_x = 20, + bar_y = 10, + bar_width = 200, + bar_height = 12, + button_x = 20, + button_y = 110, + button_size = 12 + } +end + +function Minigames.get_default_rhythm() + return { + line_position = 0, + line_speed = 0.015, + line_direction = 1, + target_center = 0.5, + target_width = 0.3, + initial_target_width = 0.3, + min_target_width = 0.08, + target_shrink_rate = 0.9, + score = 0, + max_score = 10, + button_pressed_timer = 0, + button_press_duration = 10, + return_window = nil, + bar_x = 20, + bar_y = 10, + bar_width = 200, + bar_height = 12, + button_x = 210, + button_y = 110, + button_size = 10, + press_cooldown = 0, + press_cooldown_duration = 15 + } +end + +function Minigames.configure_ddr(params) + return apply_params(Minigames.get_default_ddr(), params) +end + +function Minigames.configure_button_mash(params) + return apply_params(Minigames.get_default_button_mash(), params) +end + +function Minigames.configure_rhythm(params) + return apply_params(Minigames.get_default_rhythm(), params) +end diff --git a/inc/init/init.modules.lua b/inc/init/init.modules.lua index 0620467..0adcba2 100644 --- a/inc/init/init.modules.lua +++ b/inc/init/init.modules.lua @@ -9,7 +9,9 @@ local MinigameButtonMashWindow = {} local MinigameRhythmWindow = {} local MinigameDDRWindow = {} Util = {} -DesitionManager = {} +Meters = {} +Minigames = {} +DecisionManager = {} ScreenManager = {} MapManager = {} UI = {} diff --git a/inc/system/system.main.lua b/inc/system/system.main.lua index a30da75..aebb173 100644 --- a/inc/system/system.main.lua +++ b/inc/system/system.main.lua @@ -58,4 +58,8 @@ function TIC() if handler then handler() end + Meters.update() + if Context.game_in_progress then + UI.draw_meters() + end end diff --git a/inc/system/system.ui.lua b/inc/system/system.ui.lua index 3008260..bc28d45 100644 --- a/inc/system/system.ui.lua +++ b/inc/system/system.ui.lua @@ -84,26 +84,58 @@ function UI.create_action_item(label, action) } end -function UI.draw_desition_selector(desitions, selected_desition_index) +function UI.draw_decision_selector(decisions, selected_decision_index) local bar_height = 16 local bar_y = Config.screen.height - bar_height rect(0, bar_y, Config.screen.width, bar_height, Config.colors.dark_grey) - if #desitions > 0 then - local selected_desition = desitions[selected_desition_index] - local desition_label = selected_desition.label - local text_width = #desition_label * 4 local text_y = bar_y + 4 + if #decisions > 0 then + local selected_decision = decisions[selected_decision_index] + local decision_label = selected_decision.label + 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(desition_label, text_x, text_y, Config.colors.item) Print.text(">", Config.screen.width - 6, text_y, Config.colors.green) end + Print.text(decision_label, text_x, text_y, Config.colors.item) Print.text(">", Config.screen.width - 6, text_y, Config.colors.green) end end -function UI.update_desition_selector(desitions, selected_desition_index) +function UI.draw_meters() + 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 max = Meters.get_max() + local bar_w = 44 + local bar_h = 2 + local bar_x = 182 + local label_x = 228 + local line_h = 5 + local start_y = 11 + local bar_offset = math.floor((line_h - bar_h) / 2) + + local meter_list = { + { key = "wpm", label = "WPM", color = Meters.COLOR_WPM, row = 0 }, + { key = "ism", label = "ISM", color = Meters.COLOR_ISM, row = 1 }, + { key = "bm", label = "BM", color = Meters.COLOR_BM, row = 2 }, + } + + for _, meter in ipairs(meter_list) do + local label_y = start_y + meter.row * line_h + local bar_y = label_y + bar_offset + local fill_w = math.max(0, math.floor((m[meter.key] / max) * bar_w)) + rect(bar_x, bar_y, bar_w, bar_h, Meters.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) + end +end + +function UI.update_decision_selector(decisions, selected_decision_index) if Input.left() then Audio.sfx_beep() - selected_desition_index = Util.safeindex(desitions, selected_desition_index - 1) + selected_decision_index = Util.safeindex(decisions, selected_decision_index - 1) elseif Input.right() then Audio.sfx_beep() - selected_desition_index = Util.safeindex(desitions, selected_desition_index + 1) + selected_decision_index = Util.safeindex(decisions, selected_decision_index + 1) end - return selected_desition_index + return selected_decision_index end \ No newline at end of file diff --git a/inc/system/system.util.lua b/inc/system/system.util.lua index cee625a..2c128e3 100644 --- a/inc/system/system.util.lua +++ b/inc/system/system.util.lua @@ -8,7 +8,7 @@ function Util.go_to_screen_by_id(screen_id) local screen_index = Context.screen_indices_by_id[screen_id] if screen_index then Context.current_screen = screen_index - Context.selected_desition_index = 1 else + Context.selected_decision_index = 1 else PopupWindow.show({"Error: Screen '" .. screen_id .. "' not found or not indexed!"}) end end \ No newline at end of file diff --git a/inc/window/window.audiotest.lua b/inc/window/window.audiotest.lua index 8a29b97..2b30a8b 100644 --- a/inc/window/window.audiotest.lua +++ b/inc/window/window.audiotest.lua @@ -10,7 +10,7 @@ function AudioTestWindow.generate_menuitems(list_func, index_func) return { { label = "Play music/sound: " .. (list_func[index_func] or "?"), - desition = function() + decision = function() local current_func = Audio[list_func[index_func]] if current_func then current_func() @@ -21,13 +21,13 @@ function AudioTestWindow.generate_menuitems(list_func, index_func) }, { label = "Stop playing music", - desition = function() + decision = function() Audio.music_stop() end }, { label = "Back", - desition = function() + decision = function() AudioTestWindow.back() end }, @@ -90,7 +90,7 @@ function AudioTestWindow.update() AudioTestWindow.list_func, AudioTestWindow.index_func ) elseif Input.menu_confirm() then - AudioTestWindow.menuitems[AudioTestWindow.index_menu].desition() + AudioTestWindow.menuitems[AudioTestWindow.index_menu].decision() elseif Input.menu_back() then AudioTestWindow.back() end diff --git a/inc/window/window.configuration.lua b/inc/window/window.configuration.lua index b9bc4cf..2147eaf 100644 --- a/inc/window/window.configuration.lua +++ b/inc/window/window.configuration.lua @@ -5,11 +5,11 @@ ConfigurationWindow = { function ConfigurationWindow.init() ConfigurationWindow.controls = { - UI.create_desition_item( + UI.create_decision_item( "Save", function() Config.save() end ), - UI.create_desition_item( + UI.create_decision_item( "Restore Defaults", function() Config.restore_defaults() end ), @@ -40,7 +40,7 @@ function ConfigurationWindow.draw() Print.text(label_text, x_start, current_y, color) Print.text(value_text, value_x, current_y, color) end - elseif control.type == "desition_item" then + elseif control.type == "decision_item" then local label_text = control.label if i == ConfigurationWindow.selected_control then color = Config.colors.item @@ -82,9 +82,9 @@ function ConfigurationWindow.update() elseif btnp(3) then local new_value = math.min(control.max, current_value + control.step) control.set(new_value) end - elseif control.type == "desition_item" then + elseif control.type == "decision_item" then if Input.menu_confirm() then - control.desition() + control.decision() end end end diff --git a/inc/window/window.game.lua b/inc/window/window.game.lua index 815f64a..fbf322f 100644 --- a/inc/window/window.game.lua +++ b/inc/window/window.game.lua @@ -3,15 +3,15 @@ function GameWindow.draw() MapManager.draw(screen.background) UI.draw_top_bar(screen.name) if screen and screen.decisions and #screen.decisions > 0 then - local available_desitions = {} - for _, desition_id in ipairs(screen.decisions) do - local desition = DesitionManager.get(desition_id) - if desition and desition.condition() then - table.insert(available_desitions, desition) + local available_decisions = {} + for _, decision_id in ipairs(screen.decisions) do + local decision = DecisionManager.get(decision_id) + if decision and decision.condition() then + table.insert(available_decisions, decision) end end - if #available_desitions > 0 then - UI.draw_desition_selector(available_desitions, Context.selected_desition_index) + if #available_decisions > 0 then + UI.draw_decision_selector(available_decisions, Context.selected_decision_index) end end end @@ -28,36 +28,36 @@ function GameWindow.update() if Context.current_screen < 1 then Context.current_screen = #Context.screens end - Context.selected_desition_index = 1 elseif Input.down() then + Context.selected_decision_index = 1 elseif Input.down() then Context.current_screen = Context.current_screen + 1 if Context.current_screen > #Context.screens then Context.current_screen = 1 end - Context.selected_desition_index = 1 end + Context.selected_decision_index = 1 end local screen = Context.screens[Context.current_screen] if screen and screen.decisions and #screen.decisions > 0 then - local available_desitions = {} - for _, desition_id in ipairs(screen.decisions) do - local desition = DesitionManager.get(desition_id) - if desition and desition.condition() then table.insert(available_desitions, desition) + local available_decisions = {} + for _, decision_id in ipairs(screen.decisions) do + local decision = DecisionManager.get(decision_id) + if decision and decision.condition() then table.insert(available_decisions, decision) end end - if #available_desitions == 0 then return end + if #available_decisions == 0 then return end - local new_selected_desition_index = UI.update_desition_selector( - available_desitions, - Context.selected_desition_index + local new_selected_decision_index = UI.update_decision_selector( + available_decisions, + Context.selected_decision_index ) - if new_selected_desition_index ~= Context.selected_desition_index then - Context.selected_desition_index = new_selected_desition_index + if new_selected_decision_index ~= Context.selected_decision_index then + Context.selected_decision_index = new_selected_decision_index end if Input.select() then - local selected_desition = available_desitions[Context.selected_desition_index] - if selected_desition and selected_desition.handle then Audio.sfx_select() selected_desition.handle() + local selected_decision = available_decisions[Context.selected_decision_index] + if selected_decision and selected_decision.handle then Audio.sfx_select() selected_decision.handle() end end end diff --git a/inc/window/window.menu.lua b/inc/window/window.menu.lua index 1c7d02b..5b6cfbb 100644 --- a/inc/window/window.menu.lua +++ b/inc/window/window.menu.lua @@ -8,9 +8,9 @@ function MenuWindow.update() if Input.menu_confirm() then local selected_item = Context.menu_items[Context.selected_menu_item] - if selected_item and selected_item.desition then + if selected_item and selected_item.decision then Audio.sfx_select() - selected_item.desition() + selected_item.decision() end end end @@ -47,14 +47,14 @@ end function MenuWindow.refresh_menu_items() Context.menu_items = {} if Context.game_in_progress then - table.insert(Context.menu_items, {label = "Resume Game", desition = MenuWindow.resume_game}) - table.insert(Context.menu_items, {label = "Save Game", desition = MenuWindow.save_game}) + table.insert(Context.menu_items, {label = "Resume Game", decision = MenuWindow.resume_game}) + table.insert(Context.menu_items, {label = "Save Game", decision = MenuWindow.save_game}) end - table.insert(Context.menu_items, {label = "New Game", desition = MenuWindow.new_game}) - table.insert(Context.menu_items, {label = "Load Game", desition = MenuWindow.load_game}) - table.insert(Context.menu_items, {label = "Configuration", desition = MenuWindow.configuration}) - table.insert(Context.menu_items, {label = "Audio Test", desition = MenuWindow.audio_test}) - table.insert(Context.menu_items, {label = "Exit", desition = MenuWindow.exit}) + table.insert(Context.menu_items, {label = "New Game", decision = MenuWindow.new_game}) + table.insert(Context.menu_items, {label = "Load Game", decision = MenuWindow.load_game}) + table.insert(Context.menu_items, {label = "Configuration", decision = MenuWindow.configuration}) + table.insert(Context.menu_items, {label = "Audio Test", decision = MenuWindow.audio_test}) + table.insert(Context.menu_items, {label = "Exit", decision = MenuWindow.exit}) Context.selected_menu_item = 1 end diff --git a/inc/window/window.minigame.ddr.lua b/inc/window/window.minigame.ddr.lua index 77f2ec4..26a5f8d 100644 --- a/inc/window/window.minigame.ddr.lua +++ b/inc/window/window.minigame.ddr.lua @@ -1,67 +1,17 @@ -function MinigameDDRWindow.init() - -- Calculate evenly spaced arrow positions - local arrow_size = 12 - local arrow_spacing = 30 - local total_width = (4 * arrow_size) + (3 * arrow_spacing) - local start_x = (Config.screen.width - total_width) / 2 - Context.minigame_ddr = { - -- Progress bar (matching button mash style) - bar_fill = 0, -- 0 to 100 - max_fill = 100, - fill_per_hit = 10, -- Points gained per perfect hit - miss_penalty = 5, -- Points lost per miss - bar_x = 20, - bar_y = 10, - bar_width = 200, - bar_height = 12, - -- Arrow settings - arrow_size = arrow_size, - arrow_spawn_timer = 0, - arrow_spawn_interval = 45, -- Frames between arrow spawns (for random mode) - arrow_fall_speed = 1.5, -- Pixels per frame - arrows = {}, -- Active falling arrows {dir, x, y} - -- Target arrows at bottom (evenly spaced, centered on screen) - target_y = 115, -- Y position of target arrows - target_arrows = { - {dir = "left", x = start_x}, - {dir = "down", x = start_x + arrow_size + arrow_spacing}, - {dir = "up", x = start_x + (arrow_size + arrow_spacing) * 2}, - {dir = "right", x = start_x + (arrow_size + arrow_spacing) * 3} - }, - -- Hit detection - hit_threshold = 8, -- Pixels of tolerance for perfect hit - button_pressed_timers = {}, -- Visual feedback per arrow - button_press_duration = 8, - -- Input cooldown per direction - input_cooldowns = { - left = 0, - down = 0, - up = 0, - right = 0 - }, - input_cooldown_duration = 10, - -- Song/Pattern system - frame_counter = 0, -- Tracks frames since start - current_song = nil, -- Current song data - pattern_index = 1, -- Current position in pattern - use_pattern = false, -- If true, use song pattern; if false, use random spawning - return_window = WINDOW_GAME - } +function MinigameDDRWindow.init(params) + Context.minigame_ddr = Minigames.configure_ddr(params) end -function MinigameDDRWindow.start(return_window, song_key) - MinigameDDRWindow.init() +function MinigameDDRWindow.start(return_window, song_key, params) + MinigameDDRWindow.init(params) Context.minigame_ddr.return_window = return_window or WINDOW_GAME - -- Debug: Store song_key for display Context.minigame_ddr.debug_song_key = song_key - -- Load song pattern if specified if song_key and Songs and Songs[song_key] then Context.minigame_ddr.current_song = Songs[song_key] Context.minigame_ddr.use_pattern = true Context.minigame_ddr.pattern_index = 1 Context.minigame_ddr.debug_status = "Pattern loaded: " .. song_key else - -- Default to random spawning Context.minigame_ddr.use_pattern = false if song_key then Context.minigame_ddr.debug_status = "Song not found: " .. tostring(song_key) @@ -72,21 +22,18 @@ function MinigameDDRWindow.start(return_window, song_key) Context.active_window = WINDOW_MINIGAME_DDR end --- Spawn a new arrow (random direction) local function spawn_arrow() local mg = Context.minigame_ddr 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 -- Start below progress bar + y = mg.bar_y + mg.bar_height + 10 }) end --- Spawn an arrow with specific direction local function spawn_arrow_dir(direction) local mg = Context.minigame_ddr - -- Find the target arrow for this direction for _, target in ipairs(mg.target_arrows) do if target.dir == direction then table.insert(mg.arrows, { @@ -99,38 +46,30 @@ local function spawn_arrow_dir(direction) end end --- Check if arrow is close enough for a hit local function check_hit(arrow) local mg = Context.minigame_ddr local distance = math.abs(arrow.y - mg.target_y) return distance <= mg.hit_threshold end --- Check if arrow has passed the target local function check_miss(arrow) local mg = Context.minigame_ddr return arrow.y > mg.target_y + mg.hit_threshold end --- Draw a single arrow sprite local function draw_arrow(x, y, direction, color) local size = 12 local half = size / 2 - -- Draw arrow shape based on direction if direction == "left" then - -- Triangle pointing left tri(x + half, y, x, y + half, x + half, y + size, color) rect(x + half, y + half - 2, half, 4, color) elseif direction == "right" then - -- Triangle pointing right tri(x + half, y, x + size, y + half, x + half, y + size, color) rect(x, y + half - 2, half, 4, color) elseif direction == "up" then - -- Triangle pointing up tri(x, y + half, x + half, y, x + size, y + half, color) rect(x + half - 2, y + half, 4, half, color) elseif direction == "down" then - -- Triangle pointing down tri(x, y + half, x + half, y + size, x + size, y + half, color) rect(x + half - 2, y, 4, half, color) end @@ -138,77 +77,64 @@ end function MinigameDDRWindow.update() local mg = Context.minigame_ddr - -- Check for completion (bar filled to 100%) if mg.bar_fill >= mg.max_fill then + Meters.on_minigame_complete() + Meters.show() Context.active_window = mg.return_window return end - -- Increment frame counter mg.frame_counter = mg.frame_counter + 1 - -- Check if song has ended (pattern mode only) if mg.use_pattern and mg.current_song and mg.current_song.end_frame then - -- Song has ended if we've passed the end frame AND all arrows are cleared if mg.frame_counter > mg.current_song.end_frame and #mg.arrows == 0 then - -- Song complete! Return to previous window + Meters.on_minigame_complete() + Meters.show() Context.active_window = mg.return_window return end end - -- Spawn arrows based on mode (pattern or random) if mg.use_pattern and mg.current_song and mg.current_song.pattern then - -- Pattern-based spawning (synced to song) local pattern = mg.current_song.pattern - -- Check if current frame matches any pattern entry while mg.pattern_index <= #pattern do local spawn_entry = pattern[mg.pattern_index] if mg.frame_counter >= spawn_entry.frame then - -- Time to spawn this arrow! spawn_arrow_dir(spawn_entry.dir) mg.pattern_index = mg.pattern_index + 1 else - -- Not time yet, break the loop break end end else - -- Random spawning mode (original behavior) mg.arrow_spawn_timer = mg.arrow_spawn_timer + 1 if mg.arrow_spawn_timer >= mg.arrow_spawn_interval then spawn_arrow() mg.arrow_spawn_timer = 0 end end - -- Update falling arrows local arrows_to_remove = {} for i, arrow in ipairs(mg.arrows) do arrow.y = arrow.y + mg.arrow_fall_speed - -- Check if arrow went off-screen (miss) if check_miss(arrow) then table.insert(arrows_to_remove, i) - -- Penalty for missing mg.bar_fill = mg.bar_fill - mg.miss_penalty if mg.bar_fill < 0 then mg.bar_fill = 0 end end end - -- Remove off-screen arrows (iterate backwards to avoid index issues) + -- iterate backwards to avoid index shift issues for i = #arrows_to_remove, 1, -1 do table.remove(mg.arrows, arrows_to_remove[i]) end - -- Update input cooldowns for dir, _ in pairs(mg.input_cooldowns) do if mg.input_cooldowns[dir] > 0 then mg.input_cooldowns[dir] = mg.input_cooldowns[dir] - 1 end end - -- Update button press timers for dir, _ in pairs(mg.button_pressed_timers) do if mg.button_pressed_timers[dir] > 0 then mg.button_pressed_timers[dir] = mg.button_pressed_timers[dir] - 1 end end - -- Check for arrow key inputs local input_map = { left = Input.left(), down = Input.down(), @@ -219,11 +145,9 @@ function MinigameDDRWindow.update() if pressed and mg.input_cooldowns[dir] == 0 then mg.input_cooldowns[dir] = mg.input_cooldown_duration mg.button_pressed_timers[dir] = mg.button_press_duration - -- Check if any arrow matches this direction and is in hit range local hit = false for i, arrow in ipairs(mg.arrows) do if arrow.dir == dir and check_hit(arrow) then - -- Perfect hit! mg.bar_fill = mg.bar_fill + mg.fill_per_hit if mg.bar_fill > mg.max_fill then mg.bar_fill = mg.max_fill @@ -233,7 +157,6 @@ function MinigameDDRWindow.update() break end end - -- If pressed but no arrow to hit, apply small penalty if not hit then mg.bar_fill = mg.bar_fill - 2 if mg.bar_fill < 0 then @@ -246,7 +169,6 @@ end function MinigameDDRWindow.draw() local mg = Context.minigame_ddr - -- Safety check if not mg then cls(0) print("DDR ERROR: Context not initialized", 10, 10, 12) @@ -256,31 +178,24 @@ function MinigameDDRWindow.draw() end return end - -- Draw the underlying window first (for overlay effect) if mg.return_window == WINDOW_GAME then GameWindow.draw() end - -- Draw semi-transparent overlay background rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black) - -- Draw progress bar background rect(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.light_grey) rectb(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.dark_grey) - -- Draw progress bar fill local fill_width = (mg.bar_fill / mg.max_fill) * mg.bar_width if fill_width > 0 then - -- Color changes as bar fills local bar_color = Config.colors.green if mg.bar_fill > 66 then - bar_color = Config.colors.item -- yellow + bar_color = Config.colors.item elseif mg.bar_fill > 33 then - bar_color = Config.colors.bar + bar_color = Config.colors.blue end rect(mg.bar_x, mg.bar_y, fill_width, mg.bar_height, bar_color) end - -- Draw progress percentage 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) - -- Draw target arrows at bottom (light grey when not pressed) 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 @@ -288,15 +203,12 @@ function MinigameDDRWindow.draw() draw_arrow(target.x, mg.target_y, target.dir, color) end end - -- Draw falling arrows (blue) if mg.arrows then for _, arrow in ipairs(mg.arrows) do - draw_arrow(arrow.x, arrow.y, arrow.dir, Config.colors.bar) -- blue color + draw_arrow(arrow.x, arrow.y, arrow.dir, Config.colors.blue) end end - -- Draw instruction text Print.text_center("Hit the arrows!", Config.screen.width / 2, mg.bar_y + mg.bar_height + 10, Config.colors.light_grey) - -- Debug info (large and visible) local debug_y = 60 if mg.debug_status then Print.text_center(mg.debug_status, Config.screen.width / 2, debug_y, Config.colors.item) @@ -318,6 +230,6 @@ function MinigameDDRWindow.draw() ) end else - Print.text_center("RANDOM MODE", Config.screen.width / 2, debug_y, Config.colors.bar) + Print.text_center("RANDOM MODE", Config.screen.width / 2, debug_y, Config.colors.blue) end -end \ No newline at end of file +end diff --git a/inc/window/window.minigame.mash.lua b/inc/window/window.minigame.mash.lua index a2ddc6c..30d3f41 100644 --- a/inc/window/window.minigame.mash.lua +++ b/inc/window/window.minigame.mash.lua @@ -1,53 +1,33 @@ -function MinigameButtonMashWindow.init() - Context.minigame_button_mash = { - bar_fill = 0, -- 0 to 100 - max_fill = 100, - fill_per_press = 8, - base_degradation = 0.15, -- Base degradation per frame - degradation_multiplier = 0.006, -- Increases with bar fill - button_pressed_timer = 0, -- Visual feedback timer - button_press_duration = 8, -- Frames to show button press - return_window = WINDOW_GAME, -- Window to return to after completion - bar_x = 20, - bar_y = 10, - bar_width = 200, - bar_height = 12, - button_x = 20, - button_y = 110, - button_size = 12 - } +function MinigameButtonMashWindow.init(params) + Context.minigame_button_mash = Minigames.configure_button_mash(params) end -function MinigameButtonMashWindow.start(return_window) - MinigameButtonMashWindow.init() +function MinigameButtonMashWindow.start(return_window, params) + MinigameButtonMashWindow.init(params) Context.minigame_button_mash.return_window = return_window or WINDOW_GAME Context.active_window = WINDOW_MINIGAME_BUTTON_MASH end function MinigameButtonMashWindow.update() local mg = Context.minigame_button_mash - -- Check for Z button press if Input.select() then mg.bar_fill = mg.bar_fill + mg.fill_per_press mg.button_pressed_timer = mg.button_press_duration - -- Clamp to max if mg.bar_fill > mg.max_fill then mg.bar_fill = mg.max_fill end end - -- Check if bar is full (completed) if mg.bar_fill >= mg.max_fill then + Meters.on_minigame_complete() + Meters.show() Context.active_window = mg.return_window return end - -- Automatic degradation (increases with bar fill level) local degradation = mg.base_degradation + (mg.bar_fill * mg.degradation_multiplier) mg.bar_fill = mg.bar_fill - degradation - -- Clamp to minimum if mg.bar_fill < 0 then mg.bar_fill = 0 end - -- Update button press timer if mg.button_pressed_timer > 0 then mg.button_pressed_timer = mg.button_pressed_timer - 1 end @@ -55,42 +35,32 @@ end function MinigameButtonMashWindow.draw() local mg = Context.minigame_button_mash - -- Draw the underlying window first (for overlay effect) if mg.return_window == WINDOW_GAME then GameWindow.draw() end - -- Draw semi-transparent overlay background - -- Draw darker rectangles to create overlay effect rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black) - -- Draw progress bar background rect(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.light_grey) rectb(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.dark_grey) - -- Draw progress bar fill local fill_width = (mg.bar_fill / mg.max_fill) * mg.bar_width if fill_width > 0 then - -- Color changes as bar fills (green -> yellow -> red analogy using available colors) local bar_color = Config.colors.green if mg.bar_fill > 66 then - bar_color = Config.colors.item -- yellow + bar_color = Config.colors.item elseif mg.bar_fill > 33 then - bar_color = Config.colors.bar -- medium color + bar_color = Config.colors.blue end - rect(mg.bar_x, mg.bar_y, fill_width, bar_color) + rect(mg.bar_x, mg.bar_y, fill_width, mg.bar_height, bar_color) end - -- Draw button indicator local button_color = Config.colors.light_grey if mg.button_pressed_timer > 0 then - button_color = Config.colors.green -- Highlight when pressed + button_color = Config.colors.green end circb(mg.button_x, mg.button_y, mg.button_size, button_color) if mg.button_pressed_timer > 0 then circ(mg.button_x, mg.button_y, mg.button_size - 2, button_color) end - -- Draw Z text in the button Print.text_center(" Z", mg.button_x - 2, mg.button_y - 3, Config.colors.light_grey) - -- Draw instruction text Print.text_center("MASH Z!", Config.screen.width / 2, mg.bar_y + mg.bar_height + 10, Config.colors.light_grey) - -- Draw progress percentage 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) -end \ No newline at end of file +end diff --git a/inc/window/window.minigame.rhythm.lua b/inc/window/window.minigame.rhythm.lua index d24ad56..9a4d186 100644 --- a/inc/window/window.minigame.rhythm.lua +++ b/inc/window/window.minigame.rhythm.lua @@ -1,44 +1,16 @@ -function MinigameRhythmWindow.init() - Context.minigame_rhythm = { - line_position = 0, -- Normalized position (0 to 1) - line_speed = 0.015, -- Movement speed per frame - line_direction = 1, -- 1 for left-to-right, -1 for right-to-left - target_center = 0.5, -- Center of target area (middle of bar) - target_width = 0.3, -- Width of target area (normalized) - initial_target_width = 0.3, - min_target_width = 0.08, -- Minimum width to keep game possible - target_shrink_rate = 0.9, -- Multiplier per successful hit (0.9 = 10% shrink) - score = 0, - max_score = 10, - button_pressed_timer = 0, - button_press_duration = 10, - return_window = WINDOW_GAME, - -- Visual layout (match button mash minigame dimensions) - bar_x = 20, - bar_y = 10, - bar_width = 200, - bar_height = 12, - -- Button indicator - button_x = 210, - button_y = 110, - button_size = 10, - -- Cooldown to prevent multiple presses in one frame - press_cooldown = 0, - press_cooldown_duration = 15 - } +function MinigameRhythmWindow.init(params) + Context.minigame_rhythm = Minigames.configure_rhythm(params) end -function MinigameRhythmWindow.start(return_window) - MinigameRhythmWindow.init() +function MinigameRhythmWindow.start(return_window, params) + MinigameRhythmWindow.init(params) Context.minigame_rhythm.return_window = return_window or WINDOW_GAME Context.active_window = WINDOW_MINIGAME_RHYTHM end function MinigameRhythmWindow.update() local mg = Context.minigame_rhythm - -- Move the line across the bar (bidirectional) mg.line_position = mg.line_position + (mg.line_speed * mg.line_direction) - -- Reverse direction when reaching either end if mg.line_position > 1 then mg.line_position = 1 mg.line_direction = -1 @@ -46,40 +18,33 @@ function MinigameRhythmWindow.update() mg.line_position = 0 mg.line_direction = 1 end - -- Decrease cooldown timer if mg.press_cooldown > 0 then mg.press_cooldown = mg.press_cooldown - 1 end - -- Check for Z button press (only if cooldown expired) if Input.select() and mg.press_cooldown == 0 then mg.button_pressed_timer = mg.button_press_duration mg.press_cooldown = mg.press_cooldown_duration - -- Calculate if line is within target area local target_left = mg.target_center - (mg.target_width / 2) local target_right = mg.target_center + (mg.target_width / 2) if mg.line_position >= target_left and mg.line_position <= target_right then - -- HIT! Award point mg.score = mg.score + 1 else - -- MISS! Deduct point (but not below 0) mg.score = mg.score - 1 if mg.score < 0 then mg.score = 0 end end - -- Calculate target width dynamically based on score - -- Each point shrinks by 10%, so reverse the formula mg.target_width = mg.initial_target_width * (mg.target_shrink_rate ^ mg.score) if mg.target_width < mg.min_target_width then mg.target_width = mg.min_target_width end end - -- Check win condition if mg.score >= mg.max_score then + Meters.on_minigame_complete() + Meters.show() Context.active_window = mg.return_window return end - -- Update button press timer if mg.button_pressed_timer > 0 then mg.button_pressed_timer = mg.button_pressed_timer - 1 end @@ -87,46 +52,34 @@ end function MinigameRhythmWindow.draw() local mg = Context.minigame_rhythm - -- Draw the underlying window first (for overlay effect) if mg.return_window == WINDOW_GAME then GameWindow.draw() end - -- Draw semi-transparent overlay background rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black) - -- Calculate actual pixel positions - -- Draw bar container background rect(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.light_grey) rectb(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.dark_grey) - -- Draw bar background (empty area) rect(mg.bar_x, mg.bar_y, mg.bar_width, mg.bar_height, Config.colors.dark_grey) - -- Draw target area (highlighted section in middle) 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) - -- Draw the moving line 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) -- Yellow line - -- Draw score text + rect(line_x - 1, mg.bar_y, 2, mg.bar_height, Config.colors.item) local score_text = "SCORE: " .. mg.score .. " / " .. mg.max_score Print.text_center(score_text, Config.screen.width / 2, mg.bar_y + mg.bar_height + 8, Config.colors.light_grey) - -- Draw instruction text Print.text_center( "Press Z when line is in green!", Config.screen.width / 2, mg.bar_y + mg.bar_height + 20, Config.colors.light_grey ) - -- Draw button indicator in bottom-right corner local button_color = Config.colors.light_grey if mg.button_pressed_timer > 0 then - button_color = Config.colors.green -- Highlight when pressed + button_color = Config.colors.green end - -- Draw button circle circb(mg.button_x, mg.button_y, mg.button_size, button_color) if mg.button_pressed_timer > 0 then circ(mg.button_x, mg.button_y, mg.button_size - 2, button_color) end - -- Draw Z text in the button Print.text_center("Z", mg.button_x - 2, mg.button_y - 3, button_color) -end \ No newline at end of file +end