Compare commits

..

4 Commits

Author SHA1 Message Date
4e6590174a - lint fix
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-21 23:31:40 +01:00
6a33be82e9 - added music and sounds to things
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- hooked up ddr logic into game logic
- TODO: "left_only" ddr needs tweak
2026-03-21 23:20:48 +01:00
mr.one
9a3c9ee28c - ddr special logic seems to be working in solation
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-21 17:26:39 +01:00
mr.one
c41bf23a45 - musicator: generation + ddr operational
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- TODO: special logic, code cleanup
2026-03-21 01:41:30 +01:00
21 changed files with 390 additions and 87 deletions

2
.gitignore vendored
View File

@@ -4,5 +4,3 @@ impostor.original.lua
prompts prompts
docs docs
minify.lua minify.lua
*.tic
*.zip

View File

@@ -17,6 +17,7 @@ logic/logic.glitch.lua
logic/logic.discussion.lua logic/logic.discussion.lua
system/system.ui.lua system/system.ui.lua
audio/audio.manager.lua audio/audio.manager.lua
audio/audio.generator.lua
audio/audio.songs.lua audio/audio.songs.lua
sprite/sprite.manager.lua sprite/sprite.manager.lua
sprite/sprite.norman.lua sprite/sprite.norman.lua

View File

