feature/IMP-112-ascension-8-9 #59
148
CLAUDE.md
Normal file
148
CLAUDE.md
Normal 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 (0–1000), 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` (0–7), `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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
|
||||||
})
|
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user