diff --git a/inc/audio/audio.manager.lua b/inc/audio/audio.manager.lua index 1e9bd70..4999e70 100644 --- a/inc/audio/audio.manager.lua +++ b/inc/audio/audio.manager.lua @@ -3,27 +3,35 @@ --- Stops current music. --- @within Audio function Audio.music_stop() music() end + --- Plays main menu music. --- @within Audio function Audio.music_play_mainmenu() end + --- Plays waking up music. --- @within Audio function Audio.music_play_wakingup() end + --- Plays room morning music. --- @within Audio function Audio.music_play_room_morning() end + --- Plays room street 1 music. --- @within Audio function Audio.music_play_room_street_1() end + --- Plays room street 2 music. --- @within Audio function Audio.music_play_room_street_2() end + --- Plays room music. --- @within Audio function Audio.music_play_room_() end + --- Plays room work music. --- @within Audio function Audio.music_play_room_work() music(0) end + --- Plays activity work music. --- @within Audio function Audio.music_play_activity_work() music(1) end @@ -31,18 +39,28 @@ function Audio.music_play_activity_work() music(1) end --- Plays select sound effect. --- @within Audio function Audio.sfx_select() sfx(17, 'C-7', 30) end + --- Plays deselect sound effect. --- @within Audio function Audio.sfx_deselect() sfx(18, 'C-7', 30) end + --- Plays beep sound effect. --- @within Audio function Audio.sfx_beep() sfx(19, 'C-6', 30) end + --- Plays success sound effect. --- @within Audio function Audio.sfx_success() sfx(16, 'C-7', 60) end + --- Plays bloop sound effect. --- @within Audio function Audio.sfx_bloop() sfx(21, 'C-3', 60) end + --- Plays alarm sound effect. --- @within Audio function Audio.sfx_alarm() sfx(61) end + +--- Plays sound effect for arrow hit +--- @within Audio +--- @param note string The note for the sound to play +function Audio.sfx_arrowhit(note) sfx(56, note) end diff --git a/inc/decision/decision.do_work.lua b/inc/decision/decision.do_work.lua index a3eaea2..7b50a33 100644 --- a/inc/decision/decision.do_work.lua +++ b/inc/decision/decision.do_work.lua @@ -6,9 +6,9 @@ Decision.register({ Util.go_to_screen_by_id("work") MinigameDDRWindow.start("game", nil, { on_win = function() - if (Context.minigame_ddr.special_condition_met and Context.ascension.level == 1) then + if (Context.minigame_ddr.special_mode_condition and Context.ascension.level == 1) then Context.should_ascend = true - Context.minigame_ddr.special_condition_met = false + Context.minigame_ddr.special_mode_condition = false end Meter.show() Util.go_to_screen_by_id("office") diff --git a/inc/decision/decision.play_ddr.lua b/inc/decision/decision.play_ddr.lua index 7b96a57..acf9c7e 100644 --- a/inc/decision/decision.play_ddr.lua +++ b/inc/decision/decision.play_ddr.lua @@ -1,5 +1,8 @@ Decision.register({ id = "play_ddr", label = "Play DDR (Random)", - handle = function() Meter.hide() MinigameDDRWindow.start("game", nil) end, + handle = function() + Meter.hide() + MinigameDDRWindow.start("game", nil) + end, }) diff --git a/inc/logic/logic.minigame.lua b/inc/logic/logic.minigame.lua index ec605e2..47a687c 100644 --- a/inc/logic/logic.minigame.lua +++ b/inc/logic/logic.minigame.lua @@ -3,8 +3,8 @@ --- Draws a unified win message overlay. --- @within Minigame -function Minigame.draw_win_overlay() - local text = "SUCCESS" +function Minigame.draw_win_overlay(win_text) + local text = win_text or "SUCCESS" local tw = #text * 6 local th = 6 local padding = 4 diff --git a/inc/system/system.util.lua b/inc/system/system.util.lua index f9c286e..3c8d8cb 100644 --- a/inc/system/system.util.lua +++ b/inc/system/system.util.lua @@ -39,4 +39,31 @@ function Util.contains(t, value) end end return false -end \ No newline at end of file +end + +--- Deep copies tables +--- @within Util +--- @param orig any The value to deep copy. +--- @param seen any Used for recursive calls to handle loops +--- @return any any The copied object +function Util.deepcopy(orig, seen) + if type(orig) ~= "table" then + return orig + end + + if seen and seen[orig] then + return seen[orig] -- handle cycles / shared refs + end + + local copy = {} + seen = seen or {} + seen[orig] = copy + + for k, v in pairs(orig) do + local new_k = Util.deepcopy(k, seen) + local new_v = Util.deepcopy(v, seen) + copy[new_k] = new_v + end + + return setmetatable(copy, getmetatable(orig)) +end diff --git a/inc/window/window.menu.lua b/inc/window/window.menu.lua index ebe1e49..bcd8a8b 100644 --- a/inc/window/window.menu.lua +++ b/inc/window/window.menu.lua @@ -90,7 +90,7 @@ end function MenuWindow.ddr_test() AudioTestWindow.init() GameWindow.set_state("minigame_ddr") - MinigameDDRWindow.start("menu", "generated") + MinigameDDRWindow.start("menu", "generated", { special_mode = "only_nothing" }) end --- Refreshes menu items. diff --git a/inc/window/window.minigame.ddr.lua b/inc/window/window.minigame.ddr.lua index cd8ae3c..7ddf504 100644 --- a/inc/window/window.minigame.ddr.lua +++ b/inc/window/window.minigame.ddr.lua @@ -9,7 +9,7 @@ function MinigameDDRWindow.init_context() local total_width = (4 * arrow_size) + (3 * arrow_spacing) local start_x = (Config.screen.width - total_width) / 2 return { - special_type = "normal", -- "normal", "only_special", "only_left", "only_nothing" + special_mode = "normal", -- "normal", "only_special", "only_left", "only_nothing" bar_fill = 0, max_fill = 100, fill_per_hit = 10, @@ -39,14 +39,90 @@ function MinigameDDRWindow.init_context() current_song = nil, pattern_index = 1, use_pattern = false, + generated_length = 60, return_window = nil, win_timer = 0, on_win = nil, - special_condition_met = false, total_misses = 0, + special_mode_condition = true, + special_mode_counter = 0 } end +function MinigameDDRWindow.prepareSong(song, generated_length, special_mode) + 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 special_mode == "only_special" then + local cntr = 1 + for i, v in ipairs(current_song.pattern) do + current_song.pattern[i].special = (i % 5 == 0) + end + elseif special_mode == "" then + end + end + + return current_song +end + +function MinigameDDRWindow.on_arrow_hit_special(arrow, game_context) + local special_mode = game_context.special_mode + + if special_mode == "normal" then + Audio.sfx_arrowhit(arrow.note) + elseif special_mode == "only_special" then + if arrow.special then + Audio.sfx_arrowhit(arrow.note) + game_context.special_mode_counter = game_context.special_mode_counter + 1 + else + if game_context.special_mode_condition then Audio.sfx_bloop() end + game_context.special_mode_condition = false + end + elseif special_mode == "only_left" then + if arrow.dir == "left" then + Audio.sfx_arrowhit(arrow.note) + game_context.special_mode_counter = game_context.special_mode_counter + 1 + else + if game_context.special_mode_condition then Audio.sfx_bloop() end + game_context.special_mode_condition = false + end + elseif special_mode == "only_nothing" then + if game_context.special_mode_condition then Audio.sfx_bloop() end + game_context.special_mode_condition = false + end +end + +function MinigameDDRWindow.on_end(game_context) + game_context.win_timer = Config.timing.minigame_win_duration + + local num_special = 0 + for i,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 + num_special = num_special + ((v.special and 1) or 0) + end + end + + 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 + was_ok = game_context.special_mode_counter == num_special + elseif game_context.special_mode == "only_nothing" then + -- nothing to do here :D + end + + game_context.special_mode_condition = game_context.special_mode_condition and was_ok +end + --- Initializes DDR minigame state. --- @within MinigameDDRWindow --- @param params table Optional parameters for configuration.
@@ -73,17 +149,16 @@ function MinigameDDRWindow.start(return_window, song_key, params) Context.minigame_ddr.return_window = return_window or "game" Context.minigame_ddr.debug_song_key = song_key if song_key and Songs and Songs[song_key] then - local current_song = Songs[song_key] - Context.minigame_ddr.current_song = current_song Context.minigame_ddr.use_pattern = true Context.minigame_ddr.pattern_index = 1 Context.minigame_ddr.debug_status = "Pattern loaded: " .. song_key - if current_song.generated then - local pattern = musicator_generate_pattern(30, current_song.bpm, current_song.spd * 3) - current_song.pattern = pattern - current_song.end_frame = pattern[#pattern].frame - end + Context.minigame_ddr.current_song = MinigameDDRWindow.prepareSong( + Songs[song_key], + Context.minigame_ddr.generated_length, + Context.minigame_ddr.special_mode + ) + else Context.minigame_ddr.use_pattern = false if song_key then @@ -117,7 +192,7 @@ end --- Spawns an arrow in a specific direction. --- @within MinigameDDRWindow --- @param direction string The direction of the arrow ("left", "down", "up", "right"). -local function spawn_arrow_dir(direction, note) +local function spawn_arrow_dir(direction, note, special) local mg = Context.minigame_ddr for _, target in ipairs(mg.target_arrows) do if target.dir == direction then @@ -125,7 +200,8 @@ local function spawn_arrow_dir(direction, note) dir = direction, x = target.x, y = mg.bar_y + mg.bar_height + 10, - note = note + note = note, + special = special }) break end @@ -183,7 +259,6 @@ function MinigameDDRWindow.update() if mg.win_timer > 0 then mg.win_timer = mg.win_timer - 1 if mg.win_timer == 0 then - mg.special_condition_met = (mg.total_misses == 0) Audio.music_stop() Meter.on_minigame_complete() if mg.on_win then @@ -197,7 +272,7 @@ function MinigameDDRWindow.update() end if mg.bar_fill >= mg.max_fill then - mg.win_timer = Config.timing.minigame_win_duration + MinigameDDRWindow.on_end(mg) return end @@ -205,7 +280,7 @@ function MinigameDDRWindow.update() if mg.use_pattern and mg.current_song and mg.current_song.end_frame then if mg.frame_counter > mg.current_song.end_frame and #mg.arrows == 0 then - mg.win_timer = Config.timing.minigame_win_duration + MinigameDDRWindow.on_end(mg) return end end @@ -216,7 +291,7 @@ function MinigameDDRWindow.update() local spawn_entry = pattern[mg.pattern_index] if mg.frame_counter >= spawn_entry.frame then - spawn_arrow_dir(spawn_entry.dir, spawn_entry.note) + spawn_arrow_dir(spawn_entry.dir, spawn_entry.note, spawn_entry.special) mg.pattern_index = mg.pattern_index + 1 else break @@ -275,7 +350,7 @@ function MinigameDDRWindow.update() local hit = false for i, arrow in ipairs(mg.arrows) do if arrow.dir == dir and check_hit(arrow) then - sfx(56, arrow.note) + 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 @@ -337,7 +412,8 @@ function MinigameDDRWindow.draw() end if mg.arrows then for _, arrow in ipairs(mg.arrows) do - draw_arrow(arrow.x, arrow.y, arrow.dir, Config.colors.blue) + local arrow_color = arrow.special and Config.colors.white or Config.colors.blue + 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) @@ -365,6 +441,10 @@ function MinigameDDRWindow.draw() Print.text_center("RANDOM MODE", Config.screen.width / 2, debug_y, Config.colors.blue) end if mg.win_timer > 0 then - Minigame.draw_win_overlay() + if mg.special_mode_condition then + Minigame.draw_win_overlay("SUCCESS...?") + else + Minigame.draw_win_overlay() + end +end end -end \ No newline at end of file