@@ -1,4 +1,4 @@
{ local musicator_markov_model = {
model = { model = {
["...|..."] = { ["...|..."] = {
next = { next = {
@@ -640,3 +640,105 @@
}, },
order = 2 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 not (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({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
-- 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 not (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

View File

@@ -1,48 +1,93 @@
--- @section Audio --- @section Audio
Audio = {
music_playing = nil
}
--- Stops current music. --- Stops current music.
--- @within Audio --- @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 not (Audio.music_playing == track) then
music(track)
Audio.music_playing = track
end
end
--- Plays main menu music. --- Plays main menu music.
--- @within Audio --- @within Audio
function Audio.music_play_mainmenu() end 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. --- Plays waking up music.
--- @within Audio --- @within Audio
function Audio.music_play_wakingup() end function Audio.music_play_wakingup() end
--- Plays room morning music. --- Plays room morning music.
--- @within Audio --- @within Audio
function Audio.music_play_room_morning() end function Audio.music_play_room_morning() end
--- Plays room street 1 music. --- Plays room street 1 music.
--- @within Audio --- @within Audio
function Audio.music_play_room_street_1() end function Audio.music_play_room_street_1() end
--- Plays room street 2 music. --- Plays room street 2 music.
--- @within Audio --- @within Audio
function Audio.music_play_room_street_2() end function Audio.music_play_room_street_2() end
--- Plays room music. --- Plays room music.
--- @within Audio --- @within Audio
function Audio.music_play_room_() end function Audio.music_play_room_() end
--- Plays room work music. --- Plays room work music.
--- @within Audio --- @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. --- Plays activity work music.
--- @within Audio --- @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. --- Plays select sound effect.
--- @within Audio --- @within Audio
function Audio.sfx_select() sfx(17, 'C-7', 30) end function Audio.sfx_select() sfx(17, 'C-7', 30) end
--- Plays deselect sound effect. --- Plays deselect sound effect.
--- @within Audio --- @within Audio
function Audio.sfx_deselect() sfx(18, 'C-7', 30) end function Audio.sfx_deselect() sfx(18, 'C-7', 30) end
--- Plays beep sound effect. --- Plays beep sound effect.
--- @within Audio --- @within Audio
function Audio.sfx_beep() sfx(19, 'C-6', 30) end function Audio.sfx_beep() sfx(19, 'C-6', 30) end
--- Plays success sound effect. --- Plays success sound effect.
--- @within Audio --- @within Audio
function Audio.sfx_success() sfx(16, 'C-7', 60) end function Audio.sfx_success() sfx(16, 'C-7', 60) end
--- Plays bloop sound effect. --- Plays bloop sound effect.
--- @within Audio --- @within Audio
function Audio.sfx_bloop() sfx(21, 'C-3', 60) end function Audio.sfx_bloop() sfx(21, 'C-3', 60) end
--- Plays alarm sound effect.
--- Plays alarm sound effect
--- @within Audio --- @within Audio
function Audio.sfx_alarm() sfx(61) end function Audio.sfx_alarm() sfx(34, "C-5", 240) end
--- Plays drum sound effect.
--- @within Audio
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

View File

@@ -105,6 +105,15 @@ Songs = {
fps = 60, fps = 60,
end_frame = nil, -- No end frame for random mode end_frame = nil, -- No end frame for random mode
pattern = {} -- Empty, will spawn randomly in game 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) }, 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
]]

View File

@@ -4,17 +4,31 @@ Decision.register({
handle = function() handle = function()
Meter.hide() Meter.hide()
Util.go_to_screen_by_id("work") Util.go_to_screen_by_id("work")
MinigameDDRWindow.start("game", nil, {
on_win = function() local modes_for_ascension_levels = {}
if (Context.minigame_ddr.special_condition_met and Context.ascension.level == 1) then 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.should_ascend = true
Context.minigame_ddr.special_condition_met = false
end end
Meter.show() Meter.show()
Util.go_to_screen_by_id("office") Util.go_to_screen_by_id("office")
Window.set_current("game") Window.set_current("game")
Context.have_done_work_today = true Context.have_done_work_today = true
end, end,
special_mode = modes_for_ascension_levels[Ascension.get_level()]
}) })
end, end,
}) })

View File

@@ -1,5 +1,8 @@
Decision.register({ Decision.register({
id = "play_ddr", id = "play_ddr",
label = "Play DDR (Random)", label = "Play DDR (Random)",
handle = function() Meter.hide() MinigameDDRWindow.start("game", nil) end, handle = function()
Meter.hide()
MinigameDDRWindow.start("game", nil)
end,
}) })

View File

@@ -3,8 +3,8 @@
--- Draws a unified win message overlay. --- Draws a unified win message overlay.
--- @within Minigame --- @within Minigame
function Minigame.draw_win_overlay() function Minigame.draw_win_overlay(win_text)
local text = "SUCCESS" local text = win_text or "SUCCESS"
local tw = #text * 6 local tw = #text * 6
local th = 6 local th = 6
local padding = 4 local padding = 4

View File

@@ -396,26 +396,27 @@
-- 016:ffffffffff0010201020766777001020102010201020102000fffffffffff3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f30b1b2b1b2b7667776777761b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 016:ffffffffff0010201020766777001020102010201020102000fffffffffff3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f30b1b2b1b2b7667776777761b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- </MAP> -- </MAP>
-- <SFX> -- <SFX>
-- 000:060006400600064006000640060006400600060006000600060006000600060006000600060006000600060006000600060006000600060006000600300000000900
-- 016:00000000000000400040004000700070007000400040004000700070007000c000c000c000c000c000c000c000c000c000c000c000c000c000c000c0470000000000 -- 016:00000000000000400040004000700070007000400040004000700070007000c000c000c000c000c000c000c000c000c000c000c000c000c000c000c0470000000000
-- 017:000000000000000000000000006000600060006000600060106020c030c050c060c080c0a0c0b0c0c0c0c0c0d0c0d0c0e0c0f0c0f0c0f0c0f0c0f0c0400000000000 -- 017:030003000300030003000300036003600360036003600360136023c033c053c063c083c0a3c0b3c0c3c0c3c0d3c0d3c0e3c0f3c0f3c0f3c0f3c0f3c0400000000000
-- 018:00c000c000c000c000c000c0006000600060006000600060200030005000600080009000a000b000c000d000d000e000e000e000f000f000f000f000500000000000 -- 018:03c003c003c003c003c003c0036003600360036003600360230033005300630083009300a300b300c300d300d300e300e300e300f300f300f300f300400000000000
-- 019:0000000000000000000000d010d010d020d030d050d070d090d0b0d0c0d0e0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0500000000000 -- 019:0300030003000300030003d013d013d023d033d053d073d093d0b3d0c3d0e3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0400000000000
-- 020:090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900500000000000 -- 020:090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900500000000000
-- 021:01000100010001000100f10001100110011001100110f11001200120012001200120f1201130113011302130213021302130313041308130a130d130580000000000 -- 021:01000100010001000100f10001100110011001100110f11001200120012001200120f1201130113011302130213021302130313041308130a130d130580000000000
-- 022:03b003100300030003000300130063009300b300c300d300d300e300e300e300f300f300f300f300f300f300f300f300f300f300f300f300f300f300400000000000
-- 032:010001100100011001000110010001100100010001000100010001000100010001000100010001000100010001000100010001000100010001000100400000000800 -- 032:010001100100011001000110010001100100010001000100010001000100010001000100010001000100010001000100010001000100010001000100400000000800
-- 033:000000010002000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d40000000004 -- 033:000000010002000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c40000000004
-- 044:0600f6000620f6000600f6000610f600f600f6000600f600f600f600f6000600060006000600060006000600060006000600060006000600060006004600000f0f00 -- 034:02000240020002000200020002000200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f2004700000f0200
-- 045:0000f0000020f0000000f0000010f000f000f0000000f000f000f000f0000000000000000000000000000000000000000000000000000000000000004600000f0f00 -- 044:0600f6000620f6000600f6000610f600f600f6000600f600f600f600f6000600060006000600060006000600060006000600060006000600060006001600000f0f00
-- 045:0000f0000020f0000000f0000010f000f000f0000000f000f000f000f0000000000000000000000000000000000000000000000000000000000000005600000f0f00
-- 048:090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900400000000000 -- 048:090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900400000000000
-- 056:4100510061406140717081709100b100c100d100e100e100e100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f10058a000000600 -- 056:4100510061406140717081709100b100c100d100e100e100e100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100480000000600
-- 057:000000010002000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d40000000004 -- 057:000000010002000300020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000840000000004
-- 058:41004110410041104100411041004110c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100500000080800 -- 058:41004110410041104100411041004110c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100003000080800
-- 059:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000 -- 059:03000300030003000300030003000300030003000300030003000300030003000300030003000300030003000300030003000300030003000300030000b000000000
-- 060:220022002200820082008200820082008200820082008200820082008200820082008200820082008200820082008200820082008200820082008200100000000000 -- 060:220022002200820082008200820082008200820082008200820082008200820082008200820082008200820082008200820082008200820082008200500000000000
-- 061:9f009f00bf00df00df00ef00ef00ef00ef00ef00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00400000000000 -- 061:9f009f00bf00df00df00ef00ef00ef00ef00ef00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00100000000000
-- 062:00000100010001000100510081008100910091009100a100a100a100a100a100b100b100b100b100c100c100c100d100d100d100e100e100e100f100484000000000 -- 062:00000100010001000100510081008100910091009100a100a100a100a100a100b100b100b100b100c100c100c100d100d100d100e100e100e100f100580000000000
-- 063:00b000100000000000000000100060009000b000c000d000d000e000e000e000f000f000f000f000f000f000f000f000f000f000f000f000f000f000200000000000 -- 063:00b000100000000000000000100060009000b000c000d000d000e000e000e000f000f000f000f000f000f000f000f000f000f000f000f000f000f000500000000000
-- </SFX> -- </SFX>
-- <WAVES> -- <WAVES>
-- 000:bcceefceedddddc84333121268abaa99 -- 000:bcceefceedddddc84333121268abaa99
@@ -433,10 +434,13 @@
-- 000:4008b50000000000000000001008c10000004008b50000001008c1000000000000000000e008b30000004008b50000001008c10000000008c10000000008c10000000000000000000000000000000000000000000000000000000000000000004008b50000000000000000001008c10000004008b50000001008c10000000008c1000000e008b30000004008b50000001008c10000000008c10000000008c10000000008c10000000008c10000000008c1000000000000000000000000000000 -- 000:4008b50000000000000000001008c10000004008b50000001008c1000000000000000000e008b30000004008b50000001008c10000000008c10000000008c10000000000000000000000000000000000000000000000000000000000000000004008b50000000000000000001008c10000004008b50000001008c10000000008c1000000e008b30000004008b50000001008c10000000008c10000000008c10000000008c10000000008c10000000008c1000000000000000000000000000000
-- 001:4008b50000000000000000001008c10000004008b50000001008c1000000000000000000e008b30000004008b50000001008c10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007008b50000007008b50000001008c10000007008b50000001008c10000000008c10000007008b50000009008b50000001008c10000009008b50000001008c10000009008b50000009008b50000001008c10000009008b50000001008c1000000 -- 001:4008b50000000000000000001008c10000004008b50000001008c1000000000000000000e008b30000004008b50000001008c10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007008b50000007008b50000001008c10000007008b50000001008c10000000008c10000007008b50000009008b50000001008c10000009008b50000001008c10000009008b50000009008b50000001008c10000009008b50000001008c1000000
-- 003:4008d30000000000000000000000000000000000000000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000004008d30000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000000000000000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000004008d30000004008d9000000000000000000000000000000000000000000 -- 003:4008d30000000000000000000000000000000000000000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000004008d30000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000000000000000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000004008d30000004008d9000000000000000000000000000000000000000000
-- 004:49998d000000e0088b000000b0088b000881e0088b00000040088d000000e0088b000881b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e00889000000 -- 004:43398d000000e0088b000000b0088b000881e0088b00000040088d000000e0088b000881b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e00889000000
-- 005:400881000000000881000000000881000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000 -- 005:455981000000000881000000000881000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000
-- 008:4aa9b30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005008b30000000000000000000000000000000000000000000008b10000000000000000000008910000000000000000004008b30000000000000000000000000000000000000000000000000000000000000000000008b1000000000000000000f008b1000000000000000000000000000000000000000000000891000000000000000000000000000000000000000000
-- 009:4779d30000000000000000004008d30000000000000000004008db0000000000000000004008d30000000000000000004008d30000000000000000004008d30000000000000000004008db0000000000000000004008d30000000000000000004008d30000000000000000004008d30000000000000000004008db0000000000000000004008d30000000000000000004008d30000000000000000004008d30000000000000000004008db0000000000000000004008d3000000000000000000
-- </PATTERNS> -- </PATTERNS>
-- <TRACKS> -- <TRACKS>
-- 000:1000012000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff -- 000:1000012000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff
-- 001:581000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 001:581000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 002:900082000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- </TRACKS> -- </TRACKS>

View File

@@ -7,6 +7,9 @@ Screen.register({
"go_to_sleep", "go_to_sleep",
"go_to_end", "go_to_end",
}, },
init = function()
Audio.music_play_room_work()
end,
background = "bedroom", background = "bedroom",
draw = function() draw = function()
if Context.home_norman_visible and Window.get_current_id() == "game" then if Context.home_norman_visible and Window.get_current_id() == "game" then

View File

@@ -191,6 +191,12 @@ Screen.register({
name = "Mysterious Man", name = "Mysterious Man",
decisions = {}, decisions = {},
background_color = Config.colors.black, background_color = Config.colors.black,
init = function()
Audio.music_play_mystery()
end,
exit = function()
Audio.music_stop()
end,
update = function() update = function()
if state == STATE_TEXT then if state == STATE_TEXT then
if not text_done then if not text_done then

View File

@@ -9,6 +9,9 @@ Screen.register({
situations = { situations = {
"drink_coffee", "drink_coffee",
}, },
init = function()
Audio.music_play_room_work()
end,
background = "office", background = "office",
draw = function() draw = function()
if Window.get_current_id() == "game" then if Window.get_current_id() == "game" then

View File

@@ -6,6 +6,7 @@ Screen.register({
}, },
background = "bedroom", background = "bedroom",
init = function() init = function()
Audio.music_play_mystery()
Context.stat_screen_active = true Context.stat_screen_active = true
Meter.hide() Meter.hide()
local cx = Config.screen.width * 0.75 local cx = Config.screen.width * 0.75

View File

@@ -5,6 +5,9 @@ Screen.register({
"go_to_home", "go_to_home",
"go_to_office", "go_to_office",
}, },
init = function()
Audio.music_play_room_work()
end,
background = "street", background = "street",
draw = function() draw = function()
if Window.get_current_id() == "game" then if Window.get_current_id() == "game" then

View File

@@ -6,6 +6,9 @@ Screen.register({
"go_to_office", "go_to_office",
"sumphore_discussion", "sumphore_discussion",
}, },
init = function()
Audio.music_play_room_work()
end,
background = "street", background = "street",
draw = function() draw = function()
if Window.get_current_id() == "game" then if Window.get_current_id() == "game" then

View File

@@ -40,3 +40,30 @@ function Util.contains(t, value)
end end
return false return false
end 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

View File

@@ -90,7 +90,7 @@ end
function MenuWindow.ddr_test() function MenuWindow.ddr_test()
AudioTestWindow.init() AudioTestWindow.init()
GameWindow.set_state("minigame_ddr") GameWindow.set_state("minigame_ddr")
MinigameDDRWindow.start("menu", nil) MinigameDDRWindow.start("menu", "generated", { special_mode = "only_nothing" })
end end
--- Refreshes menu items. --- Refreshes menu items.

View File

@@ -9,6 +9,7 @@ function MinigameDDRWindow.init_context()
local total_width = (4 * arrow_size) + (3 * arrow_spacing) local total_width = (4 * arrow_size) + (3 * arrow_spacing)
local start_x = (Config.screen.width - total_width) / 2 local start_x = (Config.screen.width - total_width) / 2
return { return {
special_mode = "normal", -- "normal", "only_special", "only_left", "only_nothing"
bar_fill = 0, bar_fill = 0,
max_fill = 100, max_fill = 100,
fill_per_hit = 10, fill_per_hit = 10,
@@ -38,14 +39,89 @@ function MinigameDDRWindow.init_context()
current_song = nil, current_song = nil,
pattern_index = 1, pattern_index = 1,
use_pattern = false, use_pattern = false,
generated_length = 30,
return_window = nil, return_window = nil,
win_timer = 0, win_timer = 0,
on_win = nil, on_win = nil,
special_condition_met = false,
total_misses = 0, total_misses = 0,
total_hits = 0,
special_mode_condition = true,
special_mode_counter = 0
} }
end 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
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. --- Initializes DDR minigame state.
--- @within MinigameDDRWindow --- @within MinigameDDRWindow
--- @param params table Optional parameters for configuration.<br/> --- @param params table Optional parameters for configuration.<br/>
@@ -66,13 +142,22 @@ end
--- @param[opt] params table Optional parameters for minigame configuration.</br> --- @param[opt] params table Optional parameters for minigame configuration.</br>
function MinigameDDRWindow.start(return_window, song_key, params) function MinigameDDRWindow.start(return_window, song_key, params)
MinigameDDRWindow.init(params) MinigameDDRWindow.init(params)
Audio.music_play_activity_work()
Context.minigame_ddr.return_window = return_window or "game" Context.minigame_ddr.return_window = return_window or "game"
Context.minigame_ddr.debug_song_key = song_key Context.minigame_ddr.debug_song_key = song_key
if song_key and Songs and Songs[song_key] then 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.use_pattern = true
Context.minigame_ddr.pattern_index = 1 Context.minigame_ddr.pattern_index = 1
Context.minigame_ddr.debug_status = "Pattern loaded: " .. song_key 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 else
Context.minigame_ddr.use_pattern = false Context.minigame_ddr.use_pattern = false
if song_key then if song_key then
@@ -81,12 +166,19 @@ function MinigameDDRWindow.start(return_window, song_key, params)
Context.minigame_ddr.debug_status = "Random mode" Context.minigame_ddr.debug_status = "Random mode"
end end
end end
if not Context.test_mode then
Context.minigame_ddr.debug_status = ""
end
Window.set_current("minigame_ddr") Window.set_current("minigame_ddr")
end end
--- Spawns a random arrow. --- Spawns a random arrow.
--- @within MinigameDDRWindow --- @within MinigameDDRWindow
local function spawn_arrow() local function spawn_arrow()
trace("random arrow")
local mg = Context.minigame_ddr local mg = Context.minigame_ddr
local target = mg.target_arrows[math.random(1, 4)] local target = mg.target_arrows[math.random(1, 4)]
table.insert(mg.arrows, { table.insert(mg.arrows, {
@@ -99,14 +191,16 @@ end
--- Spawns an arrow in a specific direction. --- Spawns an arrow in a specific direction.
--- @within MinigameDDRWindow --- @within MinigameDDRWindow
--- @param direction string The direction of the arrow ("left", "down", "up", "right"). --- @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 local mg = Context.minigame_ddr
for _, target in ipairs(mg.target_arrows) do for _, target in ipairs(mg.target_arrows) do
if target.dir == direction then if target.dir == direction then
table.insert(mg.arrows, { table.insert(mg.arrows, {
dir = direction, dir = direction,
x = target.x, x = target.x,
y = mg.bar_y + mg.bar_height + 10 y = mg.bar_y + mg.bar_height + 10,
note = note,
special = special
}) })
break break
end end
@@ -164,10 +258,10 @@ function MinigameDDRWindow.update()
if mg.win_timer > 0 then if mg.win_timer > 0 then
mg.win_timer = mg.win_timer - 1 mg.win_timer = mg.win_timer - 1
if mg.win_timer == 0 then if mg.win_timer == 0 then
mg.special_condition_met = (mg.total_misses == 0) Audio.music_stop()
Meter.on_minigame_complete() Meter.on_minigame_complete()
if mg.on_win then if mg.on_win then
mg.on_win() mg.on_win(mg)
else else
Meter.show() Meter.show()
Window.set_current(mg.return_window) Window.set_current(mg.return_window)
@@ -177,22 +271,26 @@ function MinigameDDRWindow.update()
end end
if mg.bar_fill >= mg.max_fill then if mg.bar_fill >= mg.max_fill then
mg.win_timer = Config.timing.minigame_win_duration MinigameDDRWindow.on_end(mg)
return return
end end
mg.frame_counter = mg.frame_counter + 1 mg.frame_counter = mg.frame_counter + 1
if mg.use_pattern and mg.current_song and mg.current_song.end_frame then 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 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 return
end end
end end
if mg.use_pattern and mg.current_song and mg.current_song.pattern then if mg.use_pattern and mg.current_song and mg.current_song.pattern then
local pattern = mg.current_song.pattern local pattern = mg.current_song.pattern
while mg.pattern_index <= #pattern do while mg.pattern_index <= #pattern do
local spawn_entry = pattern[mg.pattern_index] local spawn_entry = pattern[mg.pattern_index]
if mg.frame_counter >= spawn_entry.frame then 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 mg.pattern_index = mg.pattern_index + 1
else else
break break
@@ -205,6 +303,8 @@ function MinigameDDRWindow.update()
mg.arrow_spawn_timer = 0 mg.arrow_spawn_timer = 0
end end
end end
-- move arrow downwards
local arrows_to_remove = {} local arrows_to_remove = {}
for i, arrow in ipairs(mg.arrows) do for i, arrow in ipairs(mg.arrows) do
arrow.y = arrow.y + mg.arrow_fall_speed arrow.y = arrow.y + mg.arrow_fall_speed
@@ -217,26 +317,31 @@ function MinigameDDRWindow.update()
mg.total_misses = mg.total_misses + 1 mg.total_misses = mg.total_misses + 1
end end
end end
-- iterate backwards to avoid index shift issues -- iterate backwards to avoid index shift issues
for i = #arrows_to_remove, 1, -1 do for i = #arrows_to_remove, 1, -1 do
table.remove(mg.arrows, arrows_to_remove[i]) table.remove(mg.arrows, arrows_to_remove[i])
end end
for dir, _ in pairs(mg.input_cooldowns) do for dir, _ in pairs(mg.input_cooldowns) do
if mg.input_cooldowns[dir] > 0 then if mg.input_cooldowns[dir] > 0 then
mg.input_cooldowns[dir] = mg.input_cooldowns[dir] - 1 mg.input_cooldowns[dir] = mg.input_cooldowns[dir] - 1
end end
end end
for dir, _ in pairs(mg.button_pressed_timers) do for dir, _ in pairs(mg.button_pressed_timers) do
if mg.button_pressed_timers[dir] > 0 then if mg.button_pressed_timers[dir] > 0 then
mg.button_pressed_timers[dir] = mg.button_pressed_timers[dir] - 1 mg.button_pressed_timers[dir] = mg.button_pressed_timers[dir] - 1
end end
end end
local input_map = { local input_map = {
left = Input.left(), left = Input.left(),
down = Input.down(), down = Input.down(),
up = Input.up(), up = Input.up(),
right = Input.right() right = Input.right()
} }
for dir, pressed in pairs(input_map) do for dir, pressed in pairs(input_map) do
if pressed and mg.input_cooldowns[dir] == 0 then if pressed and mg.input_cooldowns[dir] == 0 then
mg.input_cooldowns[dir] = mg.input_cooldown_duration mg.input_cooldowns[dir] = mg.input_cooldown_duration
@@ -244,6 +349,8 @@ function MinigameDDRWindow.update()
local hit = false local hit = false
for i, arrow in ipairs(mg.arrows) do for i, arrow in ipairs(mg.arrows) do
if arrow.dir == dir and check_hit(arrow) then 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 mg.bar_fill = mg.bar_fill + mg.fill_per_hit
if mg.bar_fill > mg.max_fill then if mg.bar_fill > mg.max_fill then
mg.bar_fill = mg.max_fill mg.bar_fill = mg.max_fill
@@ -304,7 +411,8 @@ function MinigameDDRWindow.draw()
end end
if mg.arrows then if mg.arrows then
for _, arrow in ipairs(mg.arrows) do 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
end end
Print.text_center("Hit the arrows!", Config.screen.width / 2, mg.bar_y + mg.bar_height + 10, Config.colors.light_grey) Print.text_center("Hit the arrows!", Config.screen.width / 2, mg.bar_y + mg.bar_height + 10, Config.colors.light_grey)
@@ -313,7 +421,7 @@ function MinigameDDRWindow.draw()
Print.text_center(mg.debug_status, Config.screen.width / 2, debug_y, Config.colors.item) Print.text_center(mg.debug_status, Config.screen.width / 2, debug_y, Config.colors.item)
debug_y = debug_y + 10 debug_y = debug_y + 10
end end
if mg.use_pattern then if mg.use_pattern and Context.test_mode then
Print.text_center( Print.text_center(
"PATTERN MODE - Frame:" .. mg.frame_counter, "PATTERN MODE - Frame:" .. mg.frame_counter,
Config.screen.width / 2, Config.screen.width / 2,
@@ -328,10 +436,16 @@ function MinigameDDRWindow.draw()
Config.colors.light_blue Config.colors.light_blue
) )
end end
else elseif Context.test_mode then
Print.text_center("RANDOM MODE", Config.screen.width / 2, debug_y, Config.colors.blue) Print.text_center("RANDOM MODE", Config.screen.width / 2, debug_y, Config.colors.blue)
end end
if mg.win_timer > 0 then if mg.win_timer > 0 then
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() Minigame.draw_win_overlay()
end
end end
end end

View File

@@ -84,6 +84,8 @@ function MinigameButtonMashWindow.update()
end end
if Input.select() then if Input.select() then
Audio.sfx_drum_high()
mg.bar_fill = mg.bar_fill + mg.fill_per_press mg.bar_fill = mg.bar_fill + mg.fill_per_press
mg.button_pressed_timer = mg.button_press_duration mg.button_pressed_timer = mg.button_press_duration
if mg.bar_fill > mg.target_points then if mg.bar_fill > mg.target_points then
@@ -91,6 +93,7 @@ function MinigameButtonMashWindow.update()
end end
end end
if mg.bar_fill >= mg.target_points then if mg.bar_fill >= mg.target_points then
Audio.sfx_select()
mg.win_timer = Config.timing.minigame_win_duration mg.win_timer = Config.timing.minigame_win_duration
return return
end end

View File

@@ -114,6 +114,7 @@ function MinigameRhythmWindow.update()
end end
end end
if mg.score >= mg.max_score then if mg.score >= mg.max_score then
Audio.sfx_select()
mg.win_timer = Config.timing.minigame_win_duration mg.win_timer = Config.timing.minigame_win_duration
return return
end end

View File

@@ -87,7 +87,7 @@ function build_markov_model(sequence, order)
end end
function generate_sequence(model_data, length) function generate_sequence(model_data, length)
local order = model.order local order = model_data.order
local model_data = model_data.model local model_data = model_data.model
-- random start key -- random start key