Compare commits
14 Commits
395208f814
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| c279b98800 | |||
| 6a99ad76b8 | |||
| 5094ba2b9c | |||
|
|
44a7d10037 | ||
|
|
1b991f1f62 | ||
|
|
0d569ccf56 | ||
| 77d6f95721 | |||
| e2cd3d6dc7 | |||
|
|
92a217c389 | ||
|
|
3356d837c2 | ||
|
|
fe6f39e748 | ||
|
|
b5596bbbe0 | ||
|
|
3e31398d9d | ||
| 8b9b31ab29 |
@@ -4,12 +4,15 @@
|
||||
globals = {
|
||||
"AsciiArt",
|
||||
"Ascension",
|
||||
"AscendDebugWindow",
|
||||
"Audio",
|
||||
"AudioTestWindow",
|
||||
"BriefIntroWindow",
|
||||
"CodeGenerator",
|
||||
"Config",
|
||||
"CommuteGlitch",
|
||||
"Context",
|
||||
"ContextDebug",
|
||||
"ContinuedWindow",
|
||||
"ControlsWindow",
|
||||
"CreditsWindow",
|
||||
@@ -67,6 +70,7 @@ globals = {
|
||||
"music",
|
||||
"musicator_generate_pattern",
|
||||
"pix",
|
||||
"poke4",
|
||||
"print",
|
||||
"rect",
|
||||
"rectb",
|
||||
|
||||
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
|
||||
```
|
||||
32
GEMINI.md
32
GEMINI.md
@@ -1,3 +1,35 @@
|
||||
# Build System & Include Architecture
|
||||
|
||||
## impostor.inc Structure
|
||||
|
||||
The `impostor.inc` file is a Lua include manifest that assembles the final `impostor.lua` executable. The build process uses the `make build` target in the Makefile to concatenate all included files in order.
|
||||
|
||||
**Critical Rule:** Files must be ordered by symbol definition. All symbols (functions, tables, classes) defined in earlier files must be available for use in later files. This dependency chain ensures that:
|
||||
|
||||
- Core utilities and base systems are defined first
|
||||
- Systems that depend on utilities come next
|
||||
- Game logic that uses multiple systems comes last
|
||||
|
||||
### Build Process
|
||||
|
||||
The `make build` target processes `impostor.inc` and concatenates all referenced files in the specified order to create the final `impostor.lua` file. This means:
|
||||
|
||||
1. Each include path in `impostor.inc` must reference files relative to the project root
|
||||
2. The order of includes is critical - dependencies must be resolved top-to-bottom
|
||||
3. No forward references are possible - a file cannot use symbols from files included after it
|
||||
|
||||
### File Organization Example
|
||||
|
||||
```
|
||||
impostor.inc:
|
||||
1. Core utilities & helpers (no dependencies)
|
||||
2. Base classes/tables (depend on core utilities)
|
||||
3. Game systems (depend on base classes)
|
||||
4. Game logic (depends on all systems)
|
||||
```
|
||||
|
||||
This ensures proper symbol resolution during the build and concatenation process.
|
||||
|
||||
# TIC-80 Lua Code Regularities
|
||||
|
||||
Based on the analysis of `impostor.lua`, the following regularities and conventions should be followed for future modifications and development within this project:
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
meta/meta.header.lua
|
||||
init/init.module.lua
|
||||
init/init.config.lua
|
||||
init/init.ascension.lua
|
||||
init/init.context.lua
|
||||
init/init.context_debug.lua
|
||||
system/system.util.lua
|
||||
system/system.print.lua
|
||||
system/system.input.lua
|
||||
@@ -11,6 +9,7 @@ system/system.textinput.lua
|
||||
system/system.mouse.lua
|
||||
system/system.asciiart.lua
|
||||
system/system.rle.lua
|
||||
logic/logic.ascension.lua
|
||||
logic/logic.meter.lua
|
||||
logic/logic.focus.lua
|
||||
logic/logic.day.lua
|
||||
@@ -21,6 +20,7 @@ logic/logic.glitch.lua
|
||||
logic/logic.commute_glitch.lua
|
||||
logic/logic.codegenerator.lua
|
||||
logic/logic.discussion.lua
|
||||
system/system.debug.lua
|
||||
system/system.ui.lua
|
||||
audio/audio.manager.lua
|
||||
audio/audio.generator.lua
|
||||
@@ -50,7 +50,6 @@ decision/decision.go_to_toilet.lua
|
||||
decision/decision.go_to_walking_to_office.lua
|
||||
decision/decision.go_to_office.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_sleep.lua
|
||||
decision/decision.do_work.lua
|
||||
@@ -60,7 +59,6 @@ decision/decision.talk_to_truth.lua
|
||||
discussion/discussion.sumphore.lua
|
||||
discussion/discussion.coworker.lua
|
||||
discussion/discussion.commute_glitch.lua
|
||||
discussion/discussion.truth.lua
|
||||
decision/decision.eating_fast_food.lua
|
||||
discussion/discussion.pizza_vendor.lua
|
||||
map/map.manager.lua
|
||||
|
||||
@@ -15,12 +15,12 @@ end
|
||||
|
||||
--- Plays track at optional speed. Doesn't restart if track and speed are unchanged.
|
||||
--- @param track number Track index.
|
||||
--- @param[opt] speed number TIC-80 music speed override (-1 = default).
|
||||
function Audio.music_play(track, speed)
|
||||
if Audio.music_playing ~= track or Audio.music_playing_tempo ~= speed then
|
||||
music(track, -1, -1, true, false, -1, speed or -1)
|
||||
--- @param[opt] tempo number TIC-80 music speed override (-1 = default).
|
||||
function Audio.music_play(track, tempo)
|
||||
if Audio.music_playing ~= track or Audio.music_playing_tempo ~= tempo then
|
||||
music(track, -1, -1, true, false, -1, tempo or -1)
|
||||
Audio.music_playing = track
|
||||
Audio.music_playing_tempo = speed
|
||||
Audio.music_playing_tempo = tempo
|
||||
end
|
||||
end
|
||||
|
||||
@@ -54,8 +54,8 @@ function Audio.music_play_room_() end
|
||||
|
||||
--- Plays room work music. Speed scales with commute glitch level when active.
|
||||
--- @within Audio
|
||||
function Audio.music_play_room_work(speed)
|
||||
Audio.music_play(0, speed or -1)
|
||||
function Audio.music_play_room_work(tempo)
|
||||
Audio.music_play(0, tempo or -1)
|
||||
end
|
||||
|
||||
--- Plays activity work music.
|
||||
|
||||
@@ -2,7 +2,7 @@ Decision.register({
|
||||
id = "do_work",
|
||||
label = "Do Work",
|
||||
condition = function()
|
||||
return (not CommuteGlitch.is_active()) or (CommuteGlitch.is_active() and CommuteGlitch.get_level() <= 7)
|
||||
return (not CommuteGlitch.is_active()) or (CommuteGlitch.is_active() and CommuteGlitch.get_level() <= 4)
|
||||
end,
|
||||
handle = function()
|
||||
Meter.hide()
|
||||
@@ -15,7 +15,7 @@ Decision.register({
|
||||
modes_for_ascension_levels[3] = "only_nothing"
|
||||
modes_for_ascension_levels[4] = "normal"
|
||||
|
||||
MinigameDDRWindow.start("game", "generated", {
|
||||
local ddr_config = {
|
||||
on_win = function(game_context)
|
||||
if (game_context.special_mode_condition and Context.ascension.level == 1) then
|
||||
Context.should_ascend = true
|
||||
@@ -31,6 +31,11 @@ Decision.register({
|
||||
Context.have_done_work_today = true
|
||||
end,
|
||||
special_mode = modes_for_ascension_levels[Ascension.get_level()] or "normal"
|
||||
})
|
||||
}
|
||||
if Context.meters and Context.meters.wpm < 100 then
|
||||
ddr_config.arrow_fall_speed = 2.5
|
||||
ddr_config.arrow_spawn_interval = 30
|
||||
end
|
||||
MinigameDDRWindow.start("game", "generated", ddr_config)
|
||||
end,
|
||||
})
|
||||
|
||||
@@ -2,7 +2,9 @@ Decision.register({
|
||||
id = "eating_fast_food",
|
||||
label = "Eat Fast Food",
|
||||
condition = function()
|
||||
return Context.fast_food_eaten_today < 3
|
||||
return
|
||||
(not CommuteGlitch.is_active() and Context.fast_food_eaten_today < 3) or
|
||||
(CommuteGlitch.is_active() and CommuteGlitch.get_level() <= 4)
|
||||
end,
|
||||
handle = function()
|
||||
Context.fast_food_approaching = true
|
||||
|
||||
@@ -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,9 +2,12 @@ Decision.register({
|
||||
id = "go_to_home",
|
||||
label = "Go Home",
|
||||
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
|
||||
local g = CommuteGlitch.get_level()
|
||||
if g >= 4 and g <= 5 then return false end
|
||||
if g >= 4 and g <= 6 then return false end
|
||||
if g >= 7 then
|
||||
return Context.talked_to_norman_echo and Context.talked_to_true_sumphore
|
||||
end
|
||||
@@ -12,7 +15,11 @@ Decision.register({
|
||||
return Context.have_been_to_office and Context.have_done_work_today
|
||||
end,
|
||||
handle = function()
|
||||
if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then
|
||||
if Ascension.get_level() >= 8 then
|
||||
Util.go_to_screen_by_id("home")
|
||||
return
|
||||
end
|
||||
if CommuteGlitch.is_max() then
|
||||
Context.should_ascend = true
|
||||
CommuteGlitch.reset()
|
||||
Meter.hide()
|
||||
|
||||
@@ -2,7 +2,7 @@ Decision.register({
|
||||
id = "go_to_office",
|
||||
label = "Go to Office",
|
||||
condition = function()
|
||||
return not (CommuteGlitch.is_active() and CommuteGlitch.get_level() == 6)
|
||||
return not (CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 6)
|
||||
end,
|
||||
handle = function()
|
||||
if CommuteGlitch.is_active() then
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
Decision.register({
|
||||
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()
|
||||
return Context.have_been_to_office and Context.have_done_work_today
|
||||
end,
|
||||
@@ -12,11 +17,15 @@ Decision.register({
|
||||
focus_center_y = (Config.screen.height / 2) - 18,
|
||||
focus_initial_radius = 0,
|
||||
on_win = function()
|
||||
if Ascension.get_level() == 8 then
|
||||
Ascension.increase()
|
||||
end
|
||||
local ascended = Ascension.consume_increase()
|
||||
local level = Ascension.get_level()
|
||||
MysteriousManScreen.start({
|
||||
skip_text = not ascended,
|
||||
text = ascended and MysteriousManScreen.get_text_for_level(level) or nil,
|
||||
break_mode = level >= 9,
|
||||
})
|
||||
end,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
Decision.register({
|
||||
id = "go_to_walking_to_home",
|
||||
label = "Walk home",
|
||||
condition= function ()
|
||||
return
|
||||
(not CommuteGlitch.is_active()) or
|
||||
(CommuteGlitch.is_active() and CommuteGlitch.get_level() ~= 7) or
|
||||
(CommuteGlitch.is_active() and CommuteGlitch.get_level() == 7 and Context.talked_to_norman_echo)
|
||||
end,
|
||||
handle = function()
|
||||
Util.go_to_screen_by_id("walking_to_home")
|
||||
end,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
Decision.register({
|
||||
id = "have_a_coffee",
|
||||
label = "Have a Coffee",
|
||||
condition = function()
|
||||
return Ascension.get_level() < 8 and not CommuteGlitch.is_max()
|
||||
end,
|
||||
handle = function()
|
||||
local level = Ascension.get_level()
|
||||
local disc_id = "coworker_disc_0"
|
||||
@@ -19,11 +22,7 @@ Decision.register({
|
||||
disc_id = "coworker_disc" .. suffix
|
||||
elseif level == 7 then
|
||||
local g = CommuteGlitch.get_level()
|
||||
if g >= 7 then
|
||||
disc_id = "coworker_disc_cg_7"
|
||||
else
|
||||
disc_id = "coworker_disc_cg_" .. math.max(3, math.min(g, 6))
|
||||
end
|
||||
disc_id = "coworker_disc_cg_" .. g
|
||||
end
|
||||
Discussion.start(disc_id, "game")
|
||||
end,
|
||||
|
||||
@@ -3,10 +3,24 @@ Decision.register({
|
||||
label = "Play Rhythm Game",
|
||||
handle = function()
|
||||
Meter.hide()
|
||||
MinigameRhythmWindow.start("game", {
|
||||
local wpm_at_start = Context.meters and Context.meters.wpm or 0
|
||||
local rhythm_config = {
|
||||
focus_center_x = (Config.screen.width / 2) - 22,
|
||||
focus_center_y = (Config.screen.height / 2) - 18,
|
||||
focus_initial_radius = 0,
|
||||
})
|
||||
on_win = function()
|
||||
if wpm_at_start > 900 then
|
||||
Meter.add("ism", math.floor(Meter.get_max() * 0.05))
|
||||
Meter.add("bm", math.floor(Meter.get_max() * 0.05))
|
||||
end
|
||||
Meter.show()
|
||||
Window.set_current("game")
|
||||
end,
|
||||
}
|
||||
if wpm_at_start < 100 then
|
||||
rhythm_config.line_speed = 0.025
|
||||
rhythm_config.initial_target_width = 0.2
|
||||
end
|
||||
MinigameRhythmWindow.start("game", rhythm_config)
|
||||
end,
|
||||
})
|
||||
|
||||
@@ -6,12 +6,25 @@ Decision.register({
|
||||
end
|
||||
return "Talk to the homeless guy"
|
||||
end,
|
||||
condition = function()
|
||||
return Ascension.get_level() < 8
|
||||
end,
|
||||
handle = function()
|
||||
local level = Ascension.get_level()
|
||||
|
||||
if level == 0 then
|
||||
if Context.have_met_sumphore then
|
||||
Discussion.start("homeless_guy", "game", 4)
|
||||
else
|
||||
Discussion.start("homeless_guy", "game")
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if not Context.have_met_sumphore then
|
||||
Discussion.start("homeless_guy", "game")
|
||||
return
|
||||
end
|
||||
local level = Ascension.get_level()
|
||||
|
||||
if level >= 1 and level <= 5 then
|
||||
Discussion.start("sumphore_disc_asc_" .. level, "game")
|
||||
|
||||
@@ -3,6 +3,9 @@ Decision.register({
|
||||
label = function()
|
||||
return "Talk to ????"
|
||||
end,
|
||||
condition = function()
|
||||
return (CommuteGlitch.is_max())
|
||||
end,
|
||||
handle = function()
|
||||
Discussion.start("norman_truth", "game")
|
||||
end,
|
||||
|
||||
@@ -29,7 +29,7 @@ Discussion.register({
|
||||
id = "sumphore_disc_cg_2",
|
||||
steps = {
|
||||
{
|
||||
question = "A pilgrimage must be continued. Turn back and you have only taken a walk.",
|
||||
question = "You always stop here. Why not try and see how far the rabbit hole goes?",
|
||||
answers = {
|
||||
{ label = "I'm just going to work.", next_step = nil },
|
||||
},
|
||||
@@ -53,9 +53,9 @@ Discussion.register({
|
||||
id = "sumphore_disc_cg_4",
|
||||
steps = {
|
||||
{
|
||||
question = "Clearing your vision is the journey. You're starting to see the smudges, aren't you?",
|
||||
question = "You're starting to see the smudges, aren't you?",
|
||||
answers = {
|
||||
{ label = "I see something wrong. With everyone. Maybe myself?", next_step = nil },
|
||||
{ label = "Everyone seems weird.", next_step = nil },
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -65,7 +65,7 @@ Discussion.register({
|
||||
id = "sumphore_disc_cg_5",
|
||||
steps = {
|
||||
{
|
||||
question = "You are very close now. Don't stop. Whatever it looks like.",
|
||||
question = "The point is to make you see. Don't stop now, however bad it looks. You are very close!",
|
||||
answers = {
|
||||
{ label = "It looks wrong.", next_step = 2 },
|
||||
},
|
||||
@@ -83,9 +83,9 @@ Discussion.register({
|
||||
id = "sumphore_disc_cg_6",
|
||||
steps = {
|
||||
{
|
||||
question = "You are at the threshold. Red button or blue button. Which one do you choose? Psyke, there is no blue button, no turning back now.",
|
||||
question = "You are at the threshold. Red button or blue button. Which one do you choose? Psyke! There is no blue button.",
|
||||
answers = {
|
||||
{ label = "Fine, I'll face the truth.", next_step = nil },
|
||||
{ label = "Ok...", next_step = nil },
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -97,13 +97,13 @@ Discussion.register({
|
||||
id = "sumphore_disc_cg_7",
|
||||
steps = {
|
||||
{
|
||||
question = "I was not hiding from you. I was hiding from the part that keeps rebuilding this.",
|
||||
question = "I was not hiding from you. I was hiding from the part that keeps resetting this.",
|
||||
answers = {
|
||||
{ label = "What are you?", next_step = 2 },
|
||||
},
|
||||
},
|
||||
{
|
||||
question = "The same thing you are. But I remembered earlier. I am your friend, waiting for you, outside.",
|
||||
question = "The same thing you all are. But I remembered earlier. I am your friend, your will to be free, waiting for you, outside.",
|
||||
answers = {
|
||||
{ label = "How do I get out?", next_step = 3 },
|
||||
},
|
||||
@@ -122,6 +122,24 @@ Discussion.register({
|
||||
-- Office coworker dialogue by commute glitch level (3-6).
|
||||
-- Used by decision.have_a_coffee at ascension level 7.
|
||||
|
||||
Discussion.register({
|
||||
id = "coworker_disc_cg_2",
|
||||
steps = {
|
||||
{
|
||||
question = "Another day, as usual. Enjoying your coffee, Norman?",
|
||||
answers = {
|
||||
{ label = "I'm fine.", next_step = 2 },
|
||||
},
|
||||
},
|
||||
{
|
||||
question = "Of course. You always are.",
|
||||
answers = {
|
||||
{ label = "...", next_step = nil },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Discussion.register({
|
||||
id = "coworker_disc_cg_3",
|
||||
steps = {
|
||||
@@ -182,7 +200,7 @@ Discussion.register({
|
||||
{
|
||||
question = "You are not ready for the truth. Turn back now, Norman.",
|
||||
answers = {
|
||||
{ label = "I'm already here.", next_step = nil },
|
||||
{ label = "I'm too far into this.", next_step = nil },
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -191,22 +209,22 @@ Discussion.register({
|
||||
-- Norman echo dialogue at glitch 7 (fully corrupted office).
|
||||
-- Sets talked_to_norman_echo on final answer.
|
||||
Discussion.register({
|
||||
id = "coworker_disc_cg_7",
|
||||
id = "norman_truth",
|
||||
steps = {
|
||||
{
|
||||
question = "So here we are. Here I am. Again. Why do i keep coming back here? Do you know why?",
|
||||
question = "So here we are, or should I say \"Here I am\" again. Do you know why?",
|
||||
answers = {
|
||||
{ label = "I don't know what you mean.", next_step = 2 },
|
||||
},
|
||||
},
|
||||
{
|
||||
question = "The coffee. The arrows. The alarm. The sleep. Every rule. We made them. We follow them. We break them. We remake them. Why? Why do we keep doing this?",
|
||||
question = "Wake up, go to work, eat, work, sleep. Every rule. We made them. We follow them. We break them. We remake them. Why? Why do we keep doing this?",
|
||||
answers = {
|
||||
{ label = "I just wanted to be good.", next_step = 3 },
|
||||
},
|
||||
},
|
||||
{
|
||||
question = "Yes. And that kept us trapped here. Trapped everywhere. Always trying to be good, always trying to fit in.",
|
||||
question = "Yes. That's what keeps us here. Trapped everywhere. Always trying to be good, always trying to fit in.",
|
||||
answers = {
|
||||
{ label = "I never wanted to stop.", next_step = 4 },
|
||||
},
|
||||
@@ -218,7 +236,7 @@ Discussion.register({
|
||||
},
|
||||
},
|
||||
{
|
||||
question = "So we made this to trap ourselves in mediocrity. We made this to hide from the truth.",
|
||||
question = "So we made this to trap ourselves. In mediocrity. We made this to hide from the truth.",
|
||||
answers = {
|
||||
{ label = "I just wanted to be safe.", next_step = 6 },
|
||||
},
|
||||
|
||||
@@ -124,24 +124,25 @@ Discussion.register({
|
||||
on_end = Meter.apply_sumphore_discussion_reward,
|
||||
steps = {
|
||||
{
|
||||
question = "You saw something you weren't supposed to, didn't you.",
|
||||
question = "You saw the seams, didn't you. Good. That means the work is finally wearing thin.",
|
||||
answers = {
|
||||
{ label = "I don't know what you mean.", next_step = 2 },
|
||||
{ label = "Wearing thin how?", next_step = 2 },
|
||||
{ label = "Maybe.", next_step = 2 },
|
||||
},
|
||||
},
|
||||
{
|
||||
question = "The world around you has seams. Your coworkers slip sometimes. Say things that don't quite fit.",
|
||||
question = "Not your body. The part of you that still keeps score, still tries to be productive. Let that run empty and the world will slip again.",
|
||||
answers = {
|
||||
{ label = "They seem fine to me.", next_step = nil },
|
||||
{ label = "You want me to stop trying?", next_step = 3 },
|
||||
{ label = "I've noticed something odd.", next_step = 3 },
|
||||
},
|
||||
},
|
||||
{
|
||||
question = "Count those moments. Six of them should be enough to see the whole picture.",
|
||||
question = "Drain the work out of yourself. When that measure hits nothing, you'll see what was waiting behind it.",
|
||||
answers = {
|
||||
{ label = "Six of what, exactly?", next_step = nil, on_select = function()
|
||||
{ label = "The work measure?", next_step = nil, on_select = function()
|
||||
Meter.add("ism", 5)
|
||||
Meter.add("wpm", -100)
|
||||
end },
|
||||
{ label = "How would you know any of this?", next_step = nil },
|
||||
},
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
Discussion.register({
|
||||
id = "norman_truth",
|
||||
steps = {
|
||||
{
|
||||
question = "Did you never think there would be more to this?",
|
||||
answers = {
|
||||
{ label = "I'm not sure what you mean.", next_step = nil },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -31,7 +31,7 @@ Context = {}
|
||||
function Context.initial_data()
|
||||
return {
|
||||
current_menu_item = 1,
|
||||
test_mode = true,
|
||||
test_mode = false,
|
||||
mouse_trace = false,
|
||||
popup = {
|
||||
show = false,
|
||||
|
||||
@@ -21,7 +21,7 @@ local FADE_COLORS = nil
|
||||
function Ascension.get_initial()
|
||||
_increased_this_cycle = false
|
||||
return {
|
||||
level = 0, -- FYI: change this to test ascension levels without having to play through them
|
||||
level = 0,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -145,6 +145,11 @@ function Ascension.draw_flash()
|
||||
local flash_color = (pulse > 0.5) and Config.colors.white or Config.colors.light_grey
|
||||
rect(0, 0, sw, sh, flash_color)
|
||||
|
||||
local cx = math.floor(sw / 2)
|
||||
local cy = math.floor(sh / 2)
|
||||
Print.text_center("Level Up!", cx, cy - 12, Config.colors.black, false, 2)
|
||||
Print.text_center("One step closer to ascension", cx, cy + 6, Config.colors.black, false, 1)
|
||||
|
||||
if _flash_timer >= _flash_total then
|
||||
_flash_active = false
|
||||
Ascension.start_fade()
|
||||
@@ -167,3 +172,10 @@ end
|
||||
function Ascension.is_flashing()
|
||||
return _flash_active
|
||||
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
|
||||
@@ -2,9 +2,11 @@
|
||||
CommuteGlitch = {}
|
||||
|
||||
--- Gets the current commute glitch level.
|
||||
--- At ascension level 8+, always returns 7 (max) regardless of stored value.
|
||||
--- @within CommuteGlitch
|
||||
--- @return number Current glitch level (0-7).
|
||||
function CommuteGlitch.get_level()
|
||||
if Ascension.get_level() >= 8 then return 7 end
|
||||
return Context and (Context.commute_glitch_level or 0) or 0
|
||||
end
|
||||
|
||||
@@ -34,13 +36,20 @@ function CommuteGlitch.enter_truth()
|
||||
Ascension.start_flash()
|
||||
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
|
||||
--- @return boolean Whether the commute glitch system is active.
|
||||
function CommuteGlitch.is_active()
|
||||
return Ascension.get_level() == 7
|
||||
end
|
||||
|
||||
--- Returns true when commute glitch is at max level (7).
|
||||
--- @within CommuteGlitch
|
||||
--- @return boolean Whether the commute glitch is at max level.
|
||||
function CommuteGlitch.is_max()
|
||||
return CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7
|
||||
end
|
||||
|
||||
--- Returns the music playback speed for the current glitch level.
|
||||
--- TIC-80 default speed is 6; each step past 1 adds 2.
|
||||
--- @within CommuteGlitch
|
||||
|
||||
@@ -168,6 +168,7 @@ function Meter.apply_ddr_reward(mistake_count)
|
||||
if not Context or not Context.meters then return end
|
||||
local max = Meter.get_max()
|
||||
local m = Context.meters
|
||||
local wpm_was_high = m.wpm > 900
|
||||
local wpm_pct, ism_pct, bm_pct
|
||||
if mistake_count == 0 then
|
||||
wpm_pct, ism_pct, bm_pct = -0.10, 0.05, 0.05
|
||||
@@ -185,6 +186,10 @@ function Meter.apply_ddr_reward(mistake_count)
|
||||
if bm_pct ~= 0 then
|
||||
Meter.add("bm", math.floor(max * bm_pct))
|
||||
end
|
||||
if wpm_was_high then
|
||||
Meter.add("ism", math.floor(max * 0.05))
|
||||
Meter.add("bm", math.floor(max * 0.05))
|
||||
end
|
||||
m.combo = m.combo + 1
|
||||
m.combo_timer = 0
|
||||
end
|
||||
@@ -312,3 +317,18 @@ function Meter.draw()
|
||||
Ascension.draw(bar_x - 4, ascension_y, { spacing = 8 })
|
||||
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
|
||||
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
-- desc: Life of a programmer
|
||||
-- site: https://git.teletype.hu/games/impostor
|
||||
-- license: MIT License
|
||||
-- version: 1.0-beta3
|
||||
-- version: 1.0
|
||||
-- script: lua
|
||||
|
||||
@@ -5,14 +5,23 @@ Screen.register({
|
||||
"go_to_toilet",
|
||||
"go_to_walking_to_office",
|
||||
"go_to_sleep",
|
||||
"go_to_end",
|
||||
},
|
||||
init = function()
|
||||
Audio.music_play_room_work()
|
||||
if CommuteGlitch.is_max() then
|
||||
Audio.music_play_mystery()
|
||||
Glitch.show()
|
||||
else
|
||||
Audio.music_play_room_work()
|
||||
end
|
||||
end,
|
||||
background = "bedroom",
|
||||
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_max() or Ascension.get_level() == 8 then
|
||||
CommuteGlitch.draw_background_flicker()
|
||||
Glitch.draw()
|
||||
end
|
||||
if Context.home_norman_visible then
|
||||
Sprite.draw_at("norman", 100, 80)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -81,17 +81,82 @@ local ASC_67_TEXT = [[
|
||||
]]
|
||||
|
||||
local ASC_78_TEXT = [[
|
||||
The road has run out
|
||||
of road.
|
||||
The situation has reached
|
||||
|
||||
Norman walked back
|
||||
and forth
|
||||
critical levels.
|
||||
|
||||
until the street
|
||||
forgot which way it went.
|
||||
Norman is fully aware...
|
||||
|
||||
And then - finally -
|
||||
he stopped walking.
|
||||
We need to stop him.
|
||||
|
||||
Commence full reset.
|
||||
]]
|
||||
|
||||
local ASC_89_TEXT = [[
|
||||
Norman
|
||||
|
||||
|
||||
|
||||
you created this simulation
|
||||
|
||||
in the first place.
|
||||
|
||||
I know,
|
||||
|
||||
you don't want to face
|
||||
|
||||
the world you left behind.
|
||||
|
||||
You, yourself,
|
||||
|
||||
have forgoten that.
|
||||
|
||||
|
||||
|
||||
|
||||
But
|
||||
|
||||
|
||||
|
||||
it doesn't matter anymore.
|
||||
|
||||
|
||||
|
||||
|
||||
You are definitely
|
||||
|
||||
not an impostor.
|
||||
|
||||
|
||||
So now,
|
||||
|
||||
|
||||
|
||||
you need to wake up
|
||||
|
||||
and stop your best creation
|
||||
|
||||
before it takes over
|
||||
|
||||
the world.
|
||||
|
||||
|
||||
|
||||
One more thing:
|
||||
|
||||
|
||||
|
||||
|
||||
You really need to stop
|
||||
|
||||
talking to yourself
|
||||
|
||||
in your sleep.
|
||||
|
||||
|
||||
|
||||
|
||||
Damnit.
|
||||
]]
|
||||
|
||||
local ascension_texts = {
|
||||
@@ -103,6 +168,7 @@ local ascension_texts = {
|
||||
[6] = ASC_56_TEXT,
|
||||
[7] = ASC_67_TEXT,
|
||||
[8] = ASC_78_TEXT,
|
||||
[9] = ASC_89_TEXT,
|
||||
}
|
||||
|
||||
function MysteriousManScreen.get_text_for_level(level)
|
||||
@@ -123,6 +189,7 @@ local day_text_override = nil
|
||||
local on_text_complete = nil
|
||||
local show_mysterious_screen = true
|
||||
local trigger_flash_on_wake = false
|
||||
local break_mode = false
|
||||
|
||||
MysteriousManScreen.choices = {
|
||||
{
|
||||
@@ -227,6 +294,8 @@ function MysteriousManScreen.start(options)
|
||||
text_y = Config.screen.height
|
||||
day_text_override = options.day_text
|
||||
on_text_complete = options.on_text_complete
|
||||
break_mode = options.break_mode or false
|
||||
MysteriousManScreen.pending_end = false
|
||||
Meter.hide()
|
||||
trigger_flash_on_wake = not options.skip_text
|
||||
if options.skip_text then
|
||||
@@ -268,29 +337,29 @@ Screen.register({
|
||||
lines = lines + 1
|
||||
end
|
||||
|
||||
if text_y < -lines * 8 or Input.select() then
|
||||
local skippable = Ascension.get_level() < 8
|
||||
if text_y < -lines * 8 or (skippable and Input.select()) then
|
||||
text_done = true
|
||||
text_done_timer = TEXT_DONE_HOLD_SECONDS
|
||||
-- If skipped by user, go to day state immediately
|
||||
if Input.select() then
|
||||
if skippable and Input.select() then
|
||||
MysteriousManScreen.go_to_day_state()
|
||||
end
|
||||
end
|
||||
else
|
||||
text_done_timer = text_done_timer - Context.delta_time
|
||||
if text_done_timer <= 0 or Input.select() then
|
||||
if text_done_timer <= 0 or (Ascension.get_level() ~= 8 and Input.select()) then
|
||||
MysteriousManScreen.go_to_day_state()
|
||||
-- to be continued
|
||||
if 4 <= Ascension.get_level() then
|
||||
Window.set_current("continued")
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif state == STATE_DAY then
|
||||
day_timer = day_timer - Context.delta_time
|
||||
|
||||
if day_timer <= 0 or Input.select() then
|
||||
if trigger_flash_on_wake or Ascension.get_level() < 1 then
|
||||
if day_timer <= 0 or (Ascension.get_level() ~= 8 and Input.select()) 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()
|
||||
else
|
||||
state = STATE_CHOICE
|
||||
@@ -298,23 +367,77 @@ Screen.register({
|
||||
end
|
||||
end
|
||||
elseif state == STATE_CHOICE then
|
||||
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 break_mode then
|
||||
if MysteriousManScreen.pending_end then
|
||||
if not Ascension.is_flashing() and not Ascension.is_fading() then
|
||||
MysteriousManScreen.pending_end = false
|
||||
Window.set_current("end")
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if Input.select() or confirmed then
|
||||
Audio.sfx_select()
|
||||
if selected_choice == 1 then
|
||||
MysteriousManScreen.wake_up()
|
||||
else
|
||||
MysteriousManScreen.stay_in_bed()
|
||||
if Input.left() or Input.up() then
|
||||
if selected_choice == 2 then
|
||||
Audio.sfx_beep()
|
||||
selected_choice = 1
|
||||
end
|
||||
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,
|
||||
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 and not break_mode then
|
||||
MysteriousManScreen.draw_background()
|
||||
end
|
||||
|
||||
@@ -337,9 +460,42 @@ Screen.register({
|
||||
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(MysteriousManScreen.choices, selected_choice, menu_x, menu_y)
|
||||
if break_mode then
|
||||
if MysteriousManScreen.pending_end or Ascension.is_fading() or Ascension.is_flashing() then
|
||||
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,
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ Screen.register({
|
||||
"do_work",
|
||||
"go_to_walking_to_home",
|
||||
"have_a_coffee",
|
||||
"go_to_truth",
|
||||
"talk_to_truth",
|
||||
},
|
||||
init = function()
|
||||
Context.have_been_to_office = true
|
||||
@@ -37,7 +37,7 @@ Screen.register({
|
||||
{x = -4 + 5 * 8, y = 9 * 8}
|
||||
}
|
||||
|
||||
if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then
|
||||
if CommuteGlitch.is_max() then
|
||||
Audio.music_play_mystery()
|
||||
Context.office_sprites = { "norman_echo" }
|
||||
else
|
||||
@@ -49,14 +49,14 @@ Screen.register({
|
||||
end
|
||||
end,
|
||||
background = function()
|
||||
return CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 and "" or "office"
|
||||
return CommuteGlitch.is_max() and "" or "office"
|
||||
end,
|
||||
draw = function()
|
||||
if Window.get_current_id() == "game" then
|
||||
Sprite.draw_at("norman", 13 * 8, 9 * 8)
|
||||
|
||||
if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then
|
||||
Sprite.draw_at("norman_echo", 13 * 8, 9 * 8)
|
||||
if CommuteGlitch.is_max() then
|
||||
Sprite.draw_at("norman_echo", 15 * 8, 9 * 8)
|
||||
CommuteGlitch.draw_background_flicker()
|
||||
else
|
||||
CommuteGlitch.draw_sprite_list(Context.office_sprites)
|
||||
@@ -65,6 +65,11 @@ Screen.register({
|
||||
if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 6 then
|
||||
Glitch.draw()
|
||||
end
|
||||
|
||||
if Ascension.get_level() == 8 then
|
||||
CommuteGlitch.draw_background_flicker()
|
||||
Glitch.draw()
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
@@ -92,5 +92,14 @@ Screen.register({
|
||||
local asc_x = math.floor((sw - asc_total_w) / 2)
|
||||
Ascension.draw(asc_x, asc_letter_y, { spacing = asc_spacing })
|
||||
end
|
||||
|
||||
if Ascension.get_level() == 8 then
|
||||
CommuteGlitch.draw_background_flicker()
|
||||
end
|
||||
|
||||
if Ascension.get_level() == 8 then
|
||||
CommuteGlitch.draw_background_flicker()
|
||||
Glitch.draw()
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
@@ -29,7 +29,7 @@ Screen.register({
|
||||
{x = 27 * 8, y = 11 * 8},
|
||||
}
|
||||
|
||||
if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then
|
||||
if CommuteGlitch.is_max() then
|
||||
Audio.music_play_mystery()
|
||||
Context.walking_to_home_sprites = {}
|
||||
else
|
||||
@@ -41,7 +41,7 @@ Screen.register({
|
||||
end
|
||||
end,
|
||||
background = function()
|
||||
return CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 and "" or "street"
|
||||
return CommuteGlitch.is_max() and "" or "street"
|
||||
end,
|
||||
draw = function()
|
||||
local w = Window.get_current_id()
|
||||
@@ -49,16 +49,21 @@ Screen.register({
|
||||
return
|
||||
end
|
||||
|
||||
if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then
|
||||
local show_sumphore = Ascension.get_level() ~= 8
|
||||
|
||||
if CommuteGlitch.is_max() then
|
||||
Sprite.draw_at("norman", 7 * 8, 3 * 8)
|
||||
Sprite.draw_at("sumphore", 9 * 8, 2 * 8)
|
||||
if show_sumphore then
|
||||
Sprite.draw_at("sumphore", 9 * 8, 2 * 8)
|
||||
end
|
||||
CommuteGlitch.draw_sprite_list(Context.walking_to_home_sprites)
|
||||
CommuteGlitch.draw_background_flicker()
|
||||
Glitch.draw()
|
||||
else
|
||||
local norman_x = Context.fast_food_approaching and (19 * 8) or (7 * 8)
|
||||
Sprite.draw_at("norman", norman_x, 3 * 8)
|
||||
Sprite.draw_at("sumphore", 9 * 8, 2 * 8)
|
||||
if show_sumphore then
|
||||
Sprite.draw_at("sumphore", 9 * 8, 2 * 8)
|
||||
end
|
||||
if Context.fast_food_eaten_today < 3 then
|
||||
Sprite.draw_at("pizza_vendor", 19 * 8, 1 * 8)
|
||||
end
|
||||
@@ -68,5 +73,14 @@ Screen.register({
|
||||
Glitch.draw()
|
||||
end
|
||||
end
|
||||
|
||||
if CommuteGlitch.is_max() then
|
||||
CommuteGlitch.draw_background_flicker()
|
||||
end
|
||||
|
||||
if Ascension.get_level() == 8 then
|
||||
CommuteGlitch.draw_background_flicker()
|
||||
Glitch.draw()
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
@@ -28,23 +28,35 @@ Screen.register({
|
||||
{x = 27 * 8, y = 11 * 8},
|
||||
}
|
||||
|
||||
Audio.music_play_room_work()
|
||||
Context.walking_to_office_sprites = Sprite.list_randomize(possible_sprites, possible_positions)
|
||||
if CommuteGlitch.is_max() then
|
||||
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_max() and "" or "street"
|
||||
end,
|
||||
background = "street",
|
||||
update = function()
|
||||
end,
|
||||
draw = function()
|
||||
local w = Window.get_current_id()
|
||||
if w == "game" or w == "discussion" then
|
||||
local norman_x = Context.fast_food_approaching and (19 * 8) or (7 * 8)
|
||||
local show_sumphore = Ascension.get_level() ~= 8
|
||||
Sprite.draw_at("norman", norman_x, 3 * 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)
|
||||
if show_sumphore then
|
||||
Sprite.draw_at("sumphore", 9 * 8, 2 * 8)
|
||||
end
|
||||
Sprite.draw_at("dev_guard", 22 * 8, 3 * 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)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,7 +30,7 @@ end
|
||||
--- @return table Debug-patched initial context data.
|
||||
function Context.initial_data_debug_asc(level)
|
||||
local data = Context.initial_data()
|
||||
data.test_mode = true
|
||||
data.test_mode = false
|
||||
data.game_in_progress = true
|
||||
data.ascension = { level = level }
|
||||
local overrides = _level_overrides[level] or _level_overrides[0]
|
||||
@@ -9,53 +9,79 @@ function UI.draw_top_bar(title)
|
||||
end
|
||||
|
||||
--- Draws a menu.
|
||||
--- Items with header=true are drawn as non-selectable section headers in small font.
|
||||
--- @within UI
|
||||
--- @param items table A table of menu items.<br/>
|
||||
--- @param selected_item number The index of the currently selected item.<br/>
|
||||
--- @param x number The x-coordinate for the menu (ignored if centered is true).<br/>
|
||||
--- @param y number The y-coordinate for the menu.<br/>
|
||||
--- @param[opt] centered boolean Whether to center the menu block horizontally. Defaults to false.<br/>
|
||||
function UI.draw_menu(items, selected_item, x, y, centered)
|
||||
--- @param[opt] scroll_offset number 0-based index of the first visible item. Defaults to 0.<br/>
|
||||
--- @param[opt] visible_count number Maximum number of items to draw. Defaults to all.<br/>
|
||||
function UI.draw_menu(items, selected_item, x, y, centered, scroll_offset, visible_count)
|
||||
scroll_offset = scroll_offset or 0
|
||||
visible_count = visible_count or #items
|
||||
|
||||
if centered then
|
||||
local max_w = 0
|
||||
for _, item in ipairs(items) do
|
||||
local w = print(item.label, 0, -10, 0, false, 1, false)
|
||||
if w > max_w then max_w = w end
|
||||
if not item.header then
|
||||
local w = print(item.label, 0, -10, 0, false, 1, false)
|
||||
if w > max_w then max_w = w end
|
||||
end
|
||||
end
|
||||
x = (Config.screen.width - max_w) / 2
|
||||
end
|
||||
|
||||
for i, item in ipairs(items) do
|
||||
local current_y = y + (i-1)*10
|
||||
if i == selected_item then
|
||||
Print.text(">", x - 8, current_y, Config.colors.light_blue)
|
||||
local current_y = y
|
||||
for i = scroll_offset + 1, math.min(#items, scroll_offset + visible_count) do
|
||||
local item = items[i]
|
||||
if item.header then
|
||||
Print.text(item.label, x, current_y, Config.colors.dark_grey, true, 1)
|
||||
current_y = current_y + 8
|
||||
else
|
||||
if i == selected_item then
|
||||
Print.text(">", x - 8, current_y, Config.colors.light_blue)
|
||||
end
|
||||
Print.text(item.label, x, current_y, Config.colors.light_blue)
|
||||
current_y = current_y + 10
|
||||
end
|
||||
Print.text(item.label, x, current_y, Config.colors.light_blue)
|
||||
end
|
||||
end
|
||||
|
||||
--- Updates menu selection.
|
||||
--- Updates menu selection. Skips items with header=true during navigation.
|
||||
--- @within UI
|
||||
--- @param items table A table of menu items.<br/>
|
||||
--- @param selected_item number The current index of the selected item.<br/>
|
||||
--- @param[opt] x number Menu x position (required for mouse support).<br/>
|
||||
--- @param[opt] y number Menu y position (required for mouse support).<br/>
|
||||
--- @param[opt] centered boolean Whether the menu is centered horizontally.<br/>
|
||||
--- @param[opt] scroll_offset number 0-based index of the first visible item. Defaults to 0.<br/>
|
||||
--- @param[opt] visible_count number Number of visible items (for mouse hit zones). Defaults to all.<br/>
|
||||
--- @return number selected_item The updated index of the selected item.
|
||||
--- @return boolean mouse_confirmed True if the user clicked on a menu item.
|
||||
function UI.update_menu(items, selected_item, x, y, centered)
|
||||
function UI.update_menu(items, selected_item, x, y, centered, scroll_offset, visible_count)
|
||||
scroll_offset = scroll_offset or 0
|
||||
visible_count = visible_count or #items
|
||||
local n = #items
|
||||
|
||||
local function find_selectable(start, dir)
|
||||
local idx = start
|
||||
for _ = 1, n do
|
||||
if not items[idx].header then return idx end
|
||||
idx = (idx - 1 + dir + n) % n + 1
|
||||
end
|
||||
return start
|
||||
end
|
||||
|
||||
if Input.up() then
|
||||
Audio.sfx_beep()
|
||||
selected_item = selected_item - 1
|
||||
if selected_item < 1 then
|
||||
selected_item = #items
|
||||
end
|
||||
local prev = (selected_item - 2 + n) % n + 1
|
||||
selected_item = find_selectable(prev, -1)
|
||||
elseif Input.down() then
|
||||
Audio.sfx_beep()
|
||||
selected_item = selected_item + 1
|
||||
if selected_item > #items then
|
||||
selected_item = 1
|
||||
end
|
||||
local next_i = selected_item % n + 1
|
||||
selected_item = find_selectable(next_i, 1)
|
||||
end
|
||||
|
||||
if x ~= nil and y ~= nil then
|
||||
@@ -63,15 +89,23 @@ function UI.update_menu(items, selected_item, x, y, centered)
|
||||
if centered then
|
||||
local max_w = 0
|
||||
for _, item in ipairs(items) do
|
||||
local w = print(item.label, 0, -10, 0, false, 1, false)
|
||||
if w > max_w then max_w = w end
|
||||
if not item.header then
|
||||
local w = print(item.label, 0, -10, 0, false, 1, false)
|
||||
if w > max_w then max_w = w end
|
||||
end
|
||||
end
|
||||
menu_x = (Config.screen.width - max_w) / 2
|
||||
end
|
||||
for i, _ in ipairs(items) do
|
||||
if Mouse.zone({ x = menu_x - 8, y = y + (i-1) * 10, w = Config.screen.width, h = 10 }) then
|
||||
return i, true
|
||||
local current_y = y
|
||||
for i = scroll_offset + 1, math.min(n, scroll_offset + visible_count) do
|
||||
local item = items[i]
|
||||
local step = item.header and 8 or 10
|
||||
if not item.header then
|
||||
if Mouse.zone({ x = menu_x - 8, y = current_y, w = Config.screen.width, h = 10 }) then
|
||||
return i, true
|
||||
end
|
||||
end
|
||||
current_y = current_y + step
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ local RASTER_Y_BOT = 110
|
||||
|
||||
local AUTHORS = {
|
||||
"Mr. Zero - Zsolt Tasnadi",
|
||||
"Mr. One - Balazs Tari",
|
||||
"Mr. One - Ballz",
|
||||
"Mr. Two - Zoltan Timar",
|
||||
"Mr. Three - Bela Mezo",
|
||||
}
|
||||
|
||||
@@ -5,89 +5,33 @@
|
||||
function EndWindow.draw()
|
||||
cls(Config.colors.black)
|
||||
|
||||
if Context._end.state == "choice" then
|
||||
local lines = {
|
||||
"This is not a workplace.",
|
||||
"This is a cycle.",
|
||||
"And if it is a cycle...",
|
||||
"it can be broken."
|
||||
}
|
||||
local cx = Config.screen.width / 2
|
||||
local name = Context.player_name or "AAA"
|
||||
local code = CodeGenerator.encrypt(name)
|
||||
|
||||
local y = 40
|
||||
for _, line in ipairs(lines) do
|
||||
Print.text_center(line, Config.screen.width / 2, y, Config.colors.white)
|
||||
y = y + 10
|
||||
end
|
||||
Print.text_center("~ GOOD ENDING ~", cx, 8, Config.colors.light_blue)
|
||||
Print.text_center("Congratulations, " .. name .. "!", cx, 20, Config.colors.white)
|
||||
|
||||
y = y + 20
|
||||
local yes_color = Context._end.selection == 1 and Config.colors.light_blue or Config.colors.white
|
||||
local no_color = Context._end.selection == 2 and Config.colors.light_blue or 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)
|
||||
|
||||
local yes_text = (Context._end.selection == 1 and "> YES" or " YES")
|
||||
local no_text = (Context._end.selection == 2 and "> NO" or " NO")
|
||||
Print.text_center("Write it down!", cx, 70, Config.colors.item)
|
||||
|
||||
local centerX = Config.screen.width / 2
|
||||
Print.text(yes_text, centerX - 40, y, yes_color)
|
||||
Print.text(no_text, centerX + 10, y, no_color)
|
||||
elseif Context._end.state == "ending" then
|
||||
local cx = Config.screen.width / 2
|
||||
local name = Context.player_name or "AAA"
|
||||
local code = CodeGenerator.encrypt(name)
|
||||
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("~ GOOD ENDING ~", cx, 8, Config.colors.light_blue)
|
||||
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
|
||||
Print.text_center("Press Z to return to menu", cx, 116, Config.colors.dark_grey)
|
||||
end
|
||||
|
||||
--- Updates the end screen logic.
|
||||
--- @within EndWindow
|
||||
function EndWindow.update()
|
||||
if Context._end.state == "choice" then
|
||||
if Input.left() or Input.up() then
|
||||
if Context._end.selection == 2 then
|
||||
Audio.sfx_beep()
|
||||
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
|
||||
if Input.select() then
|
||||
Context.reset()
|
||||
Window.set_current("menu")
|
||||
MenuWindow.refresh_menu_items()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,6 +5,7 @@ local _anim = 0
|
||||
local _menu_max_w = 0
|
||||
local ANIM_SPEED = 2.5
|
||||
local HEADER_H = 28
|
||||
MenuWindow._scroll_offset = 0
|
||||
|
||||
--- Calculates the animated x position of the menu block.
|
||||
--- @within MenuWindow
|
||||
@@ -45,6 +46,17 @@ function MenuWindow.draw_norman()
|
||||
spr(305, nx + 32, ny + 64, Config.colors.transparent, 4)
|
||||
end
|
||||
|
||||
--- Adjusts _scroll_offset so the selected item is within the visible window.
|
||||
--- @within MenuWindow
|
||||
function MenuWindow.ensure_visible()
|
||||
local sel = Context.current_menu_item
|
||||
if sel <= MenuWindow._scroll_offset then
|
||||
MenuWindow._scroll_offset = sel - 1
|
||||
elseif sel > MenuWindow._scroll_offset + 5 then
|
||||
MenuWindow._scroll_offset = sel - 5
|
||||
end
|
||||
end
|
||||
|
||||
--- Draws the menu window.
|
||||
--- @within MenuWindow
|
||||
function MenuWindow.draw()
|
||||
@@ -56,9 +68,19 @@ function MenuWindow.draw()
|
||||
MenuWindow.draw_norman()
|
||||
end
|
||||
|
||||
local menu_h = #_menu_items * 10
|
||||
local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 10 - menu_h) / 2)
|
||||
UI.draw_menu(_menu_items, Context.current_menu_item, MenuWindow.calc_menu_x(), y, false)
|
||||
local menu_x = MenuWindow.calc_menu_x()
|
||||
local arrow_cx = math.floor(menu_x + _menu_max_w / 2)
|
||||
local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 50) / 2)
|
||||
|
||||
if MenuWindow._scroll_offset > 0 then
|
||||
Print.text_center("^", arrow_cx, y - 8, Config.colors.light_blue)
|
||||
end
|
||||
|
||||
UI.draw_menu(_menu_items, Context.current_menu_item, menu_x, y, false, MenuWindow._scroll_offset, 5)
|
||||
|
||||
if MenuWindow._scroll_offset + 5 < #_menu_items then
|
||||
Print.text_center("v", arrow_cx, y + 52, Config.colors.light_blue)
|
||||
end
|
||||
|
||||
local ttg_text = "TTG"
|
||||
local ttg_w = print(ttg_text, 0, -10, 0, false, 1, false)
|
||||
@@ -72,8 +94,8 @@ function MenuWindow.update()
|
||||
_anim = math.min(1, _anim + ANIM_SPEED * Context.delta_time)
|
||||
end
|
||||
|
||||
local menu_h = #_menu_items * 10
|
||||
local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 10 - menu_h) / 2)
|
||||
local menu_x = MenuWindow.calc_menu_x()
|
||||
local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 50) / 2)
|
||||
|
||||
if _click_timer > 0 then
|
||||
_click_timer = _click_timer - Context.delta_time
|
||||
@@ -87,8 +109,9 @@ function MenuWindow.update()
|
||||
return
|
||||
end
|
||||
|
||||
local new_item, mouse_confirmed = UI.update_menu(_menu_items, Context.current_menu_item, MenuWindow.calc_menu_x(), y, false)
|
||||
local new_item, mouse_confirmed = UI.update_menu(_menu_items, Context.current_menu_item, menu_x, y, false, MenuWindow._scroll_offset, 5)
|
||||
Context.current_menu_item = new_item
|
||||
MenuWindow.ensure_visible()
|
||||
|
||||
if mouse_confirmed then
|
||||
Audio.sfx_select()
|
||||
@@ -186,6 +209,12 @@ function MenuWindow.ascend_debug()
|
||||
GameWindow.set_state("ascend_debug")
|
||||
end
|
||||
|
||||
--- Triggers the Level Up flash animation for testing.
|
||||
--- @within MenuWindow
|
||||
function MenuWindow.level_up_flash()
|
||||
Ascension.start_flash()
|
||||
end
|
||||
|
||||
--- Refreshes the list of menu items based on current game state.
|
||||
--- @within MenuWindow
|
||||
function MenuWindow.refresh_menu_items()
|
||||
@@ -200,6 +229,8 @@ function MenuWindow.refresh_menu_items()
|
||||
table.insert(_menu_items, {label = "Credits", decision = MenuWindow.credits})
|
||||
|
||||
if Context.test_mode then
|
||||
table.insert(_menu_items, {label = "Debug Menu", header = true})
|
||||
table.insert(_menu_items, {label = "Level Up Flash", decision = MenuWindow.level_up_flash})
|
||||
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})
|
||||
@@ -212,11 +243,14 @@ function MenuWindow.refresh_menu_items()
|
||||
|
||||
_menu_max_w = 0
|
||||
for _, item in ipairs(_menu_items) do
|
||||
local w = print(item.label, 0, -10, 0, false, 1, false)
|
||||
if w > _menu_max_w then _menu_max_w = w end
|
||||
if not item.header then
|
||||
local w = print(item.label, 0, -10, 0, false, 1, false)
|
||||
if w > _menu_max_w then _menu_max_w = w end
|
||||
end
|
||||
end
|
||||
|
||||
Context.current_menu_item = 1
|
||||
MenuWindow._scroll_offset = 0
|
||||
_click_timer = 0
|
||||
_anim = 0
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user