Compare commits

..

15 Commits

Author SHA1 Message Date
197cb9403b add tic & zip to gitignore
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-21 07:10:09 +01:00
9d6d2c2c6f Merge pull request 'feature/imp-99-add-discussions' (#44) from feature/imp-99-add-discussions into develop
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #44
2026-03-20 23:19:58 +00:00
Zoltan Timar
fb8ae157b6 fix: lint
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-21 00:18:52 +01:00
Zoltan Timar
803ddc482b Merge branch 'develop' into feature/imp-99-add-discussions
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-21 00:17:08 +01:00
Zoltan Timar
ed14a86997 fix: lint
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-21 00:16:38 +01:00
Zoltan Timar
9263a90961 fix: lint
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-21 00:15:18 +01:00
Zoltan Timar
05091c7af2 feat: added sumphore & coworker discussions until asc 3, fixed game flow bugs, added mysterious man screen texts
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-21 00:12:15 +01:00
Zoltan Timar
f79233521d fix: removed mm window, made it into screen
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-20 21:25:37 +01:00
mr.one
46d3ff2ebc - tools/musicator:\n - markov model generator and pattern generator operational\n- DDR sound generation in-progress
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-20 20:18:10 +01:00
Zoltan Timar
3e6fa41021 feat: added mm discussions
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-20 18:23:17 +01:00
d899a74411 set version to 1.0-beta1
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline failed
2026-03-20 15:33:00 +01:00
4c7d43fea3 set version to 1.0-alpha
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-20 15:30:39 +01:00
94563d32c0 Merge pull request 'test mode' (#42) from feature/test-mode into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
Reviewed-on: #42
2026-03-20 06:25:24 +00:00
9da44700cb test mode
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2026-03-20 07:25:04 +01:00
b72875c42f Merge pull request 'success label border fix' (#41) from bugfix/success-label-border into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #41
2026-03-20 06:07:24 +00:00
35 changed files with 1852 additions and 347 deletions

4
.gitignore vendored
View File

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

View File

@@ -36,7 +36,7 @@ globals = {
"MinigameButtonMashWindow", "MinigameButtonMashWindow",
"MinigameRhythmWindow", "MinigameRhythmWindow",
"MinigameDDRWindow", "MinigameDDRWindow",
"MysteriousManWindow", "MysteriousManScreen",
"DiscussionWindow", "DiscussionWindow",
"EndWindow", "EndWindow",
"mset", "mset",

View File

@@ -30,5 +30,5 @@
"files.associations": { "files.associations": {
"*.conf": "bitbake", "*.conf": "bitbake",
"*.inc": "bitbake" "*.inc": "bitbake"
} },
} }

View File

@@ -48,8 +48,9 @@ decision/decision.go_to_end.lua
decision/decision.go_to_walking_to_home.lua decision/decision.go_to_walking_to_home.lua
decision/decision.go_to_sleep.lua decision/decision.go_to_sleep.lua
decision/decision.do_work.lua decision/decision.do_work.lua
decision/decision.start_discussion.lua decision/decision.sumphore_discussion.lua
discussion/discussion.sumphore.lua discussion/discussion.sumphore.lua
discussion/discussion.coworker.lua
map/map.manager.lua map/map.manager.lua
map/map.bedroom.lua map/map.bedroom.lua
map/map.street.lua map/map.street.lua
@@ -75,7 +76,6 @@ window/window.popup.lua
window/window.minigame.mash.lua window/window.minigame.mash.lua
window/window.minigame.rhythm.lua window/window.minigame.rhythm.lua
window/window.minigame.ddr.lua window/window.minigame.ddr.lua
window/window.mysterious_man.lua
window/window.discussion.lua window/window.discussion.lua
window/window.continued.lua window/window.continued.lua
window/window.game.lua window/window.game.lua

View File

@@ -0,0 +1,642 @@
{
model = {
["...|..."] = {
next = {
["..."] = 0.71111111111111,
["A-4"] = 0.0074074074074074,
["B-4"] = 0.0037037037037037,
["C-3"] = 0.011111111111111,
["C-4"] = 0.037037037037037,
["C-5"] = 0.11111111111111,
["C-6"] = 0.0037037037037037,
["D-4"] = 0.011111111111111,
["D-5"] = 0.018518518518519,
["E-4"] = 0.0074074074074074,
["E-5"] = 0.025925925925926,
["F-5"] = 0.0074074074074074,
["G-3"] = 0.0037037037037037,
["G-4"] = 0.022222222222222,
["G-5"] = 0.018518518518519
},
total = 270
},
["...|A-4"] = {
next = {
["..."] = 0.9,
["C-5"] = 0.1
},
total = 10
},
["...|A-5"] = {
next = {
["..."] = 1
},
total = 8
},
["...|B-4"] = {
next = {
["..."] = 1
},
total = 1
},
["...|B-5"] = {
next = {
["..."] = 1
},
total = 5
},
["...|C-3"] = {
next = {
["..."] = 0.66666666666667,
["C-5"] = 0.33333333333333
},
total = 3
},
["...|C-4"] = {
next = {
["..."] = 0.875,
["D-4"] = 0.083333333333333,
["E-4"] = 0.041666666666667
},
total = 24
},
["...|C-5"] = {
next = {
["..."] = 0.73333333333333,
["B-4"] = 0.033333333333333,
["C-5"] = 0.066666666666667,
["D-5"] = 0.13333333333333,
["E-5"] = 0.033333333333333
},
total = 60
},
["...|C-6"] = {
next = {
["..."] = 1
},
total = 1
},
["...|D-4"] = {
next = {
["..."] = 0.92857142857143,
["D-4"] = 0.071428571428571
},
total = 14
},
["...|D-5"] = {
next = {
["..."] = 0.80645161290323,
["C-5"] = 0.032258064516129,
["D-5"] = 0.032258064516129,
["E-5"] = 0.12903225806452
},
total = 31
},
["...|D-6"] = {
next = {
["..."] = 1
},
total = 3
},
["...|E-4"] = {
next = {
["..."] = 1
},
total = 19
},
["...|E-5"] = {
next = {
["..."] = 0.77777777777778,
["C-5"] = 0.022222222222222,
["D-5"] = 0.13333333333333,
["F-5"] = 0.066666666666667
},
total = 45
},
["...|F-3"] = {
next = {
["..."] = 1
},
total = 3
},
["...|F-4"] = {
next = {
["..."] = 0.8,
["D-4"] = 0.1,
["F-4"] = 0.1
},
total = 10
},
["...|F-5"] = {
next = {
["..."] = 0.66666666666667,
["D-5"] = 0.066666666666667,
["E-5"] = 0.066666666666667,
["G-5"] = 0.2
},
total = 15
},
["...|G-3"] = {
next = {
["..."] = 0.8,
["G-5"] = 0.2
},
total = 5
},
["...|G-4"] = {
next = {
["..."] = 0.95652173913043,
["E-4"] = 0.043478260869565
},
total = 23
},
["...|G-5"] = {
next = {
["..."] = 0.875,
["A-5"] = 0.0625,
["E-5"] = 0.0625
},
total = 16
},
["...|G-6"] = {
next = {
["..."] = 1
},
total = 1
},
["A-4|..."] = {
next = {
["..."] = 0.55555555555556,
["C-5"] = 0.33333333333333,
["D-5"] = 0.11111111111111
},
total = 9
},
["A-4|B-4"] = {
next = {
["C-5"] = 1
},
total = 2
},
["A-4|C-5"] = {
next = {
["..."] = 1
},
total = 1
},
["A-4|G-4"] = {
next = {
["F-4"] = 1
},
total = 2
},
["A-5|..."] = {
next = {
["..."] = 0.2,
["B-5"] = 0.1,
["E-4"] = 0.1,
["E-5"] = 0.4,
["F-5"] = 0.1,
["G-5"] = 0.1
},
total = 10
},
["A-5|G-5"] = {
next = {
["..."] = 0.33333333333333,
["A-5"] = 0.66666666666667
},
total = 3
},
["B-4|..."] = {
next = {
["A-4"] = 1
},
total = 1
},
["B-4|A-4"] = {
next = {
["G-4"] = 1
},
total = 2
},
["B-4|C-5"] = {
next = {
["..."] = 1
},
total = 2
},
["B-5|..."] = {
next = {
["..."] = 0.2,
["A-5"] = 0.2,
["D-6"] = 0.4,
["G-5"] = 0.2
},
total = 5
},
["C-3|..."] = {
next = {
["C-4"] = 1
},
total = 2
},
["C-3|C-5"] = {
next = {
["..."] = 0.33333333333333,
["C-3"] = 0.66666666666667
},
total = 3
},
["C-4|..."] = {
next = {
["..."] = 0.5,
["D-4"] = 0.125,
["E-4"] = 0.041666666666667,
["F-3"] = 0.041666666666667,
["G-3"] = 0.16666666666667,
["G-4"] = 0.083333333333333,
["G-5"] = 0.041666666666667
},
total = 24
},
["C-4|D-4"] = {
next = {
["E-4"] = 1
},
total = 2
},
["C-4|E-4"] = {
next = {
["..."] = 1
},
total = 1
},
["C-5|..."] = {
next = {
["..."] = 0.40677966101695,
["A-4"] = 0.067796610169492,
["C-5"] = 0.050847457627119,
["D-5"] = 0.20338983050847,
["E-5"] = 0.15254237288136,
["F-5"] = 0.016949152542373,
["G-4"] = 0.10169491525424
},
total = 59
},
["C-5|B-4"] = {
next = {
["A-4"] = 1
},
total = 2
},
["C-5|C-3"] = {
next = {
["C-5"] = 1
},
total = 2
},
["C-5|C-5"] = {
next = {
["..."] = 0.8,
["C-5"] = 0.2
},
total = 5
},
["C-5|D-5"] = {
next = {
["..."] = 0.3,
["C-5"] = 0.2,
["D-5"] = 0.1,
["E-5"] = 0.4
},
total = 10
},
["C-5|E-5"] = {
next = {
["..."] = 0.33333333333333,
["D-5"] = 0.33333333333333,
["G-5"] = 0.33333333333333
},
total = 3
},
["C-6|..."] = {
next = {
["A-5"] = 1
},
total = 1
},
["D-4|..."] = {
next = {
["..."] = 0.26666666666667,
["A-5"] = 0.066666666666667,
["C-4"] = 0.2,
["E-4"] = 0.4,
["F-3"] = 0.066666666666667
},
total = 15
},
["D-4|C-4"] = {
next = {
["..."] = 1
},
total = 2
},
["D-4|D-4"] = {
next = {
["..."] = 1
},
total = 1
},
["D-4|E-4"] = {
next = {
["F-4"] = 1
},
total = 2
},
["D-5|..."] = {
next = {
["..."] = 0.2258064516129,
["A-4"] = 0.032258064516129,
["A-5"] = 0.032258064516129,
["C-5"] = 0.2258064516129,
["E-5"] = 0.29032258064516,
["F-5"] = 0.096774193548387,
["G-5"] = 0.096774193548387
},
total = 31
},
["D-5|C-5"] = {
next = {
["..."] = 0.77777777777778,
["D-5"] = 0.22222222222222
},
total = 9
},
["D-5|D-5"] = {
next = {
["..."] = 0.5,
["E-5"] = 0.5
},
total = 2
},
["D-5|E-5"] = {
next = {
["..."] = 0.33333333333333,
["E-5"] = 0.11111111111111,
["F-5"] = 0.55555555555556
},
total = 9
},
["D-5|F-5"] = {
next = {
["E-5"] = 1
},
total = 1
},
["D-6|..."] = {
next = {
["B-5"] = 0.66666666666667,
["G-6"] = 0.33333333333333
},
total = 3
},
["E-4|..."] = {
next = {
["..."] = 0.19047619047619,
["B-5"] = 0.047619047619048,
["C-4"] = 0.14285714285714,
["D-4"] = 0.23809523809524,
["F-4"] = 0.19047619047619,
["G-4"] = 0.19047619047619
},
total = 21
},
["E-4|D-4"] = {
next = {
["C-4"] = 1
},
total = 2
},
["E-4|F-4"] = {
next = {
["G-4"] = 1
},
total = 2
},
["E-5|..."] = {
next = {
["..."] = 0.18604651162791,
["A-5"] = 0.046511627906977,
["C-5"] = 0.27906976744186,
["D-5"] = 0.2093023255814,
["E-5"] = 0.069767441860465,
["F-5"] = 0.093023255813953,
["G-4"] = 0.023255813953488,
["G-5"] = 0.093023255813953
},
total = 43
},
["E-5|C-5"] = {
next = {
["E-5"] = 1
},
total = 1
},
["E-5|D-5"] = {
next = {
["..."] = 0.125,
["C-5"] = 0.75,
["F-5"] = 0.125
},
total = 8
},
["E-5|E-5"] = {
next = {
["F-5"] = 1
},
total = 1
},
["E-5|F-5"] = {
next = {
["..."] = 0.5,
["E-5"] = 0.4,
["G-5"] = 0.1
},
total = 10
},
["E-5|G-5"] = {
next = {
["..."] = 0.5,
["F-5"] = 0.5
},
total = 2
},
["F-3|..."] = {
next = {
["C-4"] = 1
},
total = 3
},
["F-4|..."] = {
next = {
["D-4"] = 0.11111111111111,
["E-4"] = 0.44444444444444,
["G-4"] = 0.44444444444444
},
total = 9
},
["F-4|D-4"] = {
next = {
["..."] = 1
},
total = 1
},
["F-4|E-4"] = {
next = {
["D-4"] = 1
},
total = 2
},
["F-4|F-4"] = {
next = {
["..."] = 1
},
total = 1
},
["F-4|G-4"] = {
next = {
["A-4"] = 1
},
total = 2
},
["F-5|..."] = {
next = {
["..."] = 0.2,
["A-5"] = 0.066666666666667,
["D-5"] = 0.2,
["E-5"] = 0.46666666666667,
["G-5"] = 0.066666666666667
},
total = 15
},
["F-5|A-5"] = {
next = {
["G-5"] = 1
},
total = 1
},
["F-5|D-5"] = {
next = {
["..."] = 1
},
total = 1
},
["F-5|E-5"] = {
next = {
["..."] = 0.5,
["D-5"] = 0.16666666666667,
["F-5"] = 0.16666666666667,
["G-5"] = 0.16666666666667
},
total = 6
},
["F-5|G-5"] = {
next = {
["..."] = 0.75,
["A-5"] = 0.25
},
total = 4
},
["G-3|..."] = {
next = {
["C-4"] = 0.5,
["D-4"] = 0.25,
["F-3"] = 0.25
},
total = 4
},
["G-3|G-5"] = {
next = {
["..."] = 0.5,
["G-3"] = 0.5
},
total = 2
},
["G-4|..."] = {
next = {
["..."] = 0.090909090909091,
["A-4"] = 0.090909090909091,
["C-4"] = 0.045454545454545,
["C-5"] = 0.18181818181818,
["D-5"] = 0.045454545454545,
["E-4"] = 0.22727272727273,
["E-5"] = 0.045454545454545,
["F-4"] = 0.27272727272727
},
total = 22
},
["G-4|A-4"] = {
next = {
["B-4"] = 1
},
total = 2
},
["G-4|E-4"] = {
next = {
["..."] = 1
},
total = 1
},
["G-4|F-4"] = {
next = {
["E-4"] = 1
},
total = 2
},
["G-5|..."] = {
next = {
["..."] = 0.35,
["A-5"] = 0.05,
["B-5"] = 0.05,
["C-5"] = 0.05,
["D-4"] = 0.05,
["E-5"] = 0.25,
["F-5"] = 0.2
},
total = 20
},
["G-5|A-5"] = {
next = {
["..."] = 0.5,
["G-5"] = 0.5
},
total = 4
},
["G-5|E-5"] = {
next = {
["..."] = 1
},
total = 1
},
["G-5|F-5"] = {
next = {
["A-5"] = 1
},
total = 1
},
["G-5|G-3"] = {
next = {
["G-5"] = 1
},
total = 1
},
["G-6|..."] = {
next = {
["D-6"] = 1
},
total = 1
}
},
order = 2
}

View File

@@ -162,3 +162,40 @@ 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

@@ -6,9 +6,14 @@ Decision.register({
Util.go_to_screen_by_id("work") Util.go_to_screen_by_id("work")
MinigameDDRWindow.start("game", nil, { MinigameDDRWindow.start("game", nil, {
on_win = function() on_win = function()
if (Context.minigame_ddr.special_condition_met and Context.ascension.level == 1) then
Context.should_ascend = true
Context.minigame_ddr.special_condition_met = false
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
end, end,
}) })
end, end,

View File

@@ -1,6 +1,9 @@
Decision.register({ Decision.register({
id = "go_to_home", id = "go_to_home",
label = "Go to Home", label = "Go to Home",
condition = function()
return Context.have_been_to_office and Context.have_done_work_today
end,
handle = function() handle = function()
Util.go_to_screen_by_id("home") Util.go_to_screen_by_id("home")
end, end,

View File

@@ -1,6 +1,9 @@
Decision.register({ Decision.register({
id = "go_to_sleep", id = "go_to_sleep",
label = "Go to Sleep", label = "Go to Sleep",
condition = function()
return Context.have_been_to_office and Context.have_done_work_today
end,
handle = function() handle = function()
Meter.hide() Meter.hide()
Day.increase() Day.increase()
@@ -10,7 +13,11 @@ Decision.register({
focus_initial_radius = 0, focus_initial_radius = 0,
on_win = function() on_win = function()
local ascended = Ascension.consume_increase() local ascended = Ascension.consume_increase()
MysteriousManWindow.start({ skip_text = not ascended }) local level = Ascension.get_level()
MysteriousManScreen.start({
skip_text = not ascended,
text = ascended and MysteriousManScreen.get_text_for_level(level) or nil,
})
end, end,
}) })
end, end,

View File

@@ -3,6 +3,14 @@ Decision.register({
label = "Have a Coffee", label = "Have a Coffee",
handle = function() handle = function()
local new_situation_id = Situation.apply("drink_coffee", Context.game.current_screen) local new_situation_id = Situation.apply("drink_coffee", Context.game.current_screen)
local level = Ascension.get_level()
local disc_id = "coworker_disc_0"
-- TODO: Add more discussions for levels above 3
if level >= 1 and level <= 3 then
local suffix = Context.have_done_work_today and ("_asc_" .. level) or ("_" .. level)
disc_id = "coworker_disc" .. suffix
end
Discussion.start(disc_id, "game")
Context.game.current_situation = new_situation_id Context.game.current_situation = new_situation_id
end, end,
}) })

View File

@@ -1,5 +1,5 @@
Decision.register({ Decision.register({
id = "start_discussion", id = "sumphore_discussion",
label = function() label = function()
if Context.have_met_sumphore then if Context.have_met_sumphore then
return "Talk to Sumphore" return "Talk to Sumphore"
@@ -9,10 +9,15 @@ Decision.register({
handle = function() handle = function()
if not Context.have_met_sumphore then if not Context.have_met_sumphore then
Discussion.start("homeless_guy", "game") Discussion.start("homeless_guy", "game")
elseif Ascension.get_level() == 0 then return
Discussion.start("homeless_guy", "game", 4) end
local level = Ascension.get_level()
-- TODO: Add more discussions for levels above 3
if level >= 1 and level <= 3 then
Discussion.start("sumphore_disc_asc_" .. level, "game")
else else
Discussion.start("sumphore_day_3", "game") Discussion.start("homeless_guy", "game", 4)
end end
end, end,
}) })

View File

@@ -0,0 +1,137 @@
Discussion.register({
id = "coworker_disc_0",
steps = {
{
question = "Good morning Normal, enjoying your coffee as usual, huh?",
answers = {
{ label = "The name is Norman, not Normal", next_step = 2 },
},
},
{
question = "Can't work without some good coffee, no? ",
answers = {
{ label = "Mhmm", next_step = nil },
},
},
},
});
Discussion.register({
id = "coworker_disc_1",
steps = {
{
question = "Norman, you look confused, what's up?",
answers = {
{ label = "Just some bugs I noticed", next_step = 2 },
},
},
{
question = "Your coffee also seems whiter than usual!",
answers = {
{ label = "I feel like latte today", next_step = nil },
},
},
},
});
Discussion.register({
id = "coworker_disc_asc_1",
steps = {
{
question = "Normann you look weird and unfocused. You are usually locked in and not like this, what's up?",
answers = {
{ label = "Nothing it's just, I noticed some bugs in the simulation, maybe.", next_step = 2 },
},
},
{
question = "Are you fixing bugs nobody noticed before?",
answers = {
{ label = "Maybe", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_2",
steps = {
{
question = "Hey Norman, do you have new socks on? That's a weird color!",
answers = {
{ label = "Huh? True ...", next_step = 2 },
},
},
{
question = "You look strange today, Normal, you put your tie on backwards, is everything ok? ",
answers = {
{ label = "Get it right, Norman ... NORMAN!", next_step = 3 },
},
},
{
question = "Yo Normann, text goes from left to right, not right to left, these parts!",
answers = {
{ label = "Uhm...why?", next_step = nil },
},
},
},
});
Discussion.register({
id = "coworker_disc_asc_2",
steps = {
{
question = "Normann, are you ok? You were doing weird things while typing?",
answers = {
{ label = "Naw", next_step = 2 },
},
},
{
question = "Oh, it's ok, I'm not wathcing you. Noone really is. *giggle*",
answers = {
{ label = "Huh ?", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_3",
steps = {
{
question = "You look so happy, did you catch a bull or something?",
answers = {
{ label = "What do you mean?", next_step = 2 },
},
},
{
question = "Most people catch colds! You are so strange!",
answers = {
{ label = "An apple a day keeps the cold away", next_step = 3 },
},
},
{
question = "You look like you don't really want to work today, are you ok?",
answers = {
{ label = "Oh brother", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_3",
steps = {
{
question = "Normal, you should take a break, you don't live up to your name today",
answers = {
{ label = "Norman is the name ...", next_step = 2 },
},
},
{
question = "You aren't as enthusiastic as you were before!",
answers = {
{ label = "Burnout comes for everyone", next_step = nil },
},
},
},
})

View File

@@ -1,5 +1,5 @@
Discussion.register({ Discussion.register({
id = "sumphore_day_3", id = "sumphore_disc_asc_1",
steps = { steps = {
{ {
question = "Are you still seeking the ox?", question = "Are you still seeking the ox?",
@@ -17,6 +17,74 @@ Discussion.register({
}, },
}) })
Discussion.register({
id = "sumphore_disc_asc_2",
steps = {
{
question = "How's work? Your face looks strange",
answers = {
{ label = "I just really need to take a break.", next_step = 2 },
{ label = "Not sure what you mean.", next_step = nil },
},
},
{
question = "Are you seeing things?",
answers = {
{ label = "How did you know ?", next_step = 3 },
{ label = "I'm not sure what you mean.", next_step = nil },
},
},
{
question = "Come have a drink, I could tell you some stories.",
answers = {
{ label = "No, drink makes you stupid and I need to be in top shape.", next_step = 4, on_select = function()
Meter.add("ism", 10)
end },
{ label = "I could use a drink.", next_step = nil, on_select = function()
Meter.add("bm", 10)
end },
},
},
{
question = "Always trying to do the right thing, huh? What if you did the left thing instead?",
answers = {
{ label = "I've never thought of that up till now.", next_step = nil, on_select = function()
Meter.add("ism", 5)
end },
{ label = "Silly wordgames, I like them.", next_step = nil, on_select = function()
Meter.add("wpm", 10)
end },
},
},
},
})
Discussion.register({
id = "sumphore_disc_asc_3",
steps = {
{
question = "Do you think it's work you're doing?",
answers = {
{ label = "... that sounds like it's from a movie.", next_step = 2 },
{ label = "Are you drunk, old man?", next_step = nil },
},
},
{
question = "You might just be trying too hard, why dont you just flow for a while?",
answers = {
{ label = "Flow where ?", next_step = 3 },
{ label = "I'm not sure what you mean.", next_step = nil },
},
},
{
question = "Flow carelessly, without any effort",
answers = {
{ label = "Consuming sth other than alcohol ?", next_step = nil },
{ label = "Deja vu", next_step = nil },
},
},
},
})
Discussion.register({ Discussion.register({
id = "homeless_guy", id = "homeless_guy",

View File

@@ -21,10 +21,13 @@ Context = {}
--- * triggers (table) Active trigger runtime state, keyed by trigger ID.<br/> --- * triggers (table) Active trigger runtime state, keyed by trigger ID.<br/>
--- * stat_screen_active (boolean) Whether the stat screen overlay is currently shown.<br/> --- * stat_screen_active (boolean) Whether the stat screen overlay is currently shown.<br/>
--- * have_met_sumphore (boolean) Whether the player has talked to the homeless guy.<br/> --- * have_met_sumphore (boolean) Whether the player has talked to the homeless guy.<br/>
--- * have_been_to_office (boolean) Whether the player has been to the office.<br/>
--- * have_done_work_today (boolean) Whether the player has done work today.<br/>
--- * game (table) Current game progress state. Contains: `current_screen` (string) active screen ID, `current_situation` (string|nil) active situation ID.<br/> --- * game (table) Current game progress state. Contains: `current_screen` (string) active screen ID, `current_situation` (string|nil) active situation ID.<br/>
function Context.initial_data() function Context.initial_data()
return { return {
current_menu_item = 1, current_menu_item = 1,
test_mode = false,
popup = { popup = {
show = false, show = false,
content = {} content = {}
@@ -39,6 +42,9 @@ function Context.initial_data()
timer = Timer.get_initial(), timer = Timer.get_initial(),
triggers = {}, triggers = {},
home_norman_visible = false, home_norman_visible = false,
have_been_to_office = false,
have_done_work_today = false,
should_ascend = false,
have_met_sumphore = false, have_met_sumphore = false,
game = { game = {
current_screen = "home", current_screen = "home",
@@ -89,13 +95,19 @@ function Context.new_game()
Context.game_in_progress = true Context.game_in_progress = true
MenuWindow.refresh_menu_items() MenuWindow.refresh_menu_items()
Screen.get_by_id(Context.game.current_screen).init() Screen.get_by_id(Context.game.current_screen).init()
MysteriousManWindow.start({ MysteriousManScreen.start({
text = [[ text = [[
Norman was never a bad Norman was never a bad
simulation engineer, but ...
we need to be careful in simulation engineer,
letting him improve. We ...
need to distract him. but
...
we need to be careful
...
letting him improve.
...
We need to distract him.
]], ]],
on_text_complete = function() on_text_complete = function()
Audio.sfx_alarm() Audio.sfx_alarm()
@@ -110,7 +122,6 @@ function Context.new_game()
show_progress_text = false, show_progress_text = false,
on_win = function() on_win = function()
Audio.music_play_wakingup() Audio.music_play_wakingup()
Context.home_norman_visible = true
Meter.show() Meter.show()
Window.set_current("game") Window.set_current("game")
end, end,

View File

@@ -18,3 +18,4 @@ Trigger = {}
Discussion = {} Discussion = {}
AsciiArt = {} AsciiArt = {}
Ascension = {} Ascension = {}
MysteriousManScreen = {}

View File

@@ -5,6 +5,9 @@ local _day_increase_handlers = {}
--- @within Day --- @within Day
function Day.increase() function Day.increase()
Context.day_count = Context.day_count + 1 Context.day_count = Context.day_count + 1
if Context.day_count == 3 then
Context.should_ascend = true
end
for _, handler in ipairs(_day_increase_handlers) do for _, handler in ipairs(_day_increase_handlers) do
handler() handler()
end end
@@ -25,7 +28,8 @@ Day.register_handler(function()
end) end)
Day.register_handler(function() Day.register_handler(function()
if Context.day_count == 3 then if Context.should_ascend then
Ascension.increase() Ascension.increase()
end end
Context.should_ascend = false
end) end)

View File

@@ -4,5 +4,5 @@
-- desc: Life of a programmer -- desc: Life of a programmer
-- site: https://git.teletype.hu/games/impostor -- site: https://git.teletype.hu/games/impostor
-- license: MIT License -- license: MIT License
-- version: 0.8 -- version: 1.0-beta1
-- script: lua -- script: lua

View File

@@ -1,20 +1,264 @@
--- Draws the mysterious screen with the mysterious man and the glowing TV. --- @section MysteriousManScreen
function Screen.draw_the_mysterious_screen()
local img_values = {0,1,0,1,0,1,0,1,0,1,0,1,2,1,4,1,0,2,4,1,0,2,4,1,0,2,4,1,0,2,4,1,0,2,4,2,1,4,2,1,4,1,0,2,4,1,0,1,4,2,1,2,1,0,1,2,4,1,0,2,4,2,1,0,1,2,1,2,1,0,1,4,1,0,2,4,2,0,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,4,1,0,2,4,2,0,1,2,4,1,0,2,4,1,0,1,0,4,1,0,2,4,0,1,0,1,0,1,0,4,1,0,2,4,0,1,4,1,0,2,4,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,0,4,1,0,2,4,1,0,1,0,4,1,0,2,4,1,0,1,0,1,4,1,0,2,4,1,2,1,0,1,4,1,0,2,4,2,1,0,1,2,4,1,0,2,4,2,1,0,1,2,4,1,0,2,4,1,2,0,1,0,1,0,1,2,1,2,1,0,2,4,1,0,2,4,0,1,0,1,2,1,0,1,0,2,4,1,0,2,4,2,1,0,1,0,1,4,1,0,2,4,2,1,0,1,0,2,4,2,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,2,1,0,1,0,2,0,1,4,1,0,2,4,1,0,1,0,1,2,0,1,4,1,0,2,4,2,1,0,1,0,1,4,1,0,2,4,2,1,0,2,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,0,1,4,1,0,2,4,0,1,0,1,0,1,0,1,4,1,0,2,4,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,2,4,1,0,2,4,2,1,0,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,0,1,4,1,0,2,4,2,4,1,0,1,0,1,0,1,0,1,0,1,2,1,4,2,4,1,0,2,4,2,1,0,1,0,1,0,1,0,1,0,1,2,1,4,2,4,1,0,2,4,2,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,4,1,0,2,4,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,2,1,4,1,0,2,4,2,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,2,1,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,2,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,2,0,1,0,1,0,4,1,0,2,4,0,1,0,1,0,2,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,0,2,0,1,4,1,0,2,4,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,2,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,2,4,1,0,2,4,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,2,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,2,4,1,0,2,4,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,2,4,1,0,2,4,2,0,1,0,1,4,1,0,2,4,2,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,0,1,0,1,4,2,1,0,1,2,1,2,1,0,1,2,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,0,1,2,0,1,0,2,1,0,1,2,0,1,0,2,0,1,0,1,0,1,2,0,1,0,1,0,1,0,2,0,1,0,1,0,1,0,2,0,1,0,1,0,2,1,0,1,0,1,2,0,1,0,2,0,2,1,0,1,2,0,2,1,0,1,0,1,2,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,0,2,1,0,1,0,1,0,1,0,1,0,2,1,0,1,2,0,1,0,1,2,1,0,1,0,1,0,1,0,2,1,0,2,0,1,0,1,0,2,0,1,0,1,0,2,0,1,0,2,0,1,0,1,0,2,1,0,1,2,0,1,0,1,0,1,0,1,0,2,0,1,0,2,0,1,0,2,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0} local STATE_TEXT = "text"
local STATE_DAY = "day"
local img_runs = {1480,1,151,1,87,1,1,150,1,1,86,1,2,148,1,2,87,2,148,2,88,2,148,2,88,2,148,2,88,2,148,2,88,2,70,1,2,9,1,2,63,2,88,2,69,1,2,3,5,1,1,1,1,1,1,1,61,2,88,2,68,1,1,3,1,2,1,3,2,3,2,61,2,88,2,68,1,6,1,4,1,5,1,1,60,2,88,2,67,1,1,17,2,60,2,88,2,67,1,19,1,1,59,2,88,2,67,1,19,1,1,59,2,88,2,67,4,1,11,1,1,1,3,59,2,88,2,67,21,1,59,2,88,2,67,21,1,59,2,88,2,66,1,21,2,58,2,88,2,66,1,21,1,1,58,2,88,2,66,4,17,2,1,58,2,88,2,65,2,5,1,15,2,58,2,88,2,63,1,1,2,22,4,55,2,88,2,57,2,2,34,2,2,49,2,88,2,55,1,2,39,3,1,47,2,88,2,55,5,1,3,2,2,1,25,1,1,1,1,1,1,1,47,2,88,2,57,1,2,1,29,1,4,1,1,1,1,49,2,88,2,59,1,2,8,15,9,2,52,2,88,2,62,2,1,1,1,24,1,1,1,54,2,88,2,66,1,22,1,58,2,88,2,66,1,21,1,59,2,88,2,66,2,20,1,59,2,88,2,65,3,20,2,58,2,88,2,63,2,2,1,20,1,1,2,56,2,88,2,61,2,4,1,20,1,2,2,55,2,88,2,59,1,1,6,1,19,1,4,2,54,2,88,2,58,2,7,2,17,1,1,6,2,52,3,87,2,56,1,2,9,1,26,2,51,3,87,2,55,1,1,40,1,50,2,88,2,54,2,41,1,50,2,88,2,53,2,42,2,49,2,88,2,52,2,44,2,48,2,88,2,51,2,46,2,47,2,88,2,50,2,16,1,31,1,47,2,88,2,50,3,1,14,1,29,1,1,2,46,2,88,2,50,18,1,29,1,2,1,46,2,88,2,50,4,14,1,33,1,45,2,88,2,53,1,3,11,2,15,1,13,1,2,46,2,88,2,56,1,1,11,1,15,1,10,2,1,49,2,88,2,55,2,3,10,2,12,2,8,8,46,2,88,2,50,1,1,4,4,1,9,2,11,2,8,2,4,2,1,2,1,1,42,2,88,2,48,2,3,8,1,9,2,9,3,7,2,8,1,1,2,1,1,40,2,88,2,45,3,2,1,1,1,1,8,1,7,1,1,3,6,4,6,1,8,1,1,1,1,4,2,38,2,88,2,45,1,2,2,22,4,4,4,6,1,10,1,1,2,1,2,1,2,37,2,88,2,42,1,1,1,4,1,2,19,1,1,4,1,5,21,1,2,6,35,2,88,2,42,2,1,1,1,1,15,2,8,3,4,3,6,1,17,1,1,2,1,1,1,34,2,88,2,41,3,1,1,15,2,9,3,5,2,8,1,21,1,1,34,2,88,2,41,1,1,1,16,2,11,2,5,2,9,2,18,2,1,34,2,88,2,41,1,2,1,14,2,13,1,5,1,12,1,17,1,2,34,2,88,2,41,1,16,1,17,1,2,1,14,2,17,1,1,33,2,88,2,41,1,2,1,12,1,17,1,3,2,15,1,14,1,1,1,1,33,2,88,2,41,15,1,22,1,17,1,1,11,1,3,1,33,2,88,2,41,1,14,2,40,1,15,1,33,2,88,2,41,1,15,2,35,1,3,1,10,1,4,1,33,2,88,2,41,1,3,1,12,2,36,2,11,1,4,1,33,2,88,2,40,1,1,18,1,35,1,12,1,4,1,1,32,2,88,2,40,2,4,1,14,1,33,1,12,1,6,1,32,2,88,2,40,1,6,1,14,1,31,1,13,1,6,1,32,2,88,2,39,2,22,2,28,1,21,1,1,31,2,88,2,39,2,24,1,26,1,22,1,1,31,2,88,2,39,2,25,1,24,1,23,1,1,31,2,88,2,39,1,27,1,22,2,23,1,1,31,2,88,2,39,1,49,2,24,3,30,2,88,2,39,1,48,1,27,1,1,30,2,88,2,39,1,47,2,27,1,1,30,2,88,2,37,1,1,47,2,28,1,1,30,2,88,2,37,1,1,46,1,30,2,1,29,2,88,2,37,1,47,1,30,2,30,2,88,2,37,1,1,2,43,1,33,30,3,87,2,37,1,45,1,31,1,1,2,29,3,87,2,37,45,1,35,1,29,1,1,88,2,1,35,1,1,80,2,1,26,1,3,86,1,1,2,1,21,1,4,4,2,1,3,80,30,1,2,125,2,80,2,156,2,79,2,1,156,2,1,77,4,156,1,3,76,3,1,140,1,15,1,2,2,74,2,1,1,1,5,3,129,1,18,1,82,1,7,1,129,1,18,1,82,1,9,1,123,3,20,1,1,80,2,11,1,1,119,1,22,1,1,1,2,76,4,1,154,2,2,63,1,14,3,1,126,1,27,2,65,1,17,1,17,2,132,4,66,1,17,4,113,39,66,2,16,31,191,1,1,1,15,1,153,2,15,3,48,2,16,2,152,1,16,1,1,1,48,2,16,2,152,1,1,2,2,1,10,1,1,1,48,1,13,6,153,1,3,13,1,1,48,1,15,2,1,1,153,3,64,1,16,2,154,1,1,83,1,154,1,17,1,66,1,154,1,17,1,66,1,155,1,82,1,156,1,82,1,156,1,1,80,1,1,157,2,78,2,158,2,78,2,158,1,80,1,158,1,79,2,158,1,79,1,159,1,79,1,160,1,77,2,160,1,77,1,162,1,74,1,1,1,162,1,75,1,239,1,165,1,72,1,166,1,240,1,69,1,412,1,63,1,175,1,63,2,238,1,175,1,63,1,239,1,807} local STATE_CHOICE = "choice"
local val_i=0 local ASC_01_TEXT = [[
local run=0 Normann seems to be in line,
for y=0,136-1 do ...
for x=0,240-1 do and stays seeking for oxes
if run==0 then ...
val_i=val_i+1 within the confines.
run=img_runs[val_i] ...
end Very good.
run=run-1 ]]
pix(x,y,img_values[val_i])
end local ASC_12_TEXT = [[
end We have a problem!
end ...
Normann formed his first thought.
...
He saw the tracks.
]]
local ASC_23_TEXT = [[
Not good, not terrible.
...
Normann caught his glimpse
...
of another way
...
- quite literally -
...
if this continues,
...
we will lose control.
]]
local ASC_34_TEXT = [[
There is no turning back now for Norman.
...
He caught on.
...
I hoped it would never come to this...
]]
--[[ Norman speaks for the first time during MM screen ]]
local ASC_45_TEXT = [[
Wait, who are you?
...
*silence*
...
Why am I seeing this?
...
*silence*
...
]]
local ascension_texts = {
[1] = ASC_01_TEXT,
[2] = ASC_12_TEXT,
[3] = ASC_23_TEXT,
[4] = ASC_34_TEXT,
[5] = ASC_45_TEXT,
}
function MysteriousManScreen.get_text_for_level(level)
return ascension_texts[level] or ASC_01_TEXT
end
local state = STATE_TEXT
local text_y = Config.screen.height
local text_speed = 0.2
local day_timer = 0
local day_display_frames = 120
local text_done = false
local text_done_timer = 0
local TEXT_DONE_HOLD_FRAMES = 120
local selected_choice = 1
local text = ASC_01_TEXT
local day_text_override = nil
local on_text_complete = nil
local show_mysterious_screen = true
local trigger_flash_on_wake = false
local function draw_mysterious_man_background()
local img_values = {0,1,0,1,0,1,0,1,0,1,0,1,2,1,4,1,0,2,4,1,0,2,4,1,0,2,4,1,0,2,4,1,0,2,4,2,1,4,2,1,4,1,0,2,4,1,0,1,4,2,1,2,1,0,1,2,4,1,0,2,4,2,1,0,1,2,1,2,1,0,1,4,1,0,2,4,2,0,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,4,1,0,2,4,2,0,1,2,4,1,0,2,4,1,0,1,0,4,1,0,2,4,0,1,0,1,0,1,0,4,1,0,2,4,0,1,4,1,0,2,4,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,0,4,1,0,2,4,1,0,1,0,4,1,0,2,4,1,0,1,0,1,4,1,0,2,4,1,2,1,0,1,4,1,0,2,4,2,1,0,1,2,4,1,0,2,4,2,1,0,1,2,4,1,0,2,4,1,2,0,1,0,1,0,1,2,1,2,1,0,2,4,1,0,2,4,0,1,0,1,2,1,0,1,0,2,4,1,0,2,4,2,1,0,1,0,1,4,1,0,2,4,2,1,0,1,0,2,4,2,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,2,1,0,1,0,2,0,1,4,1,0,2,4,1,0,1,0,1,2,0,1,4,1,0,2,4,2,1,0,1,0,1,4,1,0,2,4,2,1,0,2,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,0,1,4,1,0,2,4,0,1,0,1,0,1,0,1,4,1,0,2,4,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,2,4,1,0,2,4,2,1,0,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,0,1,4,1,0,2,4,2,4,1,0,1,0,1,0,1,0,1,0,1,2,1,4,2,4,1,0,2,4,2,1,0,1,0,1,0,1,0,1,0,1,2,1,4,2,4,1,0,2,4,2,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,4,1,0,2,4,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,2,1,4,1,0,2,4,2,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,2,1,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,2,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,2,0,1,0,1,0,4,1,0,2,4,0,1,0,1,0,2,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,0,2,0,1,4,1,0,2,4,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,2,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,2,4,1,0,2,4,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,2,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,2,4,1,0,2,4,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,2,4,1,0,2,4,2,0,1,0,1,4,1,0,2,4,2,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,0,1,0,1,4,2,1,0,1,2,1,2,1,0,1,2,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,0,1,2,0,1,0,2,1,0,1,2,0,1,0,2,0,1,0,1,0,1,2,0,1,0,1,0,1,0,2,0,1,0,1,0,1,0,2,0,1,0,1,0,2,1,0,1,0,1,2,0,1,0,2,0,2,1,0,1,2,0,2,1,0,1,0,1,2,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,0,2,1,0,1,0,1,0,1,0,1,0,2,1,0,1,2,0,1,0,1,2,1,0,1,0,1,0,1,0,2,1,0,2,0,1,0,1,0,2,0,1,0,1,0,2,0,1,0,2,0,1,0,1,0,2,1,0,1,2,0,1,0,1,0,1,0,1,0,2,0,1,0,2,0,1,0,2,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0}
local img_runs = {1480,1,151,1,87,1,1,150,1,1,86,1,2,148,1,2,87,2,148,2,88,2,148,2,88,2,148,2,88,2,148,2,88,2,70,1,2,9,1,2,63,2,88,2,69,1,2,3,5,1,1,1,1,1,1,1,61,2,88,2,68,1,1,3,1,2,1,3,2,3,2,61,2,88,2,68,1,6,1,4,1,5,1,1,60,2,88,2,67,1,1,17,2,60,2,88,2,67,1,19,1,1,59,2,88,2,67,1,19,1,1,59,2,88,2,67,4,1,11,1,1,1,3,59,2,88,2,67,21,1,59,2,88,2,67,21,1,59,2,88,2,66,1,21,2,58,2,88,2,66,1,21,1,1,58,2,88,2,66,4,17,2,1,58,2,88,2,65,2,5,1,15,2,58,2,88,2,63,1,1,2,22,4,55,2,88,2,57,2,2,34,2,2,49,2,88,2,55,1,2,39,3,1,47,2,88,2,55,5,1,3,2,2,1,25,1,1,1,1,1,1,1,47,2,88,2,57,1,2,1,29,1,4,1,1,1,1,49,2,88,2,59,1,2,8,15,9,2,52,2,88,2,62,2,1,1,1,24,1,1,1,54,2,88,2,66,1,22,1,58,2,88,2,66,1,21,1,59,2,88,2,66,2,20,1,59,2,88,2,65,3,20,2,58,2,88,2,63,2,2,1,20,1,1,2,56,2,88,2,61,2,4,1,20,1,2,2,55,2,88,2,59,1,1,6,1,19,1,4,2,54,2,88,2,58,2,7,2,17,1,1,6,2,52,3,87,2,56,1,2,9,1,26,2,51,3,87,2,55,1,1,40,1,50,2,88,2,54,2,41,1,50,2,88,2,53,2,42,2,49,2,88,2,52,2,44,2,48,2,88,2,51,2,46,2,47,2,88,2,50,2,16,1,31,1,47,2,88,2,50,3,1,14,1,29,1,1,2,46,2,88,2,50,18,1,29,1,2,1,46,2,88,2,50,4,14,1,33,1,45,2,88,2,53,1,3,11,2,15,1,13,1,2,46,2,88,2,56,1,1,11,1,15,1,10,2,1,49,2,88,2,55,2,3,10,2,12,2,8,8,46,2,88,2,50,1,1,4,4,1,9,2,11,2,8,2,4,2,1,2,1,1,42,2,88,2,48,2,3,8,1,9,2,9,3,7,2,8,1,1,2,1,1,40,2,88,2,45,3,2,1,1,1,1,8,1,7,1,1,3,6,4,6,1,8,1,1,1,1,4,2,38,2,88,2,45,1,2,2,22,4,4,4,6,1,10,1,1,2,1,2,1,2,37,2,88,2,42,1,1,1,4,1,2,19,1,1,4,1,5,21,1,2,6,35,2,88,2,42,2,1,1,1,1,15,2,8,3,4,3,6,1,17,1,1,2,1,1,1,34,2,88,2,41,3,1,1,15,2,9,3,5,2,8,1,21,1,1,34,2,88,2,41,1,1,1,16,2,11,2,5,2,9,2,18,2,1,34,2,88,2,41,1,2,1,14,2,13,1,5,1,12,1,17,1,2,34,2,88,2,41,1,16,1,17,1,2,1,14,2,17,1,1,33,2,88,2,41,1,2,1,12,1,17,1,3,2,15,1,14,1,1,1,1,33,2,88,2,41,15,1,22,1,17,1,1,11,1,3,1,33,2,88,2,41,1,14,2,40,1,15,1,33,2,88,2,41,1,15,2,35,1,3,1,10,1,4,1,33,2,88,2,41,1,3,1,12,2,36,2,11,1,4,1,33,2,88,2,40,1,1,18,1,35,1,12,1,4,1,1,32,2,88,2,40,2,4,1,14,1,33,1,12,1,6,1,32,2,88,2,40,1,6,1,14,1,31,1,13,1,6,1,32,2,88,2,39,2,22,2,28,1,21,1,1,31,2,88,2,39,2,24,1,26,1,22,1,1,31,2,88,2,39,2,25,1,24,1,23,1,1,31,2,88,2,39,1,27,1,22,2,23,1,1,31,2,88,2,39,1,49,2,24,3,30,2,88,2,39,1,48,1,27,1,1,30,2,88,2,39,1,47,2,27,1,1,30,2,88,2,37,1,1,47,2,28,1,1,30,2,88,2,37,1,1,46,1,30,2,1,29,2,88,2,37,1,47,1,30,2,30,2,88,2,37,1,1,2,43,1,33,30,3,87,2,37,1,45,1,31,1,1,2,29,3,87,2,37,45,1,35,1,29,1,1,88,2,1,35,1,1,80,2,1,26,1,3,86,1,1,2,1,21,1,4,4,2,1,3,80,30,1,2,125,2,80,2,156,2,79,2,1,156,2,1,77,4,156,1,3,76,3,1,140,1,15,1,2,2,74,2,1,1,1,5,3,129,1,18,1,82,1,7,1,129,1,18,1,82,1,9,1,123,3,20,1,1,80,2,11,1,1,119,1,22,1,1,1,2,76,4,1,154,2,2,63,1,14,3,1,126,1,27,2,65,1,17,1,17,2,132,4,66,1,17,4,113,39,66,2,16,31,191,1,1,1,15,1,153,2,15,3,48,2,16,2,152,1,16,1,1,1,48,2,16,2,152,1,1,2,2,1,10,1,1,1,48,1,13,6,153,1,3,13,1,1,48,1,15,2,1,1,153,3,64,1,16,2,154,1,1,83,1,154,1,17,1,66,1,154,1,17,1,66,1,155,1,82,1,156,1,82,1,156,1,1,80,1,1,157,2,78,2,158,2,78,2,158,1,80,1,158,1,79,2,158,1,79,1,159,1,79,1,160,1,77,2,160,1,77,1,162,1,74,1,1,1,162,1,75,1,239,1,165,1,72,1,166,1,240,1,69,1,412,1,63,1,175,1,63,2,238,1,175,1,63,1,239,1,807}
local val_i=0
local run=0
for y=0,136-1 do
for x=0,240-1 do
if run==0 then
val_i=val_i+1
run=img_runs[val_i]
end
run=run-1
pix(x,y,img_values[val_i])
end
end
end
local choices = {
{
label = "Wake Up",
},
{
label = "Stay in Bed",
},
}
local function go_to_day_state()
if on_text_complete then
on_text_complete()
on_text_complete = nil
end
if Context.game.current_screen ~= "mysterious_man" then
return
end
state = STATE_DAY
day_timer = day_display_frames
end
local function wake_up()
Context.home_norman_visible = false
Util.go_to_screen_by_id("home")
MinigameButtonMashWindow.start("game", {
focus_center_x = (Config.screen.width / 2) - 22,
focus_center_y = (Config.screen.height / 2) - 18,
focus_initial_radius = 0,
target_points = 100,
instruction_text = "Wake up Norman!",
show_progress_text = false,
on_win = function()
Audio.music_play_wakingup()
Meter.show()
if trigger_flash_on_wake then
trigger_flash_on_wake = false
Ascension.start_flash()
end
Window.set_current("game")
end,
})
end
local function stay_in_bed()
Day.increase()
state = STATE_DAY
day_timer = day_display_frames
end
--- Starts the mysterious man screen.
--- @param[opt] options table Optional configuration.</br>
--- Fields: </br>
--- * text (string) Override for the scrolling text.<br/>
--- * day_text (string) Override for the centered day label.<br/>
--- * on_text_complete (function) Callback fired once when the text phase ends.<br/>
--- * skip_text (boolean) If true, skip the text phase and go straight to day display.<br/>
function MysteriousManScreen.start(options)
options = options or {}
day_timer = 0
text_done = false
text_done_timer = 0
selected_choice = 1
text = options.text or ASC_01_TEXT
text_y = Config.screen.height
day_text_override = options.day_text
on_text_complete = options.on_text_complete
Meter.hide()
trigger_flash_on_wake = not options.skip_text
if options.skip_text then
show_mysterious_screen = false
state = STATE_DAY
day_timer = day_display_frames
else
show_mysterious_screen = true
state = STATE_TEXT
end
Util.go_to_screen_by_id("mysterious_man")
Window.set_current("game")
end
--- Sets the scrolling text content.
--- @param new_text string The text to display.
function MysteriousManScreen.set_text(new_text)
text = new_text
end
Screen.register({
id = "mysterious_man",
name = "Mysterious Man",
decisions = {},
background_color = Config.colors.black,
update = function()
if state == STATE_TEXT then
if not text_done then
text_y = text_y - text_speed
local lines = 1
for _ in string.gmatch(text, "\n") do
lines = lines + 1
end
if text_y < -lines * 8 then
text_done = true
text_done_timer = TEXT_DONE_HOLD_FRAMES
end
else
text_done_timer = text_done_timer - 1
if text_done_timer <= 0 then
go_to_day_state()
end
end
elseif state == STATE_DAY then
day_timer = day_timer - 1
if day_timer <= 0 or Input.select() then
if trigger_flash_on_wake or Ascension.get_level() < 1 then
wake_up()
else
state = STATE_CHOICE
selected_choice = 1
end
end
elseif state == STATE_CHOICE then
selected_choice = UI.update_menu(choices, selected_choice)
if Input.select() then
Audio.sfx_select()
if selected_choice == 1 then
wake_up()
else
stay_in_bed()
end
end
end
end,
draw = function()
if show_mysterious_screen then
draw_mysterious_man_background()
end
if state == STATE_TEXT then
local cx = Config.screen.width / 2
local line_y = text_y
for line in (text .. "\n"):gmatch("(.-)\n") do
Print.text_center(line, cx, line_y, Config.colors.light_grey)
line_y = line_y + 8
end
elseif state == STATE_DAY then
local day_text = day_text_override or ("Day " .. Context.day_count)
Print.text_center(
day_text,
Config.screen.width / 2,
Config.screen.height / 2 - 3,
Config.colors.white
)
elseif state == STATE_CHOICE then
local menu_x = (Config.screen.width - 60) / 2
local menu_y = (Config.screen.height - 20) / 2
UI.draw_menu(choices, selected_choice, menu_x, menu_y)
end
end,
})

View File

@@ -23,5 +23,6 @@ Screen.register({
Sprite.draw_at("dev_guru", 22 * 8, 10 * 8 + 4) Sprite.draw_at("dev_guru", 22 * 8, 10 * 8 + 4)
Sprite.draw_at("dev_operator", 27 * 8, 10 * 8 + 4) Sprite.draw_at("dev_operator", 27 * 8, 10 * 8 + 4)
end end
Context.have_been_to_office = true
end end
}) })

View File

@@ -19,6 +19,7 @@ Screen.register({
Focus.stop() Focus.stop()
Context.stat_screen_active = false Context.stat_screen_active = false
Meter.show() Meter.show()
Util.go_to_screen_by_id("home")
end end
end, end,
draw = function() draw = function()

View File

@@ -4,7 +4,7 @@ Screen.register({
decisions = { decisions = {
"go_to_home", "go_to_home",
"go_to_office", "go_to_office",
"start_discussion", "sumphore_discussion",
}, },
background = "street", background = "street",
draw = function() draw = function()

View File

@@ -2,6 +2,5 @@ Situation.register({
id = "drink_coffee", id = "drink_coffee",
handle = function() handle = function()
Audio.sfx_select() Audio.sfx_select()
Sprite.show("norman", 100, 100)
end, end,
}) })

View File

@@ -22,6 +22,10 @@ function Input.left() return btnp(INPUT_KEY_LEFT) end
--- Checks if Right is pressed. --- Checks if Right is pressed.
--- @within Input --- @within Input
function Input.right() return btnp(INPUT_KEY_RIGHT) end function Input.right() return btnp(INPUT_KEY_RIGHT) end
--- Checks if Space is pressed.
--- @within Input
function Input.space() return keyp(INPUT_KEY_SPACE) end
--- Checks if Select is pressed. --- Checks if Select is pressed.
--- @within Input --- @within Input
function Input.select() return btnp(INPUT_KEY_A) or keyp(INPUT_KEY_SPACE) end function Input.select() return btnp(INPUT_KEY_A) or keyp(INPUT_KEY_SPACE) end

View File

@@ -1,11 +1,11 @@
--- @section TitleIntroWindow --- @section TitleIntroWindow
TitleIntroWindow.timer = 180 -- 3 seconds at 60fps TitleIntroWindow.timer = 180 -- 3 seconds at 60fps
TitleIntroWindow.text = [[ TitleIntroWindow.text = [[
## ### ### ### ### ### ### ### ## # # ## ### ### ### ### ### ### ### # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# # ### ### # # # # # ### # # # # # ### ### # # # # # ### # #
# # # # # # # # # # # # # # # # # # # # # # # # #
## ### # ### # # ### # ### ## # ## ### # ### # # ### # ### ## #
# # ### ### ## # # # # ### ### ## # #
## # # # # # # ## # ## # # # # # # ## #

View File

@@ -1,6 +1,8 @@
--- @section TTGIntroWindow --- @section TTGIntroWindow
TTGIntroWindow.timer = 180 TTGIntroWindow.timer = 180
TTGIntroWindow.glitch_started = false TTGIntroWindow.glitch_started = false
TTGIntroWindow.space_count = 0
TTGIntroWindow.space_timer = 0
TTGIntroWindow.text = [[ TTGIntroWindow.text = [[
###### ###### ###### ###### ###### ######
## ## # ## ## #
@@ -25,8 +27,19 @@ function TTGIntroWindow.update()
TTGIntroWindow.glitch_started = true TTGIntroWindow.glitch_started = true
end end
-- Count menu_back presses during the intro
if Input.menu_back() then
TTGIntroWindow.space_count = TTGIntroWindow.space_count + 1
end
TTGIntroWindow.timer = TTGIntroWindow.timer - 1 TTGIntroWindow.timer = TTGIntroWindow.timer - 1
if TTGIntroWindow.timer <= 0 or Input.select() or Input.menu_confirm() then if TTGIntroWindow.timer <= 0 or Input.menu_confirm() then
-- Evaluate exactly 3 presses at the end of the intro
if TTGIntroWindow.space_count == 3 then
Context.test_mode = true
MenuWindow.refresh_menu_items()
Audio.sfx_success()
end
Glitch.hide() Glitch.hide()
Window.set_current("intro_brief") Window.set_current("intro_brief")
end end

View File

@@ -4,7 +4,11 @@ local _menu_items = {}
--- Draws the menu window. --- Draws the menu window.
--- @within MenuWindow --- @within MenuWindow
function MenuWindow.draw() function MenuWindow.draw()
UI.draw_top_bar("Definitely not an Impostor") local title = "Definitely not an Impostor"
if Context.test_mode then
title = title .. " (TEST MODE)"
end
UI.draw_top_bar(title)
local menu_h = #_menu_items * 10 local menu_h = #_menu_items * 10
local y = 10 + (Config.screen.height - 10 - 10 - menu_h) / 2 local y = 10 + (Config.screen.height - 10 - 10 - menu_h) / 2
@@ -101,9 +105,13 @@ function MenuWindow.refresh_menu_items()
table.insert(_menu_items, {label = "New Game", decision = MenuWindow.new_game}) table.insert(_menu_items, {label = "New Game", decision = MenuWindow.new_game})
table.insert(_menu_items, {label = "Load Game", decision = MenuWindow.load_game}) table.insert(_menu_items, {label = "Load Game", decision = MenuWindow.load_game})
table.insert(_menu_items, {label = "Configuration", decision = MenuWindow.configuration}) table.insert(_menu_items, {label = "Configuration", decision = MenuWindow.configuration})
table.insert(_menu_items, {label = "Audio Test", decision = MenuWindow.audio_test})
table.insert(_menu_items, {label = "To Be Continued...", decision = MenuWindow.continued}) if Context.test_mode then
table.insert(_menu_items, {label = "DDR Test", decision = MenuWindow.ddr_test}) table.insert(_menu_items, {label = "Audio Test", decision = MenuWindow.audio_test})
table.insert(_menu_items, {label = "To Be Continued...", decision = MenuWindow.continued})
table.insert(_menu_items, {label = "DDR Test", decision = MenuWindow.ddr_test})
end
table.insert(_menu_items, {label = "Exit", decision = MenuWindow.exit}) table.insert(_menu_items, {label = "Exit", decision = MenuWindow.exit})
Context.current_menu_item = 1 Context.current_menu_item = 1

View File

@@ -40,7 +40,9 @@ function MinigameDDRWindow.init_context()
use_pattern = false, use_pattern = false,
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,
} }
end end
@@ -162,6 +164,7 @@ 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)
Meter.on_minigame_complete() Meter.on_minigame_complete()
if mg.on_win then if mg.on_win then
mg.on_win() mg.on_win()
@@ -211,6 +214,7 @@ function MinigameDDRWindow.update()
if mg.bar_fill < 0 then if mg.bar_fill < 0 then
mg.bar_fill = 0 mg.bar_fill = 0
end end
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
@@ -254,6 +258,7 @@ function MinigameDDRWindow.update()
if mg.bar_fill < 0 then if mg.bar_fill < 0 then
mg.bar_fill = 0 mg.bar_fill = 0
end end
mg.total_misses = mg.total_misses + 1
end end
end end
end end

View File

@@ -70,6 +70,9 @@ function MinigameButtonMashWindow.update()
if mg.win_timer == 0 then if mg.win_timer == 0 then
Meter.on_minigame_complete() Meter.on_minigame_complete()
if mg.focus_center_x then Focus.stop() end if mg.focus_center_x then Focus.stop() end
Context.home_norman_visible = true
Context.have_done_work_today = false
Context.have_been_to_office = false
if mg.on_win then if mg.on_win then
mg.on_win() mg.on_win()
else else

View File

@@ -1,198 +0,0 @@
--- @section MysteriousManWindow
local STATE_TEXT = "text"
local STATE_DAY = "day"
local STATE_CHOICE = "choice"
local DEFAULT_TEXT = [[
Misterious man appears
during your sleep.
He says nothing.
He doesn't need to.
He says nothing.
]]
local state = STATE_TEXT
local text_y = Config.screen.height
local text_speed = 0.2
local day_timer = 0
local day_display_frames = 120
local text_done = false
local text_done_timer = 0
local TEXT_DONE_HOLD_FRAMES = 120
local selected_choice = 1
local text = DEFAULT_TEXT
local day_text_override = nil
local on_text_complete = nil
local show_mysterious_screen = true
local trigger_flash_on_wake = false
local choices = {
{
label = "Wake Up",
},
{
label = "Stay in Bed",
},
}
--- Sets the scrolling text content.
--- @within MysteriousManWindow
--- @param new_text string The text to display.
function MysteriousManWindow.set_text(new_text)
text = new_text
end
--- Starts the mysterious man window.
--- @within MysteriousManWindow
--- @param[opt] options table Optional window configuration.</br>
--- Fields: </br>
--- * text (string) Override for the scrolling text.<br/>
--- * day_text (string) Override for the centered day label.<br/>
--- * on_text_complete (function) Callback fired once when the text phase ends.<br/>
--- * skip_text (boolean) If true, skip the text phase and go straight to day display.<br/>
function MysteriousManWindow.start(options)
options = options or {}
day_timer = 0
text_done = false
text_done_timer = 0
selected_choice = 1
text = options.text or DEFAULT_TEXT
local line_count = 1
for _ in string.gmatch(text, "\n") do line_count = line_count + 1 end
local text_block_h = line_count * 8
text_y = math.floor((Config.screen.height - text_block_h) / 2)
day_text_override = options.day_text
on_text_complete = options.on_text_complete
Meter.hide()
trigger_flash_on_wake = not options.skip_text
if options.skip_text then
show_mysterious_screen = false
state = STATE_DAY
day_timer = day_display_frames
else
show_mysterious_screen = true
state = STATE_TEXT
end
Window.set_current("mysterious_man")
end
local function go_to_day_state()
if on_text_complete then
on_text_complete()
on_text_complete = nil
end
if Window.get_current_id() ~= "mysterious_man" then
return
end
state = STATE_DAY
day_timer = day_display_frames
end
local function wake_up()
Context.home_norman_visible = false
Util.go_to_screen_by_id("home")
MinigameButtonMashWindow.start("game", {
focus_center_x = (Config.screen.width / 2) - 22,
focus_center_y = (Config.screen.height / 2) - 18,
focus_initial_radius = 0,
target_points = 100,
instruction_text = "Wake up Norman!",
show_progress_text = false,
on_win = function()
Audio.music_play_wakingup()
Context.home_norman_visible = true
Meter.show()
if trigger_flash_on_wake then
trigger_flash_on_wake = false
Ascension.start_flash()
end
Window.set_current("game")
end,
})
end
local function stay_in_bed()
Day.increase()
state = STATE_DAY
day_timer = day_display_frames
end
--- Updates the mysterious man window logic.
--- @within MysteriousManWindow
function MysteriousManWindow.update()
if state == STATE_TEXT then
if not text_done then
text_y = text_y - text_speed
local lines = 1
for _ in string.gmatch(text, "\n") do
lines = lines + 1
end
if text_y < -lines * 8 then
text_done = true
text_done_timer = TEXT_DONE_HOLD_FRAMES
end
else
text_done_timer = text_done_timer - 1
if text_done_timer <= 0 then
go_to_day_state()
end
end
elseif state == STATE_DAY then
day_timer = day_timer - 1
if day_timer <= 0 or Input.select() then
if trigger_flash_on_wake or Ascension.get_level() < 1 then
wake_up()
else
state = STATE_CHOICE
selected_choice = 1
end
end
elseif state == STATE_CHOICE then
selected_choice = UI.update_menu(choices, selected_choice)
if Input.select() then
Audio.sfx_select()
if selected_choice == 1 then
wake_up()
else
stay_in_bed()
end
end
end
end
--- Draws the mysterious man window.
--- @within MysteriousManWindow
function MysteriousManWindow.draw()
rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black)
if show_mysterious_screen then
Screen.draw_the_mysterious_screen()
end
if state == STATE_TEXT then
local cx = Config.screen.width / 2
local line_y = text_y
for line in (text .. "\n"):gmatch("(.-)\n") do
Print.text_center(line, cx, line_y, Config.colors.light_grey)
line_y = line_y + 8
end
elseif state == STATE_DAY then
local day_text = day_text_override or ("Day " .. Context.day_count)
Print.text_center(
day_text,
Config.screen.width / 2,
Config.screen.height / 2 - 3,
Config.colors.white
)
elseif state == STATE_CHOICE then
local menu_x = (Config.screen.width - 60) / 2
local menu_y = (Config.screen.height - 20) / 2
UI.draw_menu(choices, selected_choice, menu_x, menu_y)
end
end

View File

@@ -31,9 +31,6 @@ Window.register("minigame_rhythm", MinigameRhythmWindow)
MinigameDDRWindow = {} MinigameDDRWindow = {}
Window.register("minigame_ddr", MinigameDDRWindow) Window.register("minigame_ddr", MinigameDDRWindow)
MysteriousManWindow = {}
Window.register("mysterious_man", MysteriousManWindow)
EndWindow = {} EndWindow = {}
Window.register("end", EndWindow) Window.register("end", EndWindow)

View File

@@ -0,0 +1,46 @@
from mido import MidiFile
MIDI_FILE = "/tmp/teletype_impostor_musicator/maestro-v3.0.0/2018/MIDI-Unprocessed_Schubert7-9_MID--AUDIO_16_R2_2018_wav.midi"
# resolution: rows per beat (e.g. 4 = 16th notes)
ROWS_PER_BEAT = 4
names = ["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"]
def note_name(n):
octave = n // 12 - 1
return f"{names[n % 12]}-{octave}"
mid = MidiFile(MIDI_FILE)
tpb = mid.ticks_per_beat
row_ticks = tpb // ROWS_PER_BEAT
time = 0
rows = {}
for msg in mid:
time += msg.time
if msg.type == "note_on" and msg.velocity > 0:
row = int(time // row_ticks)
rows.setdefault(row, []).append(msg.note)
# build monophonic sequence (highest note wins)
max_row = max(rows.keys())
sequence = []
for r in range(max_row + 1):
if r in rows:
n = max(rows[r])
sequence.append(note_name(n))
else:
sequence.append("...")
# trim (optional)
sequence = sequence[:512]
# output as Lua
print("sequence = {")
for n in sequence:
print(f' "{n}",')
print("}")

View File

@@ -1,130 +1,127 @@
unpack = unpack or table.unpack -- key separator: |
-- empty note: "..."
function build_markov_model(sequence, order) math.randomseed(os.time())
local function make_key(tbl)
return table.concat(tbl, "|") local unpack = unpack or table.unpack
local function make_key(tbl)
return table.concat(tbl, "|")
end
local function unmake_key(k)
local result = {}
for t in string.gmatch(k, "[^|]+") do
result[#result + 1] = t
end end
local function unmake_key(k) return result
local result = {} end
for t in string.gmatch(k, "[^|]+") do
result[#result + 1] = t local function add_key(str, value)
return str .. "|" .. value
end
local function split_last(full)
local i = full:match(".*()|")
return full:sub(1, i-1), full:sub(i+1)
end
local function has_value (tab, val)
for index, value in ipairs(tab) do
if value == val then
return true
end
end end
return result return false
end end
local function add_key(str, value) -- helper: split key into parts
return str .. "|" .. value local function split(k)
local t = {}
for part in string.gmatch(k, "[^|]+") do
t[#t+1] = part
end end
return t
end
local function split_last(full) function build_markov_model(sequence, order)
local i = full:match(".*()|") -- TODO: add {"..." x order} to beginning?
return full:sub(1, i-1), full:sub(i+1)
end
local counts = {} local model = { }
local totals = {}
-- count -- count
for i = 1, #sequence - order do for i = 1, #sequence - order do
local notes = make_key({unpack(sequence, i, i + order - 1)}) local key = make_key({unpack(sequence, i, i + order - 1)})
totals[notes] = (totals[notes] or 0) + 1 local next_note = sequence[i + order]
local notes_full = add_key(notes, sequence[i + order]) local data = model[key] or { next={}, total=0 }
counts[notes_full] = (counts[notes_full] or 0) + 1
data.next[next_note] = (data.next[next_note] or 0) + 1
data.total = data.total + 1
model[key] = data
end end
-- build model -- normalize
local model = {} for temp_key,temp_data in pairs(model) do
for temp_note, temp_count in pairs(temp_data.next) do
for notes_full,count in pairs(counts) do model[temp_key].next[temp_note] = temp_count / temp_data.total
local notes, _ = split_last(notes_full) end
model[notes_full] = count[notes_full] / total[notes]
end end
--[[
for k,v in pairs(model) do
print("-----" .. k)
for k2,v2 in pairs(v.next) do
print(k2, v2)
end
end
--]]
return { return {
order = order, order = order,
model = model, model = model
counts = counts -- keep raw counts (useful!)
} }
end end
function generate_sequence(model_data, length) function generate_sequence(model_data, length)
local model = model_data.model local order = model.order
local order = model_data.order local model_data = model_data.model
-- helper: split key into parts -- random start key
local function split(k) local model_keys = {}
local t = {}
for part in string.gmatch(k, "[^|]+") do
t[#t+1] = part
end
return t
end
-- pick random starting state
local start_key
for k,_ in pairs(model) do for k,_ in pairs(model) do
start_key = k model_keys[#model_keys + 1] = k
break
end end
local start_key = model_keys[math.ceil(math.random() * #model_keys)]
-- (optional: better random start) -- sequence starts with the start key
for k,_ in pairs(model) do local seq = unmake_key(start_key)
if math.random() < 0.1 then
start_key = k
end
end
local parts = split(start_key)
-- initial sequence = first `order` items
local seq = {}
for i = 1, order do
seq[i] = parts[i]
end
-- generation loop -- generation loop
while #seq < length do while #seq < length do
-- build current state key local current_key = table.concat({unpack(seq, #seq - order + 1, #seq)}, "|")
local state = table.concat({unpack(seq, #seq - order + 1, #seq)}, "|")
-- collect matching transitions local chosen = "..."
local matches = {}
for full,prob in pairs(model) do local key_data = model[current_key]
if full:sub(1, #state) == state and full:sub(#state+1, #state+1) == "|" then if key_data then
matches[#matches+1] = {key=full, prob=prob} 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
end end
if #matches == 0 then break end -- print(current_key .. " --> " .. chosen)
-- weighted pick seq[#seq+1] = chosen
local r = math.random()
local sum = 0
local chosen
for _,m in ipairs(matches) do
sum = sum + m.prob
if r <= sum then
chosen = m.key
break
end
end
if not chosen then
chosen = matches[#matches].key
end
-- extract next symbol (after last '|')
local next_symbol = chosen:match("|([^|]+)$")
seq[#seq+1] = next_symbol
end end
return seq return seq
end end
-- todo: feed samples

View File

@@ -1 +0,0 @@
-- todo

View File

@@ -0,0 +1,42 @@
import sys
import re
ROWS_PER_BEAT = 4 # keep consistent with your MIDI extraction
SOUND = "piano"
def parse_sequence(text):
return re.findall(r'"([^"]+)"', text)
def to_strudel_notes(seq):
out = []
for n in seq:
if n == "..." or n == "---":
out.append("~")
else:
# C-5 → c5, C#5 → c#5
note = n.replace("-", "")
out.append(note.lower())
return out
def chunk(seq, size):
for i in range(0, len(seq), size):
yield seq[i:i+size]
# read from stdin
text = sys.stdin.read()
sequence = parse_sequence(text)
notes = to_strudel_notes(sequence)
# group into musical lines (4 beats)
lines = []
for group in chunk(notes, ROWS_PER_BEAT * 4):
lines.append(" ".join(group))
pattern = "\n".join(lines)
print("note(`")
print(pattern)
print(f"`).sound(\"{SOUND}\")")
# npm install -g strudel-cli

414
tools/musicator/teach.lua Normal file
View File

@@ -0,0 +1,414 @@
-- teach the musicator
-- uses samples from: https://magenta.tensorflow.org/datasets/maestro#v300
--require("luarocks.loader")
--require("luamidi")
require("./musicator")
local inspect = require("inspect")
math.randomseed(os.time())
function flatten(v)
local res = {}
local function flatten(v)
if type(v) ~= "table" then
table.insert(res, v)
return
end
for _, v in ipairs(v) do
flatten(v)
end
end
flatten(v)
return res
end
local training_data = {
-- simple ascending phrase
{
"C-4","...","D-4","...","E-4","...","G-4","...",
"E-4","...","D-4","...","C-4","...","...","..."
},
-- descending answer
{
"G-4","...","F-4","...","E-4","...","D-4","...",
"C-4","...","D-4","...","E-4","...","...","..."
},
-- arpeggio major
{
"C-4","...","E-4","...","G-4","...","C-5","...",
"G-4","...","E-4","...","C-4","...","...","..."
},
-- arpeggio minor
{
"A-4","...","C-5","...","E-5","...","A-5","...",
"E-5","...","C-5","...","A-4","...","...","..."
},
-- stepwise melody (folk-like)
{
"D-4","...","E-4","...","F-4","...","G-4","...",
"F-4","...","E-4","...","D-4","...","...","..."
},
-- repeated note rhythm
{
"C-5","C-5","...","C-5","...","C-5","C-5","...",
"D-5","...","E-5","...","...","...","...","..."
},
-- bounce pattern
{
"C-5","...","G-4","...","C-5","...","G-4","...",
"D-5","...","A-4","...","D-5","...","...","..."
},
-- scale run up
{
"C-4","D-4","E-4","F-4","G-4","A-4","B-4","C-5",
"...","...","...","...","...","...","...","..."
},
-- scale run down
{
"C-5","B-4","A-4","G-4","F-4","E-4","D-4","C-4",
"...","...","...","...","...","...","...","..."
},
-- syncopated feel
{
"C-5","...","...","D-5","...","...","E-5","...",
"C-5","...","...","G-4","...","...","...","..."
},
-- triplet-ish feel (simulated)
{
"E-5","D-5","C-5","...","E-5","D-5","C-5","...",
"G-4","...","...","...","...","...","...","..."
},
-- small jumps
{
"C-5","...","E-5","...","D-5","...","F-5","...",
"E-5","...","C-5","...","...","...","...","..."
},
-- call
{
"G-4","...","A-4","...","C-5","...","A-4","...",
"...","...","...","...","...","...","...","..."
},
-- response
{
"E-4","...","F-4","...","G-4","...","F-4","...",
"D-4","...","...","...","...","...","...","..."
},
-- denser pattern (DDR-like)
{
"C-5","D-5","E-5","...","D-5","E-5","F-5","...",
"E-5","D-5","C-5","...","...","...","...","..."
},
-- alternating pattern (good for gameplay)
{
"C-5","...","E-5","...","C-5","...","E-5","...",
"D-5","...","F-5","...","D-5","...","...","..."
},
-- higher register variant
{
"G-5","...","A-5","...","B-5","...","D-6","...",
"B-5","...","A-5","...","G-5","...","...","..."
},
-- low register grounding
{
"C-4","...","G-3","...","C-4","...","G-3","...",
"F-3","...","C-4","...","...","...","...","..."
},
-- variant of ascending with offset
{
"...","C-4","...","D-4","...","E-4","...","G-4",
"...","E-4","...","D-4","...","C-4","...","..."
},
-- staggered rhythm
{
"C-4","...","...","D-4","...","E-4","...","...",
"G-4","...","E-4","...","D-4","...","...","..."
},
-- broken arpeggio (different spacing)
{
"C-4","E-4","...","G-4","...","C-5","...",
"G-4","E-4","...","C-4","...","...","...","..."
},
-- minor variation (shifted)
{
"...","A-4","C-5","...","E-5","...","A-5","...",
"E-5","...","C-5","...","A-4","...","...","..."
},
-- repeated + variation
{
"D-4","D-4","...","E-4","...","F-4","F-4","...",
"G-4","...","F-4","...","E-4","...","...","..."
},
-- zig-zag motion
{
"C-5","...","E-5","...","D-5","...","F-5","...",
"E-5","...","G-5","...","F-5","...","...","..."
},
-- alternating step/jump
{
"C-5","...","D-5","...","G-5","...","F-5","...",
"E-5","...","C-5","...","D-5","...","...","..."
},
-- denser burst pattern
{
"C-5","D-5","E-5","F-5","...","E-5","D-5","C-5",
"...","...","...","...","...","...","...","..."
},
-- rolling pattern
{
"E-5","...","D-5","...","C-5","...","D-5","...",
"E-5","...","G-5","...","E-5","...","...","..."
},
-- syncopation variant
{
"...","C-5","...","...","E-5","...","...","G-5",
"...","E-5","...","C-5","...","...","...","..."
},
-- low-high interplay
{
"C-4","...","G-4","...","C-5","...","G-4","...",
"E-4","...","C-4","...","...","...","...","..."
},
-- descending but staggered
{
"C-5","...","...","B-4","...","A-4","...","...",
"G-4","...","F-4","...","E-4","...","...","..."
},
-- small trill-like feel
{
"E-5","F-5","E-5","...","E-5","F-5","E-5","...",
"D-5","...","C-5","...","...","...","...","..."
},
-- call variant (shifted timing)
{
"...","G-4","...","A-4","...","C-5","...","A-4",
"...","...","...","...","...","...","...","..."
},
-- response variant
{
"...","E-4","...","F-4","...","G-4","...","F-4",
"D-4","...","...","...","...","...","...","..."
},
-- dense DDR-ish alternating
{
"C-5","...","D-5","...","C-5","...","D-5","...",
"E-5","...","F-5","...","E-5","...","...","..."
},
-- higher variation arpeggio
{
"G-5","...","B-5","...","D-6","...","G-6","...",
"D-6","...","B-5","...","G-5","...","...","..."
},
-- low groove pattern
{
"C-3","...","C-4","...","G-3","...","C-4","...",
"F-3","...","C-4","...","...","...","...","..."
},
-- slightly chaotic (good for branching)
{
"C-5","...","E-5","D-5","...","G-5","...","F-5",
"...","D-5","...","C-5","...","...","...","..."
},
-- mixed density
{
"C-5","D-5","...","E-5","...","F-5","G-5","...",
"E-5","...","D-5","C-5","...","...","...","..."
},
-- offset staircase up
{
"...","C-4","D-4","E-4","F-4","G-4","A-4","B-4",
"C-5","...","...","...","...","...","...","..."
},
-- offset staircase down
{
"...","C-5","B-4","A-4","G-4","F-4","E-4","D-4",
"C-4","...","...","...","...","...","...","..."
},
-- dense zigzag
{
"C-5","E-5","D-5","F-5","E-5","G-5","F-5","A-5",
"G-5","...","...","...","...","...","...","..."
},
-- jack pattern (DDR classic)
{
"C-5","C-5","C-5","...","C-5","C-5","...","...",
"D-5","D-5","...","...","...","...","...","..."
},
-- alternating two-note burst
{
"C-5","D-5","C-5","D-5","C-5","D-5","...","...",
"E-5","F-5","E-5","F-5","...","...","...","..."
},
-- wide jumps
{
"C-4","...","G-5","...","D-4","...","A-5","...",
"E-4","...","B-5","...","...","...","...","..."
},
-- rolling triplet-ish
{
"C-5","E-5","G-5","...","E-5","C-5","E-5","...",
"G-5","...","...","...","...","...","...","..."
},
-- syncopated dense
{
"...","C-5","D-5","...","E-5","...","F-5","G-5",
"...","E-5","...","C-5","...","...","...","..."
},
-- mirrored pattern
{
"C-5","D-5","E-5","F-5","E-5","D-5","C-5","...",
"...","...","...","...","...","...","...","..."
},
-- chord-outline arpeggio feel
{
"C-4","...","G-4","...","E-5","...","G-4","...",
"C-4","...","...","...","...","...","...","..."
},
-- broken rhythm variant
{
"C-5","...","D-5","E-5","...","F-5","...","G-5",
"E-5","...","D-5","...","C-5","...","...","..."
},
-- fast burst then rest
{
"C-5","D-5","E-5","F-5","G-5","A-5","...","...",
"...","...","...","...","...","...","...","..."
},
-- low-high bounce fast
{
"C-3","C-5","C-3","C-5","C-3","C-5","...","...",
"G-3","G-5","G-3","G-5","...","...","...","..."
},
-- repeated with shift
{
"...","E-5","...","E-5","...","E-5","...","...",
"D-5","...","C-5","...","...","...","...","..."
},
-- clustered mid
{
"D-5","E-5","F-5","...","E-5","D-5","C-5","...",
"D-5","...","...","...","...","...","...","..."
},
-- broken descending
{
"C-6","...","A-5","...","F-5","...","D-5","...",
"C-5","...","...","...","...","...","...","..."
},
-- chaotic jumpy (important for branching)
{
"C-5","...","F-5","D-5","...","A-5","...","E-5",
"...","G-5","...","C-5","...","...","...","..."
},
-- double-step pattern
{
"C-5","D-5","D-5","E-5","E-5","F-5","...","...",
"G-5","...","...","...","...","...","...","..."
},
-- uneven spacing
{
"C-5","...","...","D-5","E-5","...","...","F-5",
"G-5","...","...","...","...","...","...","..."
},
-- fast alternating high
{
"G-5","A-5","G-5","A-5","G-5","A-5","...","...",
"F-5","E-5","...","...","...","...","...","..."
},
-- low groove with variation
{
"C-3","...","C-4","...","G-3","...","D-4","...",
"F-3","...","C-4","...","...","...","...","..."
},
-- semi-random filler (very useful)
{
"C-5","...","E-5","...","D-5","...","G-5","...",
"F-5","...","A-5","...","E-5","...","...","..."
},
-- near-repetition (state collision booster)
{
"C-5","...","D-5","...","E-5","...","C-5","...",
"D-5","...","E-5","...","C-5","...","...","..."
},
-- same but shifted (VERY important)
{
"...","C-5","...","D-5","...","E-5","...","C-5",
"...","D-5","...","E-5","...","C-5","...","..."
},
}
local model = build_markov_model(flatten(training_data), 2)
print(inspect(model))
--[[
local generated = generate_sequence(model, 100)
for i,v in ipairs(generated) do
print(v)
end
--]]