diff --git a/impostor.inc b/impostor.inc index b7bf0c5..7f39293 100644 --- a/impostor.inc +++ b/impostor.inc @@ -18,6 +18,7 @@ logic/logic.glitch.lua logic/logic.discussion.lua system/system.ui.lua audio/audio.manager.lua +audio/audio.generator.lua audio/audio.songs.lua sprite/sprite.manager.lua sprite/sprite.norman.lua diff --git a/inc/audio/audio.generator_model.lua b/inc/audio/audio.generator.lua similarity index 83% rename from inc/audio/audio.generator_model.lua rename to inc/audio/audio.generator.lua index 11afe01..1f2c56d 100644 --- a/inc/audio/audio.generator_model.lua +++ b/inc/audio/audio.generator.lua @@ -1,4 +1,4 @@ -{ +local musicator_markov_model = { model = { ["...|..."] = { next = { @@ -640,3 +640,105 @@ }, order = 2 } + +local function musicator_unmake_key(k) + local result = {} + for t in string.gmatch(k, "[^|]+") do + result[#result + 1] = t + end + + return result +end + +local function musicator_count_notes(sequence) + local result = 0 + + for _,v in ipairs(sequence) do + if v ~= "..." then + result = result + 1 + end + end + + return result +end + +local function musicator_generate_sequence(model_data, length) + local order = model_data.order + local model = model_data.model + + -- random start key + local model_keys = {} + for k,_ in pairs(model) do + model_keys[#model_keys + 1] = k + end + local start_key = model_keys[math.ceil(math.random() * #model_keys)] + + -- sequence starts with the start key + local seq = musicator_unmake_key(start_key) + + -- generation loop + while musicator_count_notes(seq) < length do + local current_key = table.concat({table.unpack(seq, #seq - order + 1, #seq)}, "|") -- luacheck: ignore + + local chosen = "..." + + local key_data = model[current_key] + if key_data then + local r = math.random() + local prob_sum = 0.0 + for new_note, new_prob in pairs(key_data.next) do + prob_sum = prob_sum + new_prob + if prob_sum < r then + chosen = new_note + end + end + end + +-- print(current_key .. " --> " .. chosen) + + seq[#seq+1] = chosen + end + + return seq +end + +local function musicator_row_to_frame(row, bpm, spd) + local frames_per_row = (150 * spd) / bpm + return math.floor(row * frames_per_row) +end + +local function musicator_note_to_direction(note) + local subnote = note:sub(1,1) + + local mapping = { + C="left", + D="up", + E="up", + F="right", + G="right", + A="down" + } + + return mapping[subnote] or "up" +end + +-- converts generated sequence to pattern that the ddr minigame can consume +local function musicator_sequence_to_pattern(sequence, bpm, spd) + local result = {} + + for i, note in ipairs(sequence) do + if note ~= "..." then + + local at_ms = musicator_row_to_frame(i, bpm, spd) + local direction = musicator_note_to_direction(note) + + result[#result + 1] = { frame=at_ms, dir=direction, note=note } + end + end + + return result +end + +local function musicator_generate_pattern(length, bpm, spd) + return musicator_sequence_to_pattern(musicator_generate_sequence(musicator_markov_model, length), bpm, spd) +end diff --git a/inc/audio/audio.manager.lua b/inc/audio/audio.manager.lua index 1e9bd70..0116ed5 100644 --- a/inc/audio/audio.manager.lua +++ b/inc/audio/audio.manager.lua @@ -1,48 +1,93 @@ --- @section Audio +Audio = { + music_playing = nil +} + --- Stops current music. --- @within Audio -function Audio.music_stop() music() end +function Audio.music_stop() + music() + Audio.music_playing = nil +end + +--- Plays track, doesn't restart if already playing. +function Audio.music_play(track) + if Audio.music_playing ~= track then + music(track) + Audio.music_playing = track + end +end + --- Plays main menu music. --- @within Audio function Audio.music_play_mainmenu() end + +--- Plays mystery man music. +--- @within Audio +function Audio.music_play_mystery() Audio.music_play(2) 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 +function Audio.music_play_room_work() Audio.music_play(0) end + --- Plays activity work music. --- @within Audio -function Audio.music_play_activity_work() music(1) end +function Audio.music_play_activity_work() Audio.music_play(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. + +--- Plays alarm sound effect +--- @within Audio +function Audio.sfx_alarm() sfx(34, "C-5", 240) end + +--- Plays drum sound effect. --- @within Audio -function Audio.sfx_alarm() sfx(61) end +function Audio.sfx_drum_low() sfx(61, "C-2") end + +--- Plays drum sound effect. +--- @within Audio +function Audio.sfx_drum_high() sfx(61, "C-6") 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/audio/audio.songs.lua b/inc/audio/audio.songs.lua index 70e0589..de1fa8b 100644 --- a/inc/audio/audio.songs.lua +++ b/inc/audio/audio.songs.lua @@ -105,6 +105,15 @@ Songs = { fps = 60, end_frame = nil, -- No end frame for random mode pattern = {} -- Empty, will spawn randomly in game + }, + generated = { + name = "Markov Mode", + bpm = 150, + spd = 6, + fps = 60, + end_frame = nil, -- calculated + pattern = {}, -- generated + generated = true } } @@ -162,40 +171,3 @@ Songs.custom_song = { }, 130) } ]] - ---[[ -function generate_sequence(model_data, length) - local order = model.order - local model_data = model_data.model - - local model_keys = {} - for k,_ in pairs(model) do - model_keys[#model_keys + 1] = k - end - local start_key = model_keys[math.ceil(math.random() * #model_keys)] - - local seq = unmake_key(start_key) - - while #seq < length do - local current_key = table.concat({unpack(seq, #seq - order + 1, #seq)}, "|") - - local chosen = "..." - - local key_data = model[current_key] - if key_data then - local r = math.random() - local prob_sum = 0.0 - for new_note, new_prob in pairs(key_data.next) do - prob_sum = prob_sum + new_prob - if prob_sum < r then - chosen = new_note - end - end - end - - seq[#seq+1] = chosen - end - - return seq -end -]] diff --git a/inc/decision/decision.do_work.lua b/inc/decision/decision.do_work.lua index a3eaea2..827707b 100644 --- a/inc/decision/decision.do_work.lua +++ b/inc/decision/decision.do_work.lua @@ -4,17 +4,31 @@ Decision.register({ handle = function() Meter.hide() 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 + + local modes_for_ascension_levels = {} + modes_for_ascension_levels[0] = "normal" + modes_for_ascension_levels[1] = "only_special" + modes_for_ascension_levels[2] = "only_left" + modes_for_ascension_levels[3] = "only_nothing" + + MinigameDDRWindow.start("game", "generated", { + on_win = function(game_context) + if (game_context.special_mode_condition and Context.ascension.level == 1) then + Context.should_ascend = true + elseif (game_context.special_mode_condition and Context.ascension.level == 2) then + Context.should_ascend = true + elseif (game_context.special_mode_condition and Context.ascension.level == 3) then + Context.should_ascend = true + elseif (game_context.special_mode_condition and Context.ascension.level == 4) then Context.should_ascend = true - Context.minigame_ddr.special_condition_met = false end + Meter.show() Util.go_to_screen_by_id("office") Window.set_current("game") Context.have_done_work_today = true end, + special_mode = modes_for_ascension_levels[Ascension.get_level()] }) end, }) diff --git a/inc/decision/decision.go_to_home.lua b/inc/decision/decision.go_to_home.lua index bc02eff..b898adb 100644 --- a/inc/decision/decision.go_to_home.lua +++ b/inc/decision/decision.go_to_home.lua @@ -1,6 +1,6 @@ Decision.register({ id = "go_to_home", - label = "Go to Home", + label = "Go Home", condition = function() return Context.have_been_to_office and Context.have_done_work_today end, diff --git a/inc/decision/decision.go_to_walking_to_home.lua b/inc/decision/decision.go_to_walking_to_home.lua index 842c639..ce5734d 100644 --- a/inc/decision/decision.go_to_walking_to_home.lua +++ b/inc/decision/decision.go_to_walking_to_home.lua @@ -1,6 +1,6 @@ Decision.register({ id = "go_to_walking_to_home", - label = "Walking to home", + label = "Walk home", handle = function() Util.go_to_screen_by_id("walking_to_home") end, diff --git a/inc/decision/decision.go_to_walking_to_office.lua b/inc/decision/decision.go_to_walking_to_office.lua index 73e0594..2a713dc 100644 --- a/inc/decision/decision.go_to_walking_to_office.lua +++ b/inc/decision/decision.go_to_walking_to_office.lua @@ -1,6 +1,6 @@ Decision.register({ id = "go_to_walking_to_office", - label = "Walking to office", + label = "Walk to office", handle = function() Util.go_to_screen_by_id("walking_to_office") end, 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/meta/meta.assets.lua b/inc/meta/meta.assets.lua index a97fea2..cda7216 100644 --- a/inc/meta/meta.assets.lua +++ b/inc/meta/meta.assets.lua @@ -396,26 +396,27 @@ -- 016:ffffffffff0010201020766777001020102010201020102000fffffffffff3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f30b1b2b1b2b7667776777761b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- -- --- 000:060006400600064006000640060006400600060006000600060006000600060006000600060006000600060006000600060006000600060006000600300000000900 -- 016:00000000000000400040004000700070007000400040004000700070007000c000c000c000c000c000c000c000c000c000c000c000c000c000c000c0470000000000 --- 017:000000000000000000000000006000600060006000600060106020c030c050c060c080c0a0c0b0c0c0c0c0c0d0c0d0c0e0c0f0c0f0c0f0c0f0c0f0c0400000000000 --- 018:00c000c000c000c000c000c0006000600060006000600060200030005000600080009000a000b000c000d000d000e000e000e000f000f000f000f000500000000000 --- 019:0000000000000000000000d010d010d020d030d050d070d090d0b0d0c0d0e0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0500000000000 +-- 017:030003000300030003000300036003600360036003600360136023c033c053c063c083c0a3c0b3c0c3c0c3c0d3c0d3c0e3c0f3c0f3c0f3c0f3c0f3c0400000000000 +-- 018:03c003c003c003c003c003c0036003600360036003600360230033005300630083009300a300b300c300d300d300e300e300e300f300f300f300f300400000000000 +-- 019:0300030003000300030003d013d013d023d033d053d073d093d0b3d0c3d0e3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0400000000000 -- 020:090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900500000000000 -- 021:01000100010001000100f10001100110011001100110f11001200120012001200120f1201130113011302130213021302130313041308130a130d130580000000000 +-- 022:03b003100300030003000300130063009300b300c300d300d300e300e300e300f300f300f300f300f300f300f300f300f300f300f300f300f300f300400000000000 -- 032:010001100100011001000110010001100100010001000100010001000100010001000100010001000100010001000100010001000100010001000100400000000800 --- 033:000000010002000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d40000000004 --- 044:0600f6000620f6000600f6000610f600f600f6000600f600f600f600f6000600060006000600060006000600060006000600060006000600060006004600000f0f00 --- 045:0000f0000020f0000000f0000010f000f000f0000000f000f000f000f0000000000000000000000000000000000000000000000000000000000000004600000f0f00 +-- 033:000000010002000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c40000000004 +-- 034:02000240020002000200020002000200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f2004700000f0200 +-- 044:0600f6000620f6000600f6000610f600f600f6000600f600f600f600f6000600060006000600060006000600060006000600060006000600060006001600000f0f00 +-- 045:0000f0000020f0000000f0000010f000f000f0000000f000f000f000f0000000000000000000000000000000000000000000000000000000000000005600000f0f00 -- 048:090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900400000000000 --- 056:4100510061406140717081709100b100c100d100e100e100e100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f10058a000000600 --- 057:000000010002000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d40000000004 --- 058:41004110410041104100411041004110c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100500000080800 --- 059:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000 --- 060:220022002200820082008200820082008200820082008200820082008200820082008200820082008200820082008200820082008200820082008200100000000000 --- 061:9f009f00bf00df00df00ef00ef00ef00ef00ef00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00400000000000 --- 062:00000100010001000100510081008100910091009100a100a100a100a100a100b100b100b100b100c100c100c100d100d100d100e100e100e100f100484000000000 --- 063:00b000100000000000000000100060009000b000c000d000d000e000e000e000f000f000f000f000f000f000f000f000f000f000f000f000f000f000200000000000 +-- 056:4100510061406140717081709100b100c100d100e100e100e100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100480000000600 +-- 057:000000010002000300020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000840000000004 +-- 058:41004110410041104100411041004110c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100003000080800 +-- 059:03000300030003000300030003000300030003000300030003000300030003000300030003000300030003000300030003000300030003000300030000b000000000 +-- 060:220022002200820082008200820082008200820082008200820082008200820082008200820082008200820082008200820082008200820082008200500000000000 +-- 061:9f009f00bf00df00df00ef00ef00ef00ef00ef00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00100000000000 +-- 062:00000100010001000100510081008100910091009100a100a100a100a100a100b100b100b100b100c100c100c100d100d100d100e100e100e100f100580000000000 +-- 063:00b000100000000000000000100060009000b000c000d000d000e000e000e000f000f000f000f000f000f000f000f000f000f000f000f000f000f000500000000000 -- -- -- 000:bcceefceedddddc84333121268abaa99 @@ -433,10 +434,13 @@ -- 000:4008b50000000000000000001008c10000004008b50000001008c1000000000000000000e008b30000004008b50000001008c10000000008c10000000008c10000000000000000000000000000000000000000000000000000000000000000004008b50000000000000000001008c10000004008b50000001008c10000000008c1000000e008b30000004008b50000001008c10000000008c10000000008c10000000008c10000000008c10000000008c1000000000000000000000000000000 -- 001:4008b50000000000000000001008c10000004008b50000001008c1000000000000000000e008b30000004008b50000001008c10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007008b50000007008b50000001008c10000007008b50000001008c10000000008c10000007008b50000009008b50000001008c10000009008b50000001008c10000009008b50000009008b50000001008c10000009008b50000001008c1000000 -- 003:4008d30000000000000000000000000000000000000000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000004008d30000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000000000000000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000004008d30000004008d9000000000000000000000000000000000000000000 --- 004:49998d000000e0088b000000b0088b000881e0088b00000040088d000000e0088b000881b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e00889000000 --- 005:400881000000000881000000000881000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000 +-- 004:43398d000000e0088b000000b0088b000881e0088b00000040088d000000e0088b000881b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e00889000000 +-- 005:455981000000000881000000000881000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000 +-- 008:4aa9b30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005008b30000000000000000000000000000000000000000000008b10000000000000000000008910000000000000000004008b30000000000000000000000000000000000000000000000000000000000000000000008b1000000000000000000f008b1000000000000000000000000000000000000000000000891000000000000000000000000000000000000000000 +-- 009:4779d30000000000000000004008d30000000000000000004008db0000000000000000004008d30000000000000000004008d30000000000000000004008d30000000000000000004008db0000000000000000004008d30000000000000000004008d30000000000000000004008d30000000000000000004008db0000000000000000004008d30000000000000000004008d30000000000000000004008d30000000000000000004008db0000000000000000004008d3000000000000000000 -- -- -- 000:1000012000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff -- 001:581000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +-- 002:900082000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- diff --git a/inc/screen/screen.home.lua b/inc/screen/screen.home.lua index 41321b6..86916b3 100644 --- a/inc/screen/screen.home.lua +++ b/inc/screen/screen.home.lua @@ -7,6 +7,9 @@ Screen.register({ "go_to_sleep", "go_to_end", }, + init = function() + Audio.music_play_room_work() + end, background = "bedroom", draw = function() if Context.home_norman_visible and Window.get_current_id() == "game" then diff --git a/inc/screen/screen.mysterious_man.lua b/inc/screen/screen.mysterious_man.lua index 11f416c..5c23556 100644 --- a/inc/screen/screen.mysterious_man.lua +++ b/inc/screen/screen.mysterious_man.lua @@ -194,6 +194,12 @@ Screen.register({ name = "Mysterious Man", decisions = {}, background_color = Config.colors.black, + init = function() + Audio.music_play_mystery() + end, + exit = function() + Audio.music_stop() + end, update = function() if state == STATE_TEXT then if not text_done then @@ -216,6 +222,10 @@ Screen.register({ text_done_timer = text_done_timer - Context.delta_time if text_done_timer <= 0 or Input.select() then MysteriousManScreen.go_to_day_state() + -- to be continued + if 4 <= Ascension.get_level() then + Window.set_current("continued") + end end end elseif state == STATE_DAY then diff --git a/inc/screen/screen.office.lua b/inc/screen/screen.office.lua index 12b7c0e..917f953 100644 --- a/inc/screen/screen.office.lua +++ b/inc/screen/screen.office.lua @@ -9,6 +9,9 @@ Screen.register({ situations = { "drink_coffee", }, + init = function() + Audio.music_play_room_work() + end, background = "office", draw = function() if Window.get_current_id() == "game" then diff --git a/inc/screen/screen.toilet.lua b/inc/screen/screen.toilet.lua index be038c6..1d7be30 100644 --- a/inc/screen/screen.toilet.lua +++ b/inc/screen/screen.toilet.lua @@ -6,6 +6,7 @@ Screen.register({ }, background = "bedroom", init = function() + Audio.music_play_mystery() Context.stat_screen_active = true Meter.hide() local cx = Config.screen.width * 0.75 diff --git a/inc/screen/screen.walking_to_home.lua b/inc/screen/screen.walking_to_home.lua index 6ae9234..b9c9ad2 100644 --- a/inc/screen/screen.walking_to_home.lua +++ b/inc/screen/screen.walking_to_home.lua @@ -5,6 +5,9 @@ Screen.register({ "go_to_home", "go_to_office", }, + init = function() + Audio.music_play_room_work() + end, background = "street", draw = function() if Window.get_current_id() == "game" then diff --git a/inc/screen/screen.walking_to_office.lua b/inc/screen/screen.walking_to_office.lua index 33cf355..4e78252 100644 --- a/inc/screen/screen.walking_to_office.lua +++ b/inc/screen/screen.walking_to_office.lua @@ -6,6 +6,9 @@ Screen.register({ "go_to_office", "sumphore_discussion", }, + init = function() + Audio.music_play_room_work() + end, background = "street", draw = function() if Window.get_current_id() == "game" then 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 15c6326..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", nil) + 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 76cc95c..d549dae 100644 --- a/inc/window/window.minigame.ddr.lua +++ b/inc/window/window.minigame.ddr.lua @@ -19,6 +19,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_mode = "normal", -- "normal", "only_special", "only_left", "only_nothing" bar_fill = 0, max_fill = 100, fill_per_hit = 10, @@ -48,14 +49,92 @@ function MinigameDDRWindow.init_context() current_song = nil, pattern_index = 1, use_pattern = false, + generated_length = 30, return_window = nil, win_timer = 0, on_win = nil, - special_condition_met = false, total_misses = 0, + total_hits = 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 + for i, _ in ipairs(current_song.pattern) do + current_song.pattern[i].special = (i % 5 == 0) + end + 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 + if game_context.max_fill <= game_context.bar_fill + game_context.fill_per_hit then + game_context.bar_fill = game_context.bar_fill - game_context.fill_per_hit + end + 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) + Audio.sfx_select() + + game_context.win_timer = Config.timing.minigame_win_duration + + local num_special = 0 + for _,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 + 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.
@@ -76,13 +155,22 @@ end --- @param[opt] params table Optional parameters for minigame configuration.
function MinigameDDRWindow.start(return_window, song_key, params) MinigameDDRWindow.init(params) + + Audio.music_play_activity_work() + 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 - 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 + + 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 @@ -91,12 +179,19 @@ function MinigameDDRWindow.start(return_window, song_key, params) Context.minigame_ddr.debug_status = "Random mode" end end + + if not Context.test_mode then + Context.minigame_ddr.debug_status = "" + end + Window.set_current("minigame_ddr") end --- Spawns a random arrow. --- @within MinigameDDRWindow local function spawn_arrow() + trace("random arrow") + local mg = Context.minigame_ddr local target = mg.target_arrows[math.random(1, 4)] table.insert(mg.arrows, { @@ -109,14 +204,16 @@ 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) +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 table.insert(mg.arrows, { dir = direction, x = target.x, - y = mg.bar_y + mg.bar_height + 10 + y = mg.bar_y + mg.bar_height + 10, + note = note, + special = special }) break end @@ -174,10 +271,10 @@ 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 - mg.on_win() + mg.on_win(mg) else Meter.show() Window.set_current(mg.return_window) @@ -187,22 +284,26 @@ 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 + mg.frame_counter = mg.frame_counter + 1 + 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 + if mg.use_pattern and mg.current_song and mg.current_song.pattern then local pattern = mg.current_song.pattern while mg.pattern_index <= #pattern do local spawn_entry = pattern[mg.pattern_index] + if mg.frame_counter >= spawn_entry.frame then - spawn_arrow_dir(spawn_entry.dir) + spawn_arrow_dir(spawn_entry.dir, spawn_entry.note, spawn_entry.special) mg.pattern_index = mg.pattern_index + 1 else break @@ -215,6 +316,8 @@ function MinigameDDRWindow.update() mg.arrow_spawn_timer = 0 end end + + -- move arrow downwards local arrows_to_remove = {} for i, arrow in ipairs(mg.arrows) do arrow.y = arrow.y + mg.arrow_fall_speed @@ -227,26 +330,31 @@ function MinigameDDRWindow.update() mg.total_misses = mg.total_misses + 1 end end + -- iterate backwards to avoid index shift issues for i = #arrows_to_remove, 1, -1 do table.remove(mg.arrows, arrows_to_remove[i]) end + 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 + 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 + local input_map = { left = Input.left(), down = Input.down(), up = Input.up(), right = Input.right() } + for dir, pressed in pairs(input_map) do if pressed and mg.input_cooldowns[dir] == 0 then mg.input_cooldowns[dir] = mg.input_cooldown_duration @@ -254,6 +362,8 @@ function MinigameDDRWindow.update() local hit = false for i, arrow in ipairs(mg.arrows) do if arrow.dir == dir and check_hit(arrow) then + 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 mg.bar_fill = mg.max_fill @@ -315,7 +425,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) @@ -324,7 +435,7 @@ function MinigameDDRWindow.draw() Print.text_center(mg.debug_status, Config.screen.width / 2, debug_y, Config.colors.item) debug_y = debug_y + 10 end - if mg.use_pattern then + if mg.use_pattern and Context.test_mode then Print.text_center( "PATTERN MODE - Frame:" .. mg.frame_counter, Config.screen.width / 2, @@ -339,10 +450,16 @@ function MinigameDDRWindow.draw() Config.colors.light_blue ) end - else + elseif Context.test_mode then 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...?") + elseif mg.total_hits < 10 then + Minigame.draw_win_overlay("MEH...") + else + Minigame.draw_win_overlay() + end +end end -end \ No newline at end of file diff --git a/inc/window/window.minigame.mash.lua b/inc/window/window.minigame.mash.lua index fbc4850..c39bfd9 100644 --- a/inc/window/window.minigame.mash.lua +++ b/inc/window/window.minigame.mash.lua @@ -84,6 +84,8 @@ function MinigameButtonMashWindow.update() end if Input.select() then + Audio.sfx_drum_high() + mg.bar_fill = mg.bar_fill + mg.fill_per_press mg.button_pressed_timer = mg.button_press_duration if mg.bar_fill > mg.target_points then @@ -91,6 +93,7 @@ function MinigameButtonMashWindow.update() end end if mg.bar_fill >= mg.target_points then + Audio.sfx_select() mg.win_timer = Config.timing.minigame_win_duration return end diff --git a/inc/window/window.minigame.rhythm.lua b/inc/window/window.minigame.rhythm.lua index a874b18..8e13276 100644 --- a/inc/window/window.minigame.rhythm.lua +++ b/inc/window/window.minigame.rhythm.lua @@ -114,6 +114,7 @@ function MinigameRhythmWindow.update() end end if mg.score >= mg.max_score then + Audio.sfx_select() mg.win_timer = Config.timing.minigame_win_duration return end diff --git a/tools/musicator/musicator.lua b/tools/musicator/musicator.lua index d9927e4..ed27fbb 100644 --- a/tools/musicator/musicator.lua +++ b/tools/musicator/musicator.lua @@ -87,7 +87,7 @@ function build_markov_model(sequence, order) end function generate_sequence(model_data, length) - local order = model.order + local order = model_data.order local model_data = model_data.model -- random start key