feature/IMP-112-ascension-8-9 #59

Merged
mr.two merged 13 commits from feature/IMP-112-ascension-8-9 into develop 2026-04-29 21:27:24 +00:00
17 changed files with 399 additions and 118 deletions
Showing only changes of commit 3e31398d9d - Show all commits

148
CLAUDE.md Normal file
View File

@@ -0,0 +1,148 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
**Definitely not an Impostor** is a narrative-driven fantasy game built for [TIC-80](https://tic80.com/), a fantasy console. The game is written entirely in Lua. All source modules in `inc/` are concatenated at build time into a single `impostor.lua` file that TIC-80 loads.
## Build Commands
```bash
make build # Concatenate inc/**/*.lua into impostor.lua (order from impostor.inc)
make minify # Build then minify (downloads minify.lua if missing)
make lint # Run luacheck with source mapping to original files
make watch # Auto-rebuild on file changes in inc/
make export # Export minified game to HTML and .tic formats
make import_assets # Import PNG sprite/tile assets into the TIC-80 cartridge
make export_assets # Extract TIC-80 asset sections into inc/meta/meta.assets.lua
make docs # Generate documentation with ldoc
make clean # Remove build artifacts
```
To run the game locally: `tic80 --fs=. impostor.lua`
VSCode tasks are available for "Run TIC80", "Build & Run TIC80", "Export assets", and "Make build".
There is no test framework — validation is done via `make lint` (luacheck).
## Important Workflow Note
**Do not run `git add` or `git commit`** — git operations are the user's responsibility.
## Code Conventions (from GEMINI.md)
- **Functions**: `PascalCase` (e.g., `UpdatePlayer`, `DrawHUD`)
- **Variables**: `snake_case` (e.g., `player_x`, `game_state`)
- **Constants**: `SCREAMING_SNAKE_CASE` (e.g., `MAX_SPEED`)
- **Indentation**: 2 spaces
- **Tables**: Always multi-line with one key-value pair per line
- **Code sections**: Delimited with `--- @section SectionName` comments
- **TIC-80 APIs**: Use `btn()` for input, `spr()` for sprites, `map()` for tilemaps, `Print.text()` for text
## Architecture
The game is a **state machine** driven by a window manager. The build order is defined in `impostor.inc` — 99 source files are concatenated in dependency order.
### Main Loop
`TIC()` in `inc/system/system.main.lua` is TIC-80's per-frame callback. It:
1. Initializes game state once on first call
2. Updates mouse/context timing
3. Delegates to the current active window handler
4. Updates meters, timers, triggers, and glitch effects
5. Draws UI overlays
### Window Manager (`inc/window/window.manager.lua`)
Central UI state machine. Windows register with `id`, `update()`, and `draw()` handlers. Only one window is active at a time. All windows are declared in `window.register.lua`.
| Window | Purpose |
|--------|---------|
| `intro_title` | Title screen |
| `intro_ttg` | "Thanks To Grandma" credits |
| `intro_brief` | Game briefing |
| `menu` | Main menu |
| `game` | Main gameplay (screens + decisions) |
| `popup` | General popup overlay |
| `discussion` | NPC dialogue/conversation |
| `minigame_button_mash` | Button Mash minigame |
| `minigame_rhythm` | Rhythm minigame |
| `minigame_ddr` | DDR minigame |
| `game_over` | Game over / restart screen |
| `end` | End game choice screen |
| `continued` | Day-continued notification |
| `credits` | Credits roll |
| `controls` | Control scheme display |
| `audiotest` | Audio testing utility |
| `player_name` | 3-character name entry before new game |
| `ascend_debug` | Debug utility: start at a specific ascension level |
### Screen & Decision System (`inc/screen/`, `inc/decision/`)
- **Screens** are gameplay scenes. Registered with `Screen.register({id, name, decisions[], background, init, update, draw, exit})`. They manage background maps and NPC sprite placement.
- **Decisions** are player choices available on a screen. Registered with `Decision.register({id, label, condition, handle})`. A `condition` function gates visibility; `handle` drives transitions (to new screens, dialogue, minigames).
Screens: `home`, `office`, `work`, `toilet`, `walking_to_office`, `walking_to_home`, `mysterious_man`, `manager`
Maps (`inc/map/`): `bedroom`, `office`, `street` — rendered via `map.manager.lua`.
### Game Logic (`inc/logic/`)
| Module | Purpose |
|--------|---------|
| `logic.meter.lua` | Tracks ISM/WPM/BM stats (01000), combo multipliers, daily decay (20/day) |
| `logic.day.lua` | Day counter; ascension triggers at day 3, game over at day 100 |
| `logic.timer.lua` | Event scheduling/delayed callbacks, one-shot and repeating |
| `logic.trigger.lua` | Conditional event handlers with start/stop callbacks |
| `logic.discussion.lua` | Dialogue parsing, branching answers, NPC portrait rendering |
| `logic.minigame.lua` | Config and win-overlay for Button Mash, Rhythm, and DDR |
| `logic.focus.lua` | Circular reveal/hide overlay transitions (expanding/shrinking circle) |
| `logic.glitch.lua` | Visual glitch effect (random vertical stripes), toggled via `Glitch.show()/hide()` |
| `logic.commute_glitch.lua` | 7-level glitch progression during ascension 7: corrupts sprite lists, remaps Norman to `norman_echo`, speeds up music, blocks/redirects decisions |
| `logic.codegenerator.lua` | Encodes player's 3-char name to a 6-char base-36 completion code shown on the end screen |
### Global State (`inc/init/`)
- `init.context.lua`: All runtime game state (current screen, meter values, progress flags). Persisted in memory bank 6. Key fields: `player_name` (3-char string), `commute_glitch_level` (07), `talked_to_norman_echo`, `talked_to_true_sumphore`, `have_been_to_office`, `have_done_work_today`.
- `init.config.lua`: Screen dimensions (240×136), palette colors, timing constants. Persisted in memory bank 7.
- `init.ascension.lua`: 9-level meta-progression system ("ASCENSION" letters progressively lit). Level 7 activates CommunteGlitch; level 9 unlocks the final "Break the cycle" decision.
- `init.context_debug.lua`: `Context.new_game_debug(level)` — starts a new game at a specific ascension level for testing.
### Audio (`inc/audio/`)
- `audio.manager.lua`: Music playback (no-restart if already playing). Named tracks: `room_work` (0), `activity_work` (1), `mystery` (2).
- `audio.generator.lua` / `audio.songs.lua`: Sound generation and song definitions.
### Sprites (`inc/sprite/`)
`sprite.manager.lua` handles registration. Supports single and composite sprites with offset layers.
NPCs: `norman`, `norman_echo` (palette-remapped glitch variant of Norman, shown at commute glitch level 7), `sumphore`, `pizza_vendor`, and 10 developer archetypes (`dev_boy`, `dev_buddy`, `dev_extrovert`, `dev_girl`, `dev_guard`, `dev_guru`, `dev_hr_girl`, `dev_introvert`, `dev_operator`, `dev_project_manager`). Matrix characters: `matrix_architect`, `matrix_neo`, `matrix_oraculum`, `matrix_trinity`.
### Discussions (`inc/discussion/`)
Branching dialogue files loaded by `logic.discussion.lua`. Each file defines one or more named dialogue trees (keyed strings with answer arrays that apply meter deltas).
| File | Dialogues |
|------|-----------|
| `discussion.sumphore.lua` | Sumphore conversations (glitch-aware variants at commute glitch level 7) |
| `discussion.coworker.lua` | Coworker coffee-chat variants per ascension level (`disc_0`, `disc_1`, `disc_asc_1`, `disc_2`, `disc_asc_2`, …) |
| `discussion.commute_glitch.lua` | 8 commute glitch encounter variants (`cg_0``cg_7`) + truth/Sumphore variant |
| `discussion.truth.lua` | Dialogue with the "truth" mysterious man |
| `discussion.pizza_vendor.lua` | Pizza vendor interaction |
### Input Utilities (`inc/system/`)
- `system.textinput.lua`: 3-character uppercase letter selector. Supports next/prev letter cycling (A↔Z wrapping) and cursor navigation. Used by `PlayerNameWindow`.
### Key Directories
```
inc/ Source modules (concatenated at build)
assets/ Game assets (sprites, tiles, SFX, music)
assets_src/ Source art (Aseprite files, PNGs for import)
docs/ Design documentation (mostly Hungarian)
tools/ Build utilities (musicator: MIDI→TIC-80 converter)
prompts/ Feature templates
```

View File

@@ -50,7 +50,6 @@ decision/decision.go_to_toilet.lua
decision/decision.go_to_walking_to_office.lua decision/decision.go_to_walking_to_office.lua
decision/decision.go_to_office.lua decision/decision.go_to_office.lua
decision/decision.go_to_truth.lua decision/decision.go_to_truth.lua
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

View File

@@ -1,10 +0,0 @@
Decision.register({
id = "go_to_end",
label = "Break the cycle",
condition = function()
return Ascension.is_complete()
end,
handle = function()
Window.set_current("end")
end,
})

View File

@@ -2,6 +2,9 @@ Decision.register({
id = "go_to_home", id = "go_to_home",
label = "Go Home", label = "Go Home",
condition = function() condition = function()
if Ascension.get_level() >= 8 then
return Context.have_been_to_office and Context.have_done_work_today
end
if CommuteGlitch.is_active() then if CommuteGlitch.is_active() then
local g = CommuteGlitch.get_level() local g = CommuteGlitch.get_level()
if g >= 4 and g <= 5 then return false end if g >= 4 and g <= 5 then return false end
@@ -12,6 +15,10 @@ Decision.register({
return Context.have_been_to_office and Context.have_done_work_today return Context.have_been_to_office and Context.have_done_work_today
end, end,
handle = function() handle = function()
if Ascension.get_level() >= 8 then
Util.go_to_screen_by_id("home")
return
end
if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then
Context.should_ascend = true Context.should_ascend = true
CommuteGlitch.reset() CommuteGlitch.reset()

View File

@@ -1,6 +1,11 @@
Decision.register({ Decision.register({
id = "go_to_sleep", id = "go_to_sleep",
label = "Go to Sleep", label = function()
if Ascension.get_level() >= 8 then
return "Break the Loop"
end
return "Go to Sleep"
end,
condition = function() condition = function()
return Context.have_been_to_office and Context.have_done_work_today return Context.have_been_to_office and Context.have_done_work_today
end, end,
@@ -12,11 +17,15 @@ Decision.register({
focus_center_y = (Config.screen.height / 2) - 18, focus_center_y = (Config.screen.height / 2) - 18,
focus_initial_radius = 0, focus_initial_radius = 0,
on_win = function() on_win = function()
if Ascension.get_level() == 8 then
Ascension.increase()
end
local ascended = Ascension.consume_increase() local ascended = Ascension.consume_increase()
local level = Ascension.get_level() local level = Ascension.get_level()
MysteriousManScreen.start({ MysteriousManScreen.start({
skip_text = not ascended, skip_text = not ascended,
text = ascended and MysteriousManScreen.get_text_for_level(level) or nil, text = ascended and MysteriousManScreen.get_text_for_level(level) or nil,
break_mode = level >= 9,
}) })
end, end,
}) })

View File

@@ -1,6 +1,9 @@
Decision.register({ Decision.register({
id = "have_a_coffee", id = "have_a_coffee",
label = "Have a Coffee", label = "Have a Coffee",
condition = function()
return Ascension.get_level() < 8
end,
handle = function() handle = function()
local level = Ascension.get_level() local level = Ascension.get_level()
local disc_id = "coworker_disc_0" local disc_id = "coworker_disc_0"

View File

@@ -6,6 +6,9 @@ Decision.register({
end end
return "Talk to the homeless guy" return "Talk to the homeless guy"
end, end,
condition = function()
return Ascension.get_level() < 8
end,
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")

View File

@@ -21,7 +21,7 @@ local FADE_COLORS = nil
function Ascension.get_initial() function Ascension.get_initial()
_increased_this_cycle = false _increased_this_cycle = false
return { return {
level = 0, -- FYI: change this to test ascension levels without having to play through them level = 8, -- FYI: change this to test ascension levels without having to play through them
} }
end end
@@ -167,3 +167,10 @@ end
function Ascension.is_flashing() function Ascension.is_flashing()
return _flash_active return _flash_active
end end
--- Returns whether the fade-in effect is currently active.
--- @within Ascension
--- @return boolean Whether the letter fade-in is playing.
function Ascension.is_fading()
return _fade_active
end

View File

@@ -31,7 +31,7 @@ Context = {}
function Context.initial_data() function Context.initial_data()
return { return {
current_menu_item = 1, current_menu_item = 1,
test_mode = true, test_mode = false,
mouse_trace = false, mouse_trace = false,
popup = { popup = {
show = false, show = false,

View File

@@ -30,7 +30,7 @@ end
--- @return table Debug-patched initial context data. --- @return table Debug-patched initial context data.
function Context.initial_data_debug_asc(level) function Context.initial_data_debug_asc(level)
local data = Context.initial_data() local data = Context.initial_data()
data.test_mode = true data.test_mode = false
data.game_in_progress = true data.game_in_progress = true
data.ascension = { level = level } data.ascension = { level = level }
local overrides = _level_overrides[level] or _level_overrides[0] local overrides = _level_overrides[level] or _level_overrides[0]

View File

@@ -2,9 +2,11 @@
CommuteGlitch = {} CommuteGlitch = {}
--- Gets the current commute glitch level. --- Gets the current commute glitch level.
--- At ascension level 8+, always returns 7 (max) regardless of stored value.
--- @within CommuteGlitch --- @within CommuteGlitch
--- @return number Current glitch level (0-7). --- @return number Current glitch level (0-7).
function CommuteGlitch.get_level() function CommuteGlitch.get_level()
if Ascension.get_level() >= 8 then return 7 end
return Context and (Context.commute_glitch_level or 0) or 0 return Context and (Context.commute_glitch_level or 0) or 0
end end
@@ -34,11 +36,11 @@ function CommuteGlitch.enter_truth()
Ascension.start_flash() Ascension.start_flash()
end end
--- Returns true when ascension level is 7 (ASCENSIO step active). --- Returns true when ascension level is 7 or 8 (ASCENSIO/N steps active).
--- @within CommuteGlitch --- @within CommuteGlitch
--- @return boolean Whether the commute glitch system is active. --- @return boolean Whether the commute glitch system is active.
function CommuteGlitch.is_active() function CommuteGlitch.is_active()
return Ascension.get_level() == 7 return Ascension.get_level() >= 7
end end
--- Returns the music playback speed for the current glitch level. --- Returns the music playback speed for the current glitch level.

View File

@@ -312,3 +312,18 @@ function Meter.draw()
Ascension.draw(bar_x - 4, ascension_y, { spacing = 8 }) Ascension.draw(bar_x - 4, ascension_y, { spacing = 8 })
end end
--- Draws only the ascension letters at the same position as in Meter.draw().
--- Used when meters are hidden but ascension letters still need to be visible.
--- @within Meter
function Meter.draw_ascension_only()
local screen_w = Config.screen.width
local screen_h = Config.screen.height
local bar_w = screen_w * 0.25
local edge = math.max(2, math.floor(screen_w * 0.03))
local bar_x = screen_w - bar_w - edge
local line_h = 3
local start_y = screen_h * 0.05
local ascension_y = start_y + 3 * line_h + 1
Ascension.draw(bar_x - 4, ascension_y, { spacing = 8 })
end

View File

@@ -5,14 +5,23 @@ Screen.register({
"go_to_toilet", "go_to_toilet",
"go_to_walking_to_office", "go_to_walking_to_office",
"go_to_sleep", "go_to_sleep",
"go_to_end",
}, },
init = function() init = function()
Audio.music_play_room_work() if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then
Audio.music_play_mystery()
Glitch.show()
else
Audio.music_play_room_work()
end
end, end,
background = "bedroom", background = "bedroom",
draw = function() draw = function()
if Context.home_norman_visible and Window.get_current_id() == "game" then if Window.get_current_id() ~= "game" then return end
if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then
CommuteGlitch.draw_background_flicker()
Glitch.draw()
end
if Context.home_norman_visible then
Sprite.draw_at("norman", 100, 80) Sprite.draw_at("norman", 100, 80)
end end
end end

View File

@@ -93,6 +93,38 @@ local ASC_78_TEXT = [[
And then - finally - And then - finally -
he stopped walking. he stopped walking.
]] ]]
local ASC_89_TEXT = [[
Normann
you created this simulation
in the first place,
because you could never
cope with reality.
You were always
an impostor.
also,
you have beed talking to
yourself
in your sleep
]]
local ascension_texts = { local ascension_texts = {
[1] = ASC_01_TEXT, [1] = ASC_01_TEXT,
@@ -103,6 +135,7 @@ local ascension_texts = {
[6] = ASC_56_TEXT, [6] = ASC_56_TEXT,
[7] = ASC_67_TEXT, [7] = ASC_67_TEXT,
[8] = ASC_78_TEXT, [8] = ASC_78_TEXT,
[9] = ASC_89_TEXT,
} }
function MysteriousManScreen.get_text_for_level(level) function MysteriousManScreen.get_text_for_level(level)
@@ -123,6 +156,7 @@ local day_text_override = nil
local on_text_complete = nil local on_text_complete = nil
local show_mysterious_screen = true local show_mysterious_screen = true
local trigger_flash_on_wake = false local trigger_flash_on_wake = false
local break_mode = false
MysteriousManScreen.choices = { MysteriousManScreen.choices = {
{ {
@@ -227,6 +261,8 @@ function MysteriousManScreen.start(options)
text_y = Config.screen.height text_y = Config.screen.height
day_text_override = options.day_text day_text_override = options.day_text
on_text_complete = options.on_text_complete on_text_complete = options.on_text_complete
break_mode = options.break_mode or false
MysteriousManScreen.pending_end = false
Meter.hide() Meter.hide()
trigger_flash_on_wake = not options.skip_text trigger_flash_on_wake = not options.skip_text
if options.skip_text then if options.skip_text then
@@ -281,7 +317,7 @@ Screen.register({
if text_done_timer <= 0 or Input.select() then if text_done_timer <= 0 or Input.select() then
MysteriousManScreen.go_to_day_state() MysteriousManScreen.go_to_day_state()
-- to be continued -- to be continued
if 4 <= Ascension.get_level() then if 4 <= Ascension.get_level() and not break_mode then
Window.set_current("continued") Window.set_current("continued")
end end
end end
@@ -290,7 +326,10 @@ Screen.register({
day_timer = day_timer - Context.delta_time day_timer = day_timer - Context.delta_time
if day_timer <= 0 or Input.select() then if day_timer <= 0 or Input.select() then
if trigger_flash_on_wake or Ascension.get_level() < 1 then if break_mode then
state = STATE_CHOICE
selected_choice = 1
elseif trigger_flash_on_wake or Ascension.get_level() ~= 4 then
MysteriousManScreen.wake_up() MysteriousManScreen.wake_up()
else else
state = STATE_CHOICE state = STATE_CHOICE
@@ -298,23 +337,77 @@ Screen.register({
end end
end end
elseif state == STATE_CHOICE then elseif state == STATE_CHOICE then
local menu_x = (Config.screen.width - 60) / 2 if break_mode then
local menu_y = (Config.screen.height - 20) / 2 if MysteriousManScreen.pending_end then
local confirmed if not Ascension.is_flashing() and not Ascension.is_fading() then
selected_choice, confirmed = UI.update_menu(MysteriousManScreen.choices, selected_choice, menu_x, menu_y) MysteriousManScreen.pending_end = false
Window.set_current("end")
end
return
end
if Input.select() or confirmed then if Input.left() or Input.up() then
Audio.sfx_select() if selected_choice == 2 then
if selected_choice == 1 then Audio.sfx_beep()
MysteriousManScreen.wake_up() selected_choice = 1
else end
MysteriousManScreen.stay_in_bed() elseif Input.right() or Input.down() then
if selected_choice == 1 then
Audio.sfx_beep()
selected_choice = 2
end
end
if Input.select() then
Audio.sfx_select()
if selected_choice == 1 then
Ascension.start_flash()
MysteriousManScreen.pending_end = true
else
Context.reset()
Context.game_in_progress = true
Context.home_norman_visible = true
Glitch.hide()
Meter.show()
MenuWindow.refresh_menu_items()
Util.go_to_screen_by_id("home")
Window.set_current("game")
local home_screen = Screen.get_by_id("home")
if home_screen and home_screen.init then
home_screen.init()
end
end
end
else
local menu_x = (Config.screen.width - 60) / 2
local menu_y = (Config.screen.height - 20) / 2
local confirmed
selected_choice, confirmed = UI.update_menu(MysteriousManScreen.choices, selected_choice, menu_x, menu_y)
if Input.select() or confirmed then
Audio.sfx_select()
if selected_choice == 1 then
MysteriousManScreen.wake_up()
else
MysteriousManScreen.stay_in_bed()
end
end end
end end
end end
end, end,
draw = function() draw = function()
if show_mysterious_screen then if state == STATE_CHOICE and break_mode then
if not MysteriousManScreen.pending_end then
local nx = math.floor((Config.screen.width - 64) / 2)
local ny = math.floor((Config.screen.height - 96) / 2)
spr(272, nx, ny, Config.colors.transparent, 4)
spr(273, nx + 32, ny, Config.colors.transparent, 4)
spr(288, nx, ny + 32, Config.colors.transparent, 4)
spr(289, nx + 32, ny + 32, Config.colors.transparent, 4)
spr(304, nx, ny + 64, Config.colors.transparent, 4)
spr(305, nx + 32, ny + 64, Config.colors.transparent, 4)
end
elseif show_mysterious_screen then
MysteriousManScreen.draw_background() MysteriousManScreen.draw_background()
end end
@@ -337,9 +430,42 @@ Screen.register({
Config.colors.white Config.colors.white
) )
elseif state == STATE_CHOICE then elseif state == STATE_CHOICE then
local menu_x = (Config.screen.width - 60) / 2 if break_mode then
local menu_y = (Config.screen.height - 20) / 2 if MysteriousManScreen.pending_end then
UI.draw_menu(MysteriousManScreen.choices, selected_choice, menu_x, menu_y) Meter.draw_ascension_only()
else
local lines = {
"This is not a workplace.",
"This is a cycle.",
"And if it is a cycle...",
"it can be broken."
}
local y = 40
for _, line in ipairs(lines) do
Print.text_center_contour(line, Config.screen.width / 2, y, Config.colors.orange, false, 1, Config.colors.white)
y = y + 10
end
y = y + 20
local break_color = selected_choice == 1 and Config.colors.light_blue or Config.colors.white
local cont_color = selected_choice == 2 and Config.colors.light_blue or Config.colors.white
local break_text = (selected_choice == 1 and "> BREAK" or " BREAK")
local cont_text = (selected_choice == 2 and "> CONTINUE" or " CONTINUE")
local centerX = Config.screen.width / 2
local choice_gap = 20
local break_width = print(break_text, 0, -6, 0)
local cont_width = print(cont_text, 0, -6, 0)
local total_width = break_width + choice_gap + cont_width
local break_x = math.floor(centerX - (total_width / 2))
local cont_x = break_x + break_width + choice_gap
Print.text(break_text, break_x, y, break_color)
Print.text(cont_text, cont_x, y, cont_color)
end
else
local menu_x = (Config.screen.width - 60) / 2
local menu_y = (Config.screen.height - 20) / 2
UI.draw_menu(MysteriousManScreen.choices, selected_choice, menu_x, menu_y)
end
end end
end, end,
}) })

View File

@@ -92,5 +92,9 @@ Screen.register({
local asc_x = math.floor((sw - asc_total_w) / 2) local asc_x = math.floor((sw - asc_total_w) / 2)
Ascension.draw(asc_x, asc_letter_y, { spacing = asc_spacing }) Ascension.draw(asc_x, asc_letter_y, { spacing = asc_spacing })
end end
if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then
Glitch.draw()
end
end, end,
}) })

View File

@@ -28,10 +28,18 @@ Screen.register({
{x = 27 * 8, y = 11 * 8}, {x = 27 * 8, y = 11 * 8},
} }
Audio.music_play_room_work() if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then
Context.walking_to_office_sprites = Sprite.list_randomize(possible_sprites, possible_positions) Audio.music_play_mystery()
Context.walking_to_office_sprites = Sprite.list_randomize(possible_sprites, possible_positions)
Context.walking_to_office_sprites = CommuteGlitch.corrupt_sprite_list(Context.walking_to_office_sprites)
else
Audio.music_play_room_work()
Context.walking_to_office_sprites = Sprite.list_randomize(possible_sprites, possible_positions)
end
end,
background = function()
return CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 and "" or "street"
end, end,
background = "street",
update = function() update = function()
end, end,
draw = function() draw = function()
@@ -40,12 +48,19 @@ Screen.register({
local norman_x = Context.fast_food_approaching and (19 * 8) or (7 * 8) local norman_x = Context.fast_food_approaching and (19 * 8) or (7 * 8)
Sprite.draw_at("norman", norman_x, 3 * 8) Sprite.draw_at("norman", norman_x, 3 * 8)
Sprite.draw_at("sumphore", 9 * 8, 2 * 8) Sprite.draw_at("sumphore", 9 * 8, 2 * 8)
if Context.fast_food_eaten_today < 3 then
Sprite.draw_at("pizza_vendor", 19 * 8, 1 * 8)
end
Sprite.draw_at("dev_guard", 22 * 8, 3 * 8)
Sprite.draw_list(Context.walking_to_office_sprites) if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then
Sprite.draw_at("norman_echo", norman_x, 3 * 8)
CommuteGlitch.draw_sprite_list(Context.walking_to_office_sprites)
CommuteGlitch.draw_background_flicker()
Glitch.draw()
else
if Context.fast_food_eaten_today < 3 then
Sprite.draw_at("pizza_vendor", 19 * 8, 1 * 8)
end
Sprite.draw_at("dev_guard", 22 * 8, 3 * 8)
Sprite.draw_list(Context.walking_to_office_sprites)
end
end end
end end
}) })

View File

@@ -5,89 +5,33 @@
function EndWindow.draw() function EndWindow.draw()
cls(Config.colors.black) cls(Config.colors.black)
if Context._end.state == "choice" then local cx = Config.screen.width / 2
local lines = { local name = Context.player_name or "AAA"
"This is not a workplace.", local code = CodeGenerator.encrypt(name)
"This is a cycle.",
"And if it is a cycle...",
"it can be broken."
}
local y = 40 Print.text_center("~ GOOD ENDING ~", cx, 8, Config.colors.light_blue)
for _, line in ipairs(lines) do Print.text_center("Congratulations, " .. name .. "!", cx, 20, Config.colors.white)
Print.text_center(line, Config.screen.width / 2, y, Config.colors.white)
y = y + 10
end
y = y + 20 rectb(40, 29, 160, 36, Config.colors.blue)
local yes_color = Context._end.selection == 1 and Config.colors.light_blue or Config.colors.white Print.text_center("your code", cx, 33, Config.colors.light_grey)
local no_color = Context._end.selection == 2 and Config.colors.light_blue or Config.colors.white Print.text_center(code, cx, 44, Config.colors.white, false, 2)
local yes_text = (Context._end.selection == 1 and "> YES" or " YES") Print.text_center("Write it down!", cx, 70, Config.colors.item)
local no_text = (Context._end.selection == 2 and "> NO" or " NO")
local centerX = Config.screen.width / 2 line(20, 82, 219, 82, Config.colors.dark_grey)
Print.text(yes_text, centerX - 40, y, yes_color) Print.text_center("To continue via telnet:", cx, 87, Config.colors.light_grey)
Print.text(no_text, centerX + 10, y, no_color) Print.text_center("games.teletype.hu 2324", cx, 98, Config.colors.white)
elseif Context._end.state == "ending" then line(20, 110, 219, 110, Config.colors.dark_grey)
local cx = Config.screen.width / 2
local name = Context.player_name or "AAA"
local code = CodeGenerator.encrypt(name)
Print.text_center("~ GOOD ENDING ~", cx, 8, Config.colors.light_blue) Print.text_center("Press Z to return to menu", cx, 116, Config.colors.dark_grey)
Print.text_center("Congratulations, " .. name .. "!", cx, 20, Config.colors.white)
rectb(40, 29, 160, 36, Config.colors.blue)
Print.text_center("your code", cx, 33, Config.colors.light_grey)
Print.text_center(code, cx, 44, Config.colors.white, false, 2)
Print.text_center("Write it down!", cx, 70, Config.colors.item)
line(20, 82, 219, 82, Config.colors.dark_grey)
Print.text_center("To continue via telnet:", cx, 87, Config.colors.light_grey)
Print.text_center("games.teletype.hu 2324", cx, 98, Config.colors.white)
line(20, 110, 219, 110, Config.colors.dark_grey)
Print.text_center("Press Z to return to menu", cx, 116, Config.colors.dark_grey)
end
end end
--- Updates the end screen logic. --- Updates the end screen logic.
--- @within EndWindow --- @within EndWindow
function EndWindow.update() function EndWindow.update()
if Context._end.state == "choice" then if Input.select() then
if Input.left() or Input.up() then Context.reset()
if Context._end.selection == 2 then Window.set_current("menu")
Audio.sfx_beep() MenuWindow.refresh_menu_items()
Context._end.selection = 1
end
elseif Input.right() or Input.down() then
if Context._end.selection == 1 then
Audio.sfx_beep()
Context._end.selection = 2
end
end
if Input.select() then
Audio.sfx_select()
if Context._end.selection == 1 then
Context._end.state = "ending"
else
-- NO: increment day and go home
Day.increase()
Util.go_to_screen_by_id("home")
Window.set_current("game")
-- Initialize home screen
local home_screen = Screen.get_by_id("home")
if home_screen and home_screen.init then
home_screen.init()
end
end
end
elseif Context._end.state == "ending" then
if Input.select() then
Window.set_current("menu")
MenuWindow.refresh_menu_items()
end
end end
end end