Compare commits

..

7 Commits

Author SHA1 Message Date
bb66f14bfa Add the street background and update the office.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-19 22:28:10 +01:00
86a2ebcfa1 Merge branch 'feature/add-bedroom-to-screen1' of https://git.teletype.hu/games/impostor into feature/add-bedroom-to-screen1 2026-02-19 22:13:22 +01:00
cbfe14fc16 Add the street background. 2026-02-19 21:33:01 +01:00
8f58cd713e Makefile update
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-12 20:13:29 +01:00
6977800f6a Display Norman's room at Screen 1.
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-02-12 19:22:24 +01:00
68ccdb632a Add Norman's bedroom to Screen 1.
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-02-12 19:21:11 +01:00
df085da8ff Add the map to the asset types for export-import.
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-02-12 18:58:24 +01:00
136 changed files with 1497 additions and 10181 deletions

8
.gitignore vendored
View File

@@ -1,10 +1,2 @@
.claude
.local
impostor.lua
impostor.original.lua
prompts
docs
minify.lua
*.tic
*.zip
NOTES_*

View File

@@ -1,93 +0,0 @@
-- .luacheckrc
-- Configuration for luacheck
globals = {
"AsciiArt",
"Ascension",
"AscendDebugWindow",
"Audio",
"AudioTestWindow",
"BriefIntroWindow",
"CodeGenerator",
"Config",
"CommuteGlitch",
"Context",
"ContextDebug",
"ContinuedWindow",
"ControlsWindow",
"CreditsWindow",
"Day",
"Decision",
"Discussion",
"DiscussionWindow",
"EndWindow",
"Focus",
"GameOverWindow",
"GameWindow",
"Glitch",
"Input",
"Map",
"MapBedroom",
"MenuWindow",
"Meter",
"Minigame",
"MinigameButtonMashWindow",
"MinigameDDRWindow",
"MinigameRhythmWindow",
"Mouse",
"MysteriousManScreen",
"PlayerNameWindow",
"PopupWindow",
"Print",
"RLE",
"Screen",
"Songs",
"Sprite",
"TIC",
"TTGIntroWindow",
"TextInput",
"Timer",
"TitleIntroWindow",
"Trigger",
"UI",
"Util",
"WalkingToOfficeScreen",
"Window",
"beats_to_pattern",
"btnp",
"circb",
"circ",
"cls",
"exit",
"frame_from_beat",
"index_menu",
"keyp",
"line",
"map",
"mouse",
"mget",
"mset",
"music",
"musicator_generate_pattern",
"pix",
"poke4",
"print",
"rect",
"rectb",
"sfx",
"spr",
"time",
"trace",
"tri",
}
-- Exclude certain warnings globally
exclude_warnings = {
"undefined_global", -- Will be covered by 'globals' table
"redefined_loop_variable", -- Common in Lua for iterators
}
-- Options for unused variables
std = "lua51" -- Assuming Lua 5.1, common for TIC-80

21
.vscode/settings.json vendored
View File

@@ -14,21 +14,6 @@
"inc/?.lua"
],
"Lua.diagnostics.disable": [
"undefined-global",
"undefined-doc-param",
"undefined-doc-name",
"luadoc-miss-param-name"
],
"python.autoComplete.extraPaths": [
"${workspaceFolder}/sources/poky/bitbake/lib",
"${workspaceFolder}/sources/poky/meta/lib"
],
"python.analysis.extraPaths": [
"${workspaceFolder}/sources/poky/bitbake/lib",
"${workspaceFolder}/sources/poky/meta/lib"
],
"files.associations": {
"*.conf": "bitbake",
"*.inc": "bitbake"
},
}
"undefined-global"
]
}

34
.vscode/tasks.json vendored
View File

@@ -1,34 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Run TIC80",
"type": "shell",
"command": "tic80 --fs=. impostor.lua",
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "Build & Run TIC80",
"type": "shell",
"command": "make clean && make build && tic80 --fs=. impostor.lua",
"problemMatcher": []
},
{
"label": "Export assets",
"type": "shell",
"command": "make export_assets",
"problemMatcher": []
},
{
"label": "Make build",
"type": "shell",
"command": "make build"
}
]
}

View File

@@ -5,32 +5,7 @@ steps:
- 'apk add --no-cache make'
- 'make ci-version'
- name: lint
image: alpine
commands:
- 'apk add --no-cache make lua5.4 lua5.4-dev luarocks gcc musl-dev'
- 'ln -sf /usr/bin/lua5.4 /usr/bin/lua'
- 'ln -sf /usr/bin/luarocks-5.4 /usr/bin/luarocks'
- 'luarocks install luacheck'
- 'make ci-lint'
- name: minify
image: alpine
commands:
- 'apk add --no-cache make lua5.4 curl'
- 'ln -sf /usr/bin/lua5.4 /usr/bin/lua'
- 'make ci-minify'
- name: docs
image: alpine
commands:
- 'apk add --no-cache make lua5.4 lua5.4-dev luarocks gcc musl-dev zip'
- 'ln -sf /usr/bin/lua5.4 /usr/bin/lua'
- 'ln -sf /usr/bin/luarocks-5.4 /usr/bin/luarocks'
- 'luarocks install ldoc'
- 'make ci-docs'
- name: export
- name: build
image: git.teletype.hu/internal/tic80pro:latest
environment:
XDG_RUNTIME_DIR: /tmp
@@ -48,14 +23,14 @@ steps:
from_secret: droparea_ssh_password
commands:
- 'apk add --no-cache make openssh-client sshpass'
- 'make ci-artifact'
- 'make ci-upload'
- name: update
image: alpine
environment:
UPDATE_SERVER: https://games.teletype.hu
UPDATE_SERVER: https://games.vps.teletype.hu
UPDATE_SECRET:
from_secret: update_secret_key
commands:
- 'apk add --no-cache make curl'
- 'make ci-update'
- 'make ci-update'

148
CLAUDE.md
View File

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

137
GEMINI.md
View File

@@ -1,35 +1,3 @@
# 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:
@@ -83,107 +51,4 @@ Based on the analysis of `impostor.lua`, the following regularities and conventi
## Agent Directives
- **Git Operations:** In the future, do not perform `git add` or `git commit` operations. This responsibility will be handled by the user.
---
# Impostor Minigame Documentation
This document provides comprehensive documentation for all three minigames implemented in the Impostor game: Button Mash, Rhythm, and DDR (Dance Dance Revolution).
## Table of Contents
- [Overview](#overview)
- [Button Mash Minigame](#button-mash-minigame)
- [Rhythm Minigame](#rhythm-minigame)
- [DDR Minigame](#ddr-minigame)
- [Integration Guide](#integration-guide)
- [Common Features](#common-features)
---
## Overview
The Impostor game includes three interactive minigames that can be triggered during gameplay. Each minigame presents a unique challenge that the player must complete to progress. All minigames feature:
- **Overlay rendering** - Minigame render over the current game window
- **Progress tracking** - Visual indicators show completion status
- **Return mechanism** - Automatic return to the calling window upon completion
- **Visual feedback** - Button presses and hits are visually indicated
---
## Button Mash Minigame
**File**: `inc/window/window.minigame.mash.lua`
### Description
A fast-paced minigame where the player must repeatedly press the Z button to fill a progress bar. The bar automatically degrades over time, and the degradation rate increases as the bar fills up, creating increasing difficulty.
### Gameplay Mechanics
- **Objective**: Fill the progress bar to 100% by pressing Z repeatedly
- **Challenge**: The bar degrades automatically, with degradation increasing as it fills
- **Win Condition**: Bar reaches 100%
- **No Fail State**: Player can keep trying until successful
### Visual Elements
#### Simple sketch
![button_mash.png](/button_mash.png)
```
Progress Bar (200x12px at 20,10):
┌────────────────────────────────────┐
│████████████████░░░░░░░░░░░░░░ 45%│
└────────────────────────────────────┘
Button Indicator (Bottom):
╔═══╗
║ Z ║ ← Press repeatedly!
╚═══╝
Text: "MASH Z!"
```
#### Configuration Parameters
```lua
bar_fill = 0 -- Current fill level (0-100)
max_fill = 100 -- Target fill level
fill_per_press = 8 -- Points gained per button press
base_degradation = 0.15 -- Base degradation per frame
degradation_multiplier = 0.006 -- Increases degradation with bar fill
button_press_duration = 8 -- Visual feedback duration (frames)
```
#### Usage Example
```lua
-- Start button mash minigame
MinigameButtonMashWindow.start(WINDOW_GAME)
-- Or from any window
MinigameButtonMashWindow.start(WINDOW_POPUP)
```
#### Color Coding
- **Green**: Low fill (0-33%)
- **Medium (NPC color)**: Medium fill (34-66%)
- **Yellow (Item color)**: High fill (67-100%)
---
## Rhythm Minigame
**File**: `inc/window/window.minigame.rhythm.lua`
### Description
A timing-based minigame where the player must press Z when a moving line crosses a green target zone. The target zone shrinks with each successful hit, making the game progressively more challenging.
### Gameplay Mechanics
- **Objective**: Score 10 points by timing button presses correctly
- **Git Operations:** In the future, do not perform `git add` or `git commit` operations. This responsibility will be handled by the user.

186
Makefile
View File

@@ -6,22 +6,15 @@ PROJECT = impostor
ORDER = $(PROJECT).inc
OUTPUT = $(PROJECT).lua
OUTPUT_ORIGINAL = $(PROJECT).original.lua
OUTPUT_ZIP = $(PROJECT).html.zip
OUTPUT_TIC = $(PROJECT).tic
MINIFY = minify.lua
MINIFY_URL = https://raw.githubusercontent.com/ztimar31/lua-minify-tic80/refs/heads/master/minify.lua
SRC_DIR = inc
SRC = $(shell sed 's|^|$(SRC_DIR)/|' $(ORDER))
ASSETS_LUA = inc/meta/meta.assets.lua
ASSETS_DIR = assets
ASSET_TYPES = tiles sprites sfx music
LINT_TMP_LUA := /tmp/_lint_combined.lua
LINT_TMP_MAP := /tmp/_lint_map.txt
ASSET_TYPES = map tiles sprites sfx music
# CI/CD variables
VERSION_FILE = .version
@@ -30,26 +23,20 @@ DROPAREA_HOST ?= vps.teletype.hu
DROPAREA_PORT ?= 2223
DROPAREA_TARGET_PATH ?= /home/drop
DROPAREA_USER ?= drop
UPDATE_SERVER ?= https://games.teletype.hu
UPDATE_SERVER ?= https://games.vps.teletype.hu
all: build
build:
build: $(OUTPUT)
$(OUTPUT): $(SRC) $(ORDER)
@rm -f $(OUTPUT)
@sed 's/\r$$//' $(ORDER) | while read f; do \
@while read f; do \
cat "$(SRC_DIR)/$$f" >> $(OUTPUT); \
echo "" >> $(OUTPUT); \
done
done < $(ORDER)
download-minify:
@test -f $(MINIFY) || { echo "==> Downloading $(MINIFY)"; curl -fsSL $(MINIFY_URL) -o $(MINIFY); }
minify: build download-minify
@echo "==> Minifying $(OUTPUT)"
@cp $(OUTPUT) $(OUTPUT_ORIGINAL)
@lua $(MINIFY) minify $(OUTPUT_ORIGINAL) > $(OUTPUT)
export: minify
export: build
@if [ -z "$(VERSION)" ]; then \
echo "ERROR: VERSION not set!"; \
exit 1; \
@@ -68,10 +55,10 @@ export: minify
@ls -lh $(PROJECT)-$(VERSION).* $(PROJECT).tic $(PROJECT).html.zip 2>/dev/null || true
watch:
$(MAKE) build
fswatch -o $(SRC_DIR) $(ORDER) assets | while read; do $(MAKE) build; done
make build
fswatch -o $(SRC_DIR) $(ORDER) assets | while read; do make build; done
import_assets: build
import_assets: $(OUTPUT)
@TIC_CMD="load $(OUTPUT) &"; \
for t in $(ASSET_TYPES); do \
for f in $(ASSETS_DIR)/$$t/*.png; do \
@@ -89,69 +76,6 @@ define f_export_asset_awk
cat $(2) | awk '/-- <$(1)>/,/<\/$(1)>/' >> $(3)
endef
lint:
@echo "==> Merging..."
@rm -f $(LINT_TMP_LUA) $(LINT_TMP_MAP)
@touch $(LINT_TMP_LUA)
@line=1; \
while IFS= read -r f || [ -n "$$f" ]; do \
f=$$(printf '%s' "$$f" | tr -d '\r'); \
[ -z "$$f" ] && continue; \
before=$$(wc -l < $(LINT_TMP_LUA)); \
cat "$(SRC_DIR)/$$f" >> $(LINT_TMP_LUA); \
printf '\n' >> $(LINT_TMP_LUA); \
after=$$(wc -l < $(LINT_TMP_LUA)); \
linecount=$$((after - before)); \
echo "$$line $$linecount $(SRC_DIR)/$$f" >> $(LINT_TMP_MAP); \
line=$$((line + linecount)); \
done < $(ORDER)
@echo "==> luacheck..."
@LINT_OUTPUT=$$(luacheck --no-max-line-length $(LINT_TMP_LUA) 2>&1 | awk -v map=$(LINT_TMP_MAP) ' \
BEGIN { \
NR_map = 0; \
while ((getline line < map) > 0) { \
n = split(line, a, " "); \
start[NR_map] = a[1]+0; \
count[NR_map] = a[2]+0; \
fname[NR_map] = a[3]; \
NR_map++; \
} \
} \
/^[^:]+:[0-9]+:[0-9]+:/ { \
colon1 = index($$0, ":"); \
rest1 = substr($$0, colon1+1); \
colon2 = index(rest1, ":"); \
absline = substr(rest1, 1, colon2-1) + 0; \
rest2 = substr(rest1, colon2+1); \
colon3 = index(rest2, ":"); \
col = substr(rest2, 1, colon3-1); \
rest = substr(rest2, colon3); \
found = 0; \
for (i = 0; i < NR_map; i++) { \
end_line = start[i] + count[i] -1; \
if (absline >= start[i] && absline <= end_line) { \
relline = absline - start[i] + 1; \
print fname[i] ":" relline ":" col ":" rest; \
found = 1; \
break; \
} \
} \
if (!found) print $$0; \
next; \
} \
{ print } \
'); \
echo "$$LINT_OUTPUT"; \
NUM_ISSUES=$$(echo "$$LINT_OUTPUT" | grep -cE "^[^:]+:[0-9]+:[0-9]+:"); \
if [ "$$NUM_ISSUES" -gt 0 ]; then \
echo "Total: $$NUM_ISSUES issue(s) found, commit aborted."; \
exit 1; \
else \
echo "Checking /tmp/_lint_combined.lua OK"; \
echo "Total: 0 warnings / 0 errors in 1 file"; \
fi
@rm -f $(LINT_TMP_LUA) $(LINT_TMP_MAP)
export_assets:
# $(OUTPUT) would be a circular dependency
@test -e $(OUTPUT)
@@ -164,47 +88,14 @@ export_assets:
@$(call f_export_asset_awk,MAP,$(OUTPUT),$(ASSETS_LUA))
@$(call f_export_asset_awk,SFX,$(OUTPUT),$(ASSETS_LUA))
@$(call f_export_asset_awk,WAVES,$(OUTPUT),$(ASSETS_LUA))
@$(call f_export_asset_awk,PATTERNS,$(OUTPUT),$(ASSETS_LUA))
@$(call f_export_asset_awk,TRACKS,$(OUTPUT),$(ASSETS_LUA))
clean:
@rm -f $(PROJECT)-*.tic $(PROJECT)-*.html.zip $(PROJECT)-*-docs.zip $(PROJECT)-docs.zip $(OUTPUT) $(OUTPUT_ORIGINAL)
@rm -f $(PROJECT)-*.tic $(PROJECT)-*.html.zip $(OUTPUT)
@echo "==> Cleaned build artifacts"
install_precommit_hook:
@echo "Installing Git pre-commit hook (lint check)..."
@mkdir -p .git/hooks
@printf '#!/bin/bash\n' > .git/hooks/pre-commit
@printf 'echo "Running lint before commit..."\n' >> .git/hooks/pre-commit
@printf 'make lint\n' >> .git/hooks/pre-commit
@printf 'if [ $$? -ne 0 ]; then\n' >> .git/hooks/pre-commit
@printf ' echo "Lint failed! Commit aborted."\n' >> .git/hooks/pre-commit
@printf ' exit 1\n' >> .git/hooks/pre-commit
@printf 'fi\n' >> .git/hooks/pre-commit
@chmod +x .git/hooks/pre-commit
@echo "Pre-commit hook installed successfully."
docs: build
@echo "==> Checking for ldoc..."
@if ! command -v ldoc &> /dev/null; then \
echo "ldoc not found, attempting to install with luarocks..."; \
if command -v luarocks &> /dev/null; then \
luarocks install ldoc; \
else \
echo "Error: luarocks not found. Please install luarocks and then ldoc manually."; \
exit 1; \
fi; \
fi
@echo "==> Running ldoc..."
@ldoc ${OUTPUT} -d docs
@echo "==> Documentation generated."
# -----------------------------------------
# CI/CD Pipeline targets
# -----------------------------------------
# CI/CD Targets
ci-version:
@VERSION=$$(sed -n "s/^-- version: //p" inc/meta/meta.header.lua | head -n 1 | tr -d "[:space:]"); \
@VERSION=$$(sed -n "s/^-- version: //p" inc-meta-meta.header.lua | head -n 1 | tr -d "[:space:]"); \
BRANCH=$${CI_COMMIT_BRANCH:-$${WOODPECKER_BRANCH}}; \
BRANCH=$$(echo "$$BRANCH" | tr '/' '-'); \
if [ "$$BRANCH" != "main" ] && [ "$$BRANCH" != "master" ] && [ -n "$$BRANCH" ]; then \
@@ -213,48 +104,33 @@ ci-version:
echo "VERSION is: $$VERSION"; \
echo $$VERSION > $(VERSION_FILE)
ci-lint: lint
ci-minify: minify
ci-docs:
@VERSION=$$(cat $(VERSION_FILE)); \
echo "==> Generating docs from $(OUTPUT_ORIGINAL)"; \
ldoc $(OUTPUT_ORIGINAL) -d docs; \
echo "==> Zipping docs for version $$VERSION"; \
(cd docs && zip -r ../$(PROJECT)-$$VERSION-docs.zip .); \
cp $(PROJECT)-$$VERSION-docs.zip $(PROJECT)-docs.zip; \
echo "==> Docs zip created"
ci-export:
@VERSION=$$(cat $(VERSION_FILE)); \
echo "==> Exporting HTML for version $$VERSION"; \
tic80 --cli --skip --fs=. \
--cmd="load $(OUTPUT) & save $(PROJECT)-$$VERSION & export html $(PROJECT)-$$VERSION.html & exit"; \
if [ -f "$(PROJECT)-$$VERSION.tic" ]; then \
cp $(PROJECT)-$$VERSION.tic $(PROJECT).tic; \
fi; \
if [ -f "$(PROJECT)-$$VERSION.html.zip" ]; then \
cp $(PROJECT)-$$VERSION.html.zip $(PROJECT).html.zip; \
fi; \
echo "==> Generated files:"; \
ls -lh $(PROJECT)-$$VERSION.* $(PROJECT).tic $(PROJECT).html.zip 2>/dev/null || true
echo "==> Building and exporting version $$VERSION"; \
$(MAKE) export VERSION=$$VERSION
ci-artifact:
ci-upload:
@VERSION=$$(cat $(VERSION_FILE)); \
echo "==> Uploading artifacts for version $$VERSION"; \
ls -lh $(PROJECT)-$$VERSION.* $(PROJECT).tic $(PROJECT).html.zip 2>/dev/null || true; \
cp $(PROJECT).lua $(PROJECT)-$$VERSION.lua; \
FILE_LUA=$(PROJECT)-$$VERSION.lua; \
FILE_TIC=$(PROJECT)-$$VERSION.tic; \
FILE_HTML_ZIP=$(PROJECT)-$$VERSION.html.zip; \
SCP_TARGET="$(DROPAREA_USER)@$(DROPAREA_HOST):$(DROPAREA_TARGET_PATH)/"; \
sshpass -p "$(DROPAREA_SSH_PASSWORD)" scp -o StrictHostKeyChecking=no -P $(DROPAREA_PORT) \
$(PROJECT)-$$VERSION.lua \
$(PROJECT)-$$VERSION.tic \
$(PROJECT)-$$VERSION.html.zip \
$(PROJECT)-$$VERSION-docs.zip \
$$SCP_TARGET
sshpass -p "$(DROPAREA_SSH_PASSWORD)" scp -o StrictHostKeyChecking=no -P $(DROPAREA_PORT) $$FILE_LUA $$FILE_TIC $$FILE_HTML_ZIP $$SCP_TARGET
ci-update:
@VERSION=$$(cat $(VERSION_FILE)); \
echo "==> Triggering update for version $$VERSION"; \
curl "$(UPDATE_SERVER)/update?secret=$(UPDATE_SECRET)&name=$(PROJECT)&platform=tic80&version=$$VERSION"
.PHONY: all build download-minify minify export watch import_assets export_assets clean lint install_precommit_hook docs ci-version ci-lint ci-minify ci-docs ci-export ci-artifact ci-update
.PHONY: all build export watch import_assets export_assets clean ci-version ci-export ci-upload ci-update
#-- <WAVES>
#-- 000:224578acdeeeeddcba95434567653100
#-- </WAVES>
#
#-- <SFX>
#-- 000:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000
#-- </SFX>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 480 B

View File

@@ -1 +0,0 @@
0404005f574fc2c3c71d2b53fff1e8ab52367e2553ffa30000875129adff83769c00e436fa77a8ff004dc3c3c7ffccaa

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -1,97 +1,19 @@
meta/meta.header.lua
init/init.module.lua
init/init.modules.lua
init/init.config.lua
init/init.windows.lua
init/init.context.lua
system/system.util.lua
system/system.print.lua
entity/entity.npc.lua
entity/entity.item.lua
system/system.input.lua
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
logic/logic.timer.lua
logic/logic.trigger.lua
logic/logic.minigame.lua
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
audio/audio.songs.lua
sprite/sprite.manager.lua
sprite/sprite.norman.lua
sprite/sprite.norman_echo.lua
sprite/sprite.sumphore.lua
sprite/sprite.pizza_vendor.lua
sprite/sprite.dev_boy.lua
sprite/sprite.dev_buddy.lua
sprite/sprite.dev_extrovert.lua
sprite/sprite.dev_girl.lua
sprite/sprite.dev_guard.lua
sprite/sprite.dev_guru.lua
sprite/sprite.dev_hr_girl.lua
sprite/sprite.dev_introvert.lua
sprite/sprite.dev_operator.lua
sprite/sprite.dev_project_manager.lua
sprite/sprite.matrix_architect.lua
sprite/sprite.matrix_neo.lua
sprite/sprite.matrix_oraculum.lua
sprite/sprite.matrix_trinity.lua
decision/decision.manager.lua
decision/decision.go_to_home.lua
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_walking_to_home.lua
decision/decision.go_to_sleep.lua
decision/decision.do_work.lua
decision/decision.have_a_coffee.lua
decision/decision.sumphore_discussion.lua
decision/decision.talk_to_truth.lua
discussion/discussion.sumphore.lua
discussion/discussion.coworker.lua
discussion/discussion.commute_glitch.lua
decision/decision.eating_fast_food.lua
discussion/discussion.pizza_vendor.lua
map/map.manager.lua
map/map.bedroom.lua
map/map.street.lua
map/map.office.lua
screen/screen.manager.lua
screen/screen.home.lua
screen/screen.toilet.lua
screen/screen.walking_to_office.lua
screen/screen.office.lua
screen/screen.walking_to_home.lua
screen/screen.work.lua
screen/screen.mysterious_man.lua
window/window.manager.lua
window/window.register.lua
window/window.gameover.lua
window/window.end.lua
window/window.intro.title.lua
window/window.intro.ttg.lua
window/window.intro.brief.lua
window/window.splash.lua
window/window.intro.lua
window/window.menu.lua
window/window.controls.lua
window/window.audiotest.lua
window/window.ascend_debug.lua
window/window.configuration.lua
window/window.popup.lua
window/window.minigame.mash.lua
window/window.minigame.rhythm.lua
window/window.minigame.ddr.lua
window/window.discussion.lua
window/window.continued.lua
window/window.credits.lua
window/window.player_name.lua
window/window.game.lua
system/system.main.lua
meta/meta.assets.lua

View File

@@ -1,744 +0,0 @@
local musicator_markov_model = {
model = {
["...|..."] = {
next = {
["..."] = 0.71111111111111,
["A-4"] = 0.0074074074074074,
["B-4"] = 0.0037037037037037,
["C-3"] = 0.011111111111111,
["C-4"] = 0.037037037037037,
["C-5"] = 0.11111111111111,
["C-6"] = 0.0037037037037037,
["D-4"] = 0.011111111111111,
["D-5"] = 0.018518518518519,
["E-4"] = 0.0074074074074074,
["E-5"] = 0.025925925925926,
["F-5"] = 0.0074074074074074,
["G-3"] = 0.0037037037037037,
["G-4"] = 0.022222222222222,
["G-5"] = 0.018518518518519
},
total = 270
},
["...|A-4"] = {
next = {
["..."] = 0.9,
["C-5"] = 0.1
},
total = 10
},
["...|A-5"] = {
next = {
["..."] = 1
},
total = 8
},
["...|B-4"] = {
next = {
["..."] = 1
},
total = 1
},
["...|B-5"] = {
next = {
["..."] = 1
},
total = 5
},
["...|C-3"] = {
next = {
["..."] = 0.66666666666667,
["C-5"] = 0.33333333333333
},
total = 3
},
["...|C-4"] = {
next = {
["..."] = 0.875,
["D-4"] = 0.083333333333333,
["E-4"] = 0.041666666666667
},
total = 24
},
["...|C-5"] = {
next = {
["..."] = 0.73333333333333,
["B-4"] = 0.033333333333333,
["C-5"] = 0.066666666666667,
["D-5"] = 0.13333333333333,
["E-5"] = 0.033333333333333
},
total = 60
},
["...|C-6"] = {
next = {
["..."] = 1
},
total = 1
},
["...|D-4"] = {
next = {
["..."] = 0.92857142857143,
["D-4"] = 0.071428571428571
},
total = 14
},
["...|D-5"] = {
next = {
["..."] = 0.80645161290323,
["C-5"] = 0.032258064516129,
["D-5"] = 0.032258064516129,
["E-5"] = 0.12903225806452
},
total = 31
},
["...|D-6"] = {
next = {
["..."] = 1
},
total = 3
},
["...|E-4"] = {
next = {
["..."] = 1
},
total = 19
},
["...|E-5"] = {
next = {
["..."] = 0.77777777777778,
["C-5"] = 0.022222222222222,
["D-5"] = 0.13333333333333,
["F-5"] = 0.066666666666667
},
total = 45
},
["...|F-3"] = {
next = {
["..."] = 1
},
total = 3
},
["...|F-4"] = {
next = {
["..."] = 0.8,
["D-4"] = 0.1,
["F-4"] = 0.1
},
total = 10
},
["...|F-5"] = {
next = {
["..."] = 0.66666666666667,
["D-5"] = 0.066666666666667,
["E-5"] = 0.066666666666667,
["G-5"] = 0.2
},
total = 15
},
["...|G-3"] = {
next = {
["..."] = 0.8,
["G-5"] = 0.2
},
total = 5
},
["...|G-4"] = {
next = {
["..."] = 0.95652173913043,
["E-4"] = 0.043478260869565
},
total = 23
},
["...|G-5"] = {
next = {
["..."] = 0.875,
["A-5"] = 0.0625,
["E-5"] = 0.0625
},
total = 16
},
["...|G-6"] = {
next = {
["..."] = 1
},
total = 1
},
["A-4|..."] = {
next = {
["..."] = 0.55555555555556,
["C-5"] = 0.33333333333333,
["D-5"] = 0.11111111111111
},
total = 9
},
["A-4|B-4"] = {
next = {
["C-5"] = 1
},
total = 2
},
["A-4|C-5"] = {
next = {
["..."] = 1
},
total = 1
},
["A-4|G-4"] = {
next = {
["F-4"] = 1
},
total = 2
},
["A-5|..."] = {
next = {
["..."] = 0.2,
["B-5"] = 0.1,
["E-4"] = 0.1,
["E-5"] = 0.4,
["F-5"] = 0.1,
["G-5"] = 0.1
},
total = 10
},
["A-5|G-5"] = {
next = {
["..."] = 0.33333333333333,
["A-5"] = 0.66666666666667
},
total = 3
},
["B-4|..."] = {
next = {
["A-4"] = 1
},
total = 1
},
["B-4|A-4"] = {
next = {
["G-4"] = 1
},
total = 2
},
["B-4|C-5"] = {
next = {
["..."] = 1
},
total = 2
},
["B-5|..."] = {
next = {
["..."] = 0.2,
["A-5"] = 0.2,
["D-6"] = 0.4,
["G-5"] = 0.2
},
total = 5
},
["C-3|..."] = {
next = {
["C-4"] = 1
},
total = 2
},
["C-3|C-5"] = {
next = {
["..."] = 0.33333333333333,
["C-3"] = 0.66666666666667
},
total = 3
},
["C-4|..."] = {
next = {
["..."] = 0.5,
["D-4"] = 0.125,
["E-4"] = 0.041666666666667,
["F-3"] = 0.041666666666667,
["G-3"] = 0.16666666666667,
["G-4"] = 0.083333333333333,
["G-5"] = 0.041666666666667
},
total = 24
},
["C-4|D-4"] = {
next = {
["E-4"] = 1
},
total = 2
},
["C-4|E-4"] = {
next = {
["..."] = 1
},
total = 1
},
["C-5|..."] = {
next = {
["..."] = 0.40677966101695,
["A-4"] = 0.067796610169492,
["C-5"] = 0.050847457627119,
["D-5"] = 0.20338983050847,
["E-5"] = 0.15254237288136,
["F-5"] = 0.016949152542373,
["G-4"] = 0.10169491525424
},
total = 59
},
["C-5|B-4"] = {
next = {
["A-4"] = 1
},
total = 2
},
["C-5|C-3"] = {
next = {
["C-5"] = 1
},
total = 2
},
["C-5|C-5"] = {
next = {
["..."] = 0.8,
["C-5"] = 0.2
},
total = 5
},
["C-5|D-5"] = {
next = {
["..."] = 0.3,
["C-5"] = 0.2,
["D-5"] = 0.1,
["E-5"] = 0.4
},
total = 10
},
["C-5|E-5"] = {
next = {
["..."] = 0.33333333333333,
["D-5"] = 0.33333333333333,
["G-5"] = 0.33333333333333
},
total = 3
},
["C-6|..."] = {
next = {
["A-5"] = 1
},
total = 1
},
["D-4|..."] = {
next = {
["..."] = 0.26666666666667,
["A-5"] = 0.066666666666667,
["C-4"] = 0.2,
["E-4"] = 0.4,
["F-3"] = 0.066666666666667
},
total = 15
},
["D-4|C-4"] = {
next = {
["..."] = 1
},
total = 2
},
["D-4|D-4"] = {
next = {
["..."] = 1
},
total = 1
},
["D-4|E-4"] = {
next = {
["F-4"] = 1
},
total = 2
},
["D-5|..."] = {
next = {
["..."] = 0.2258064516129,
["A-4"] = 0.032258064516129,
["A-5"] = 0.032258064516129,
["C-5"] = 0.2258064516129,
["E-5"] = 0.29032258064516,
["F-5"] = 0.096774193548387,
["G-5"] = 0.096774193548387
},
total = 31
},
["D-5|C-5"] = {
next = {
["..."] = 0.77777777777778,
["D-5"] = 0.22222222222222
},
total = 9
},
["D-5|D-5"] = {
next = {
["..."] = 0.5,
["E-5"] = 0.5
},
total = 2
},
["D-5|E-5"] = {
next = {
["..."] = 0.33333333333333,
["E-5"] = 0.11111111111111,
["F-5"] = 0.55555555555556
},
total = 9
},
["D-5|F-5"] = {
next = {
["E-5"] = 1
},
total = 1
},
["D-6|..."] = {
next = {
["B-5"] = 0.66666666666667,
["G-6"] = 0.33333333333333
},
total = 3
},
["E-4|..."] = {
next = {
["..."] = 0.19047619047619,
["B-5"] = 0.047619047619048,
["C-4"] = 0.14285714285714,
["D-4"] = 0.23809523809524,
["F-4"] = 0.19047619047619,
["G-4"] = 0.19047619047619
},
total = 21
},
["E-4|D-4"] = {
next = {
["C-4"] = 1
},
total = 2
},
["E-4|F-4"] = {
next = {
["G-4"] = 1
},
total = 2
},
["E-5|..."] = {
next = {
["..."] = 0.18604651162791,
["A-5"] = 0.046511627906977,
["C-5"] = 0.27906976744186,
["D-5"] = 0.2093023255814,
["E-5"] = 0.069767441860465,
["F-5"] = 0.093023255813953,
["G-4"] = 0.023255813953488,
["G-5"] = 0.093023255813953
},
total = 43
},
["E-5|C-5"] = {
next = {
["E-5"] = 1
},
total = 1
},
["E-5|D-5"] = {
next = {
["..."] = 0.125,
["C-5"] = 0.75,
["F-5"] = 0.125
},
total = 8
},
["E-5|E-5"] = {
next = {
["F-5"] = 1
},
total = 1
},
["E-5|F-5"] = {
next = {
["..."] = 0.5,
["E-5"] = 0.4,
["G-5"] = 0.1
},
total = 10
},
["E-5|G-5"] = {
next = {
["..."] = 0.5,
["F-5"] = 0.5
},
total = 2
},
["F-3|..."] = {
next = {
["C-4"] = 1
},
total = 3
},
["F-4|..."] = {
next = {
["D-4"] = 0.11111111111111,
["E-4"] = 0.44444444444444,
["G-4"] = 0.44444444444444
},
total = 9
},
["F-4|D-4"] = {
next = {
["..."] = 1
},
total = 1
},
["F-4|E-4"] = {
next = {
["D-4"] = 1
},
total = 2
},
["F-4|F-4"] = {
next = {
["..."] = 1
},
total = 1
},
["F-4|G-4"] = {
next = {
["A-4"] = 1
},
total = 2
},
["F-5|..."] = {
next = {
["..."] = 0.2,
["A-5"] = 0.066666666666667,
["D-5"] = 0.2,
["E-5"] = 0.46666666666667,
["G-5"] = 0.066666666666667
},
total = 15
},
["F-5|A-5"] = {
next = {
["G-5"] = 1
},
total = 1
},
["F-5|D-5"] = {
next = {
["..."] = 1
},
total = 1
},
["F-5|E-5"] = {
next = {
["..."] = 0.5,
["D-5"] = 0.16666666666667,
["F-5"] = 0.16666666666667,
["G-5"] = 0.16666666666667
},
total = 6
},
["F-5|G-5"] = {
next = {
["..."] = 0.75,
["A-5"] = 0.25
},
total = 4
},
["G-3|..."] = {
next = {
["C-4"] = 0.5,
["D-4"] = 0.25,
["F-3"] = 0.25
},
total = 4
},
["G-3|G-5"] = {
next = {
["..."] = 0.5,
["G-3"] = 0.5
},
total = 2
},
["G-4|..."] = {
next = {
["..."] = 0.090909090909091,
["A-4"] = 0.090909090909091,
["C-4"] = 0.045454545454545,
["C-5"] = 0.18181818181818,
["D-5"] = 0.045454545454545,
["E-4"] = 0.22727272727273,
["E-5"] = 0.045454545454545,
["F-4"] = 0.27272727272727
},
total = 22
},
["G-4|A-4"] = {
next = {
["B-4"] = 1
},
total = 2
},
["G-4|E-4"] = {
next = {
["..."] = 1
},
total = 1
},
["G-4|F-4"] = {
next = {
["E-4"] = 1
},
total = 2
},
["G-5|..."] = {
next = {
["..."] = 0.35,
["A-5"] = 0.05,
["B-5"] = 0.05,
["C-5"] = 0.05,
["D-4"] = 0.05,
["E-5"] = 0.25,
["F-5"] = 0.2
},
total = 20
},
["G-5|A-5"] = {
next = {
["..."] = 0.5,
["G-5"] = 0.5
},
total = 4
},
["G-5|E-5"] = {
next = {
["..."] = 1
},
total = 1
},
["G-5|F-5"] = {
next = {
["A-5"] = 1
},
total = 1
},
["G-5|G-3"] = {
next = {
["G-5"] = 1
},
total = 1
},
["G-6|..."] = {
next = {
["D-6"] = 1
},
total = 1
}
},
order = 2
}
local function musicator_unmake_key(k)
local result = {}
for t in string.gmatch(k, "[^|]+") do
result[#result + 1] = t
end
return result
end
local function musicator_count_notes(sequence)
local result = 0
for _,v in ipairs(sequence) do
if v ~= "..." then
result = result + 1
end
end
return result
end
local function musicator_generate_sequence(model_data, length)
local order = model_data.order
local model = model_data.model
-- random start key
local model_keys = {}
for k,_ in pairs(model) do
model_keys[#model_keys + 1] = k
end
local start_key = model_keys[math.ceil(math.random() * #model_keys)]
-- sequence starts with the start key
local seq = musicator_unmake_key(start_key)
-- generation loop
while musicator_count_notes(seq) < length do
local current_key = table.concat({table.unpack(seq, #seq - order + 1, #seq)}, "|") -- luacheck: ignore
local chosen = "..."
local key_data = model[current_key]
if key_data then
local r = math.random()
local prob_sum = 0.0
for new_note, new_prob in pairs(key_data.next) do
prob_sum = prob_sum + new_prob
if prob_sum < r then
chosen = new_note
end
end
end
-- print(current_key .. " --> " .. chosen)
seq[#seq+1] = chosen
end
return seq
end
local function musicator_row_to_frame(row, bpm, spd)
local frames_per_row = (150 * spd) / bpm
return math.floor(row * frames_per_row)
end
local function musicator_note_to_direction(note)
local subnote = note:sub(1,1)
local mapping = {
C="left",
D="up",
E="up",
F="right",
G="right",
A="down"
}
return mapping[subnote] or "up"
end
-- converts generated sequence to pattern that the ddr minigame can consume
local function musicator_sequence_to_pattern(sequence, bpm, spd)
local result = {}
for i, note in ipairs(sequence) do
if note ~= "..." then
local at_ms = musicator_row_to_frame(i, bpm, spd)
local direction = musicator_note_to_direction(note)
result[#result + 1] = { frame=at_ms, dir=direction, note=note }
end
end
return result
end
local function musicator_generate_pattern(length, bpm, spd)
return musicator_sequence_to_pattern(musicator_generate_sequence(musicator_markov_model, length), bpm, spd)
end

View File

@@ -1,100 +0,0 @@
--- @section Audio
Audio = {
music_playing = nil,
music_playing_tempo = nil,
}
--- Stops current music.
--- @within Audio
function Audio.music_stop()
music()
Audio.music_playing = nil
Audio.music_playing_tempo = nil
end
--- Plays track at optional speed. Doesn't restart if track and speed are unchanged.
--- @param track number Track index.
--- @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 = tempo
end
end
--- Plays main menu music.
--- @within Audio
function Audio.music_play_mainmenu() end
--- Plays mystery man music.
--- @within Audio
function Audio.music_play_mystery() Audio.music_play(2) end
--- Plays waking up music.
--- @within Audio
function Audio.music_play_wakingup() end
--- Plays room morning music.
--- @within Audio
function Audio.music_play_room_morning() end
--- Plays room street 1 music.
--- @within Audio
function Audio.music_play_room_street_1() end
--- Plays room street 2 music.
--- @within Audio
function Audio.music_play_room_street_2() end
--- Plays room music.
--- @within Audio
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(tempo)
Audio.music_play(0, tempo or -1)
end
--- Plays activity work music.
--- @within Audio
function Audio.music_play_activity_work() Audio.music_play(1) end
--- Plays select sound effect.
--- @within Audio
function Audio.sfx_select() sfx(17, 'C-7', 30) end
--- Plays deselect sound effect.
--- @within Audio
function Audio.sfx_deselect() sfx(18, 'C-7', 30) end
--- Plays beep sound effect.
--- @within Audio
function Audio.sfx_beep() sfx(19, 'C-6', 30) end
--- Plays success sound effect.
--- @within Audio
function Audio.sfx_success() sfx(16, 'C-7', 60) end
--- Plays bloop sound effect.
--- @within Audio
function Audio.sfx_bloop() sfx(21, 'C-3', 60) end
--- Plays alarm sound effect
--- @within Audio
function Audio.sfx_alarm() sfx(34, "C-5", 240) end
--- Plays drum sound effect.
--- @within Audio
function Audio.sfx_drum_low() sfx(61, "C-2") end
--- Plays drum sound effect.
--- @within Audio
function Audio.sfx_drum_high() sfx(61, "C-6") end
--- Plays sound effect for arrow hit
--- @within Audio
--- @param note string The note for the sound to play
function Audio.sfx_arrowhit(note) sfx(56, note) end

View File

@@ -1,173 +0,0 @@
--- @section Songs
-- DDR Arrow Spawn Patterns
-- Each song defines when arrows should spawn, synced to music beats
Songs = {
-- Example song pattern
test_song = {
name = "Test Song",
bpm = 120, -- Beats per minute (for reference)
fps = 60, -- Frames per second (TIC-80 default)
end_frame = 570, -- Frame when song ends (last note)
-- Arrow spawn pattern
-- Each entry defines when (in frames) and which direction arrow spawns
-- Formula: frame = (beat / bpm) * 60 * fps
-- For 120 BPM: 1 beat = 30 frames, 2 beats = 60 frames, etc.
pattern = {
-- Beat 1-4 (intro)
{frame = 30, dir = "left"},
{frame = 60, dir = "down"},
{frame = 90, dir = "up"},
{frame = 120, dir = "right"},
-- Beat 5-8 (faster)
{frame = 135, dir = "left"},
{frame = 150, dir = "right"},
{frame = 165, dir = "left"},
{frame = 180, dir = "right"},
-- Beat 9-12 (complex pattern)
{frame = 210, dir = "left"},
{frame = 210, dir = "right"}, -- simultaneous
{frame = 240, dir = "up"},
{frame = 240, dir = "down"}, -- simultaneous
{frame = 270, dir = "left"},
{frame = 300, dir = "right"},
-- Beat 13-16 (rapid sequence)
{frame = 330, dir = "left"},
{frame = 345, dir = "down"},
{frame = 360, dir = "up"},
{frame = 375, dir = "right"},
{frame = 390, dir = "left"},
{frame = 405, dir = "down"},
{frame = 420, dir = "up"},
{frame = 435, dir = "right"},
-- Beat 17-20 (finale)
{frame = 465, dir = "up"},
{frame = 465, dir = "down"},
{frame = 495, dir = "left"},
{frame = 495, dir = "right"},
{frame = 525, dir = "up"},
{frame = 540, dir = "down"},
{frame = 555, dir = "left"},
{frame = 570, dir = "right"}
}
},
test_song_2 = {
name = "Test Song 2",
bpm = 120, -- Beats per minute (for reference)
fps = 60, -- Frames per second (TIC-80 default)
end_frame = 570, -- Frame when song ends (last note)
-- Arrow spawn pattern
-- Each entry defines when (in frames) and which direction arrow spawns
-- Formula: frame = (beat / bpm) * 60 * fps
-- For 120 BPM: 1 beat = 30 frames, 2 beats = 60 frames, etc.
pattern = {
-- Beat 1-4 (intro)
{frame = 30, dir = "left"},
{frame = 60, dir = "down"},
{frame = 90, dir = "up"},
{frame = 120, dir = "right"},
-- Beat 5-8 (faster)
{frame = 135, dir = "left"},
{frame = 150, dir = "right"},
{frame = 165, dir = "left"},
{frame = 180, dir = "right"},
-- Beat 9-12 (complex pattern)
{frame = 210, dir = "left"},
{frame = 210, dir = "right"}, -- simultaneous
{frame = 240, dir = "up"},
{frame = 240, dir = "down"}, -- simultaneous
{frame = 270, dir = "left"},
{frame = 300, dir = "right"},
-- Beat 13-16 (rapid sequence)
{frame = 330, dir = "left"},
{frame = 345, dir = "down"},
{frame = 360, dir = "up"},
{frame = 375, dir = "right"},
{frame = 390, dir = "left"},
{frame = 405, dir = "down"},
{frame = 420, dir = "up"},
{frame = 435, dir = "right"},
-- Beat 17-20 (finale)
{frame = 465, dir = "up"},
{frame = 465, dir = "down"},
{frame = 495, dir = "left"},
{frame = 495, dir = "right"},
{frame = 525, dir = "up"},
{frame = 540, dir = "down"},
{frame = 555, dir = "left"},
{frame = 570, dir = "right"}
}
},
-- Random mode (no predefined pattern, spawns randomly)
random = {
name = "Random Mode",
bpm = 0, -- Not applicable for random mode
fps = 60,
end_frame = nil, -- No end frame for random mode
pattern = {} -- Empty, will spawn randomly in game
},
generated = {
name = "Markov Mode",
bpm = 150,
spd = 6,
fps = 60,
end_frame = nil, -- calculated
pattern = {}, -- generated
generated = true
}
}
--- Converts beats to frames.
--- @within Songs
--- @param beat number The beat number.
--- @param bpm number Beats per minute.
--- @param[opt] fps number Frames per second (default: 60).
--- @return number The corresponding frame number.
function frame_from_beat(beat, bpm, fps)
fps = fps or 60
local seconds_per_beat = 60 / bpm
local frames_per_beat = seconds_per_beat * fps
return math.floor(beat * frames_per_beat)
end
--- Converts beat notation to frame pattern.
--- @within Songs
--- @param beats table A table of beat data, e.g., {{1, "left"}, {2, "down"}}.
--- @param beats.1 number The beat number.
--- @param beats.2 string Arrow direction ("left", "down", "up", or "right").
--- @param bpm number Beats per minute.
--- @param[opt] fps number Frames per second (default: 60).
--- @return result table The generated pattern or nil. </br>
--- Fields: </br>
--- * frame (number) The frame number when the arrow should spawn.<br/>
--- * dir (string) Arrow direction ("left", "down", "up", or "right").<br/>
function beats_to_pattern(beats, bpm, fps)
fps = fps or 60
local pattern = {}
for _, beat_data in ipairs(beats) do
local beat = beat_data[1]
local dir = beat_data[2]
table.insert(pattern, {
frame = frame_from_beat(beat, bpm, fps),
dir = dir
})
end
return pattern
end
-- Example of creating a song using beat notation:
--[[
Songs.custom_song = {
name = "Custom Song",
bpm = 130,
fps = 60,
pattern = beats_to_pattern({
{1, "left"},
{2, "down"},
{3, "up"},
{4, "right"},
{4.5, "left"},
{5, "right"}
}, 130)
}
]]

View File

@@ -1,41 +0,0 @@
Decision.register({
id = "do_work",
label = "Do Work",
condition = function()
return (not CommuteGlitch.is_active()) or (CommuteGlitch.is_active() and CommuteGlitch.get_level() <= 4)
end,
handle = function()
Meter.hide()
Util.go_to_screen_by_id("work")
local modes_for_ascension_levels = {}
modes_for_ascension_levels[0] = "normal"
modes_for_ascension_levels[1] = "only_special"
modes_for_ascension_levels[2] = "only_left"
modes_for_ascension_levels[3] = "only_nothing"
modes_for_ascension_levels[4] = "normal"
local ddr_config = {
on_win = function(game_context)
if (game_context.special_mode_condition and Context.ascension.level == 1) then
Context.should_ascend = true
elseif (game_context.special_mode_condition and Context.ascension.level == 2) then
Context.should_ascend = true
elseif (game_context.special_mode_condition and Context.ascension.level == 3) then
Context.should_ascend = true
end
Meter.show()
Util.go_to_screen_by_id("office")
Window.set_current("game")
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,
})

View File

@@ -1,13 +0,0 @@
Decision.register({
id = "eating_fast_food",
label = "Eat Fast Food",
condition = function()
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
Discussion.start("pizza_vendor_disc", "game")
end,
})

View File

@@ -1,40 +0,0 @@
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 <= 6 then return false end
if g >= 7 then
return Context.talked_to_norman_echo and Context.talked_to_true_sumphore
end
end
return Context.have_been_to_office and Context.have_done_work_today
end,
handle = function()
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()
Day.increase()
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,
})
return
elseif CommuteGlitch.is_active() then
CommuteGlitch.reset()
end
Util.go_to_screen_by_id("home")
end,
})

View File

@@ -1,14 +0,0 @@
Decision.register({
id = "go_to_office",
label = "Go to Office",
condition = function()
return not (CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 6)
end,
handle = function()
if CommuteGlitch.is_active() then
CommuteGlitch.increment()
end
Util.go_to_screen_by_id("office")
end,
})

View File

@@ -1,33 +0,0 @@
Decision.register({
id = "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,
handle = function()
Meter.hide()
Day.increase()
MinigameRhythmWindow.start("game", {
focus_center_x = (Config.screen.width / 2) - 22,
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,
})
end,
})

View File

@@ -1,23 +0,0 @@
local function apply_home_toilet_meter_delta()
local max = Meter.get_max()
Meter.add("bm", -math.floor(max * 0.15))
Meter.add("ism", -math.floor(max * 0.10))
Meter.add("wpm", math.floor(max * 0.05))
end
Decision.register({
id = "go_to_toilet",
label = "Go to Toilet",
handle = function()
if not Context.have_done_work_today and not Context.toilet_meters_today_morning then
apply_home_toilet_meter_delta()
Context.toilet_meters_today_morning = true
elseif Context.have_been_to_office
and Context.have_done_work_today
and not Context.toilet_meters_today_evening then
apply_home_toilet_meter_delta()
Context.toilet_meters_today_evening = true
end
Util.go_to_screen_by_id("toilet")
end,
})

View File

@@ -1,12 +0,0 @@
Decision.register({
id = "go_to_truth",
label = "Go to Truth",
condition = function()
return CommuteGlitch.is_active() and CommuteGlitch.get_level() == 6
end,
handle = function()
CommuteGlitch.enter_truth()
Util.go_to_screen_by_id("office")
end,
})

View File

@@ -1,13 +0,0 @@
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,
})

View File

@@ -1,7 +0,0 @@
Decision.register({
id = "go_to_walking_to_office",
label = "Walk to office",
handle = function()
Util.go_to_screen_by_id("walking_to_office")
end,
})

View File

@@ -1,29 +0,0 @@
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"
if level >= 1 and level <= 5 then
local suffix = Context.have_done_work_today and ("_asc_" .. level) or ("_" .. level)
disc_id = "coworker_disc" .. suffix
elseif level == 6 then
if not Context.glitch_conversation_done_today and Context.glitch_conversation_count < 6 then
Context.glitch_conversation_done_today = true
Context.glitch_conversation_count = Context.glitch_conversation_count + 1
Glitch.show()
Discussion.start("coworker_disc_asc_6_" .. Context.glitch_conversation_count, "game")
return
end
local suffix = Context.have_done_work_today and ("_asc_5") or ("_5")
disc_id = "coworker_disc" .. suffix
elseif level == 7 then
local g = CommuteGlitch.get_level()
disc_id = "coworker_disc_cg_" .. g
end
Discussion.start(disc_id, "game")
end,
})

View File

@@ -1,168 +0,0 @@
--- @section Decision
local _decisions = {}
--- Registers a decision definition.
--- @within Decision
--- @param decision table The decision data table.
--- @param decision.id string Unique decision identifier.
--- @param decision.label string|function Display text for the decision, or a function returning it.
--- @param[opt] decision.condition function Returns true if decision is available. Defaults to always true.
--- @param[opt] decision.handle function Called when the decision is selected. Defaults to noop.
function Decision.register(decision)
if not decision or not decision.id then
trace("Error: Invalid decision object registered (missing id)!")
return
end
if not decision.label then
trace("Error: Invalid decision object registered (missing label)!")
return
end
if not decision.condition then
decision.condition = function() return true end
end
if not decision.handle then
decision.handle = function() end
end
if _decisions[decision.id] then
trace("Warning: Overwriting decision with id: " .. decision.id)
end
_decisions[decision.id] = decision
end
--- Gets the display label for a decision.
--- @within Decision
--- @param decision table The decision data table.
--- @return string result The resolved decision label.
function Decision.get_label(decision)
if not decision then return "" end
if type(decision.label) == "function" then
return decision.label() or ""
end
return decision.label or ""
end
--- Gets a decision by ID.
--- @within Decision
--- @param id string The ID of the decision.
--- @return table|nil result The decision table or nil. </br>
--- Fields: </br>
--- * id (string) Unique decision identifier.<br/>
--- * label (string) Display text for the decision.<br/>
--- * condition (function) Returns true if decision is available.<br/>
--- * handle (function) Called when the decision is selected.
function Decision.get_by_id(id)
return _decisions[id]
end
--- Gets all registered decisions.
--- @within Decision
--- @return result table A table of all registered decisions, indexed by their IDs. </br>
--- Fields: </br>
--- * id (string) Unique decision identifier.<br/>
--- * label (string) Display text for the decision.<br/>
--- * condition (function) Returns true if decision is available.<br/>
--- * handle (function) Called when the decision is selected.
function Decision.get_all()
return _decisions
end
--- Gets decision objects based on a screen's data.
--- @within Decision
--- @param screen_data table The data for the screen.
--- @param screen_data.decisions table Array of decision ID strings.
--- @return result table An array of decision objects relevant to the screen or nil. </br>
--- Fields: </br>
--- * id (string) Unique decision identifier.<br/>
--- * label (string) Display text for the decision.<br/>
--- * condition (function) Returns true if decision is available.<br/>
--- * handle (function) Called when the decision is selected.<br/>
function Decision.get_for_screen(screen_data)
if not screen_data or not screen_data.decisions then
return {}
end
local screen_decisions = {}
for _, decision_id in ipairs(screen_data.decisions) do
local decision = Decision.get_by_id(decision_id)
if decision then
table.insert(screen_decisions, decision)
end
end
return screen_decisions
end
--- Filters a list of decision objects based on their condition function.
--- @within Decision
--- @param decisions_list table A table of decision objects.
--- @return result table An array of decisions for which condition() is true or nil. </br>
--- Fields: </br>
--- * id (string) Unique decision identifier.<br/>
--- * label (string) Display text for the decision.<br/>
--- * condition (function) Returns true if decision is available.<br/>
--- * handle (function) Called when the decision is selected.<br/>
function Decision.filter_available(decisions_list)
local available = {}
for _, decision in ipairs(decisions_list) do
if decision and (not decision.condition or decision.condition()) then
table.insert(available, decision)
end
end
return available
end
--- Draws decision selector.
--- @within Decision
--- @param decisions table A table of decision items.<br/>
--- @param selected_decision_index number The index of the selected decision.<br/>
function Decision.draw(decisions, selected_decision_index)
local bar_height = 16
local bar_y = Config.screen.height - bar_height
rect(0, bar_y, Config.screen.width, bar_height, Config.colors.dark_grey)
if #decisions > 0 then
local selected_decision = decisions[selected_decision_index]
local decision_label = Decision.get_label(selected_decision)
local text_y = bar_y + 4
local left_arrow_color = Input.left() and Config.colors.white or Config.colors.orange
local right_arrow_color = Input.right() and Config.colors.white or Config.colors.orange
local left_arrow_contour_color = Input.left() and Config.colors.white or Config.colors.black
local right_arrow_contour_color = Input.right() and Config.colors.white or Config.colors.black
Print.text_center_contour("<", 6, text_y, left_arrow_color, false, 1, left_arrow_contour_color)
Print.text_center_contour(decision_label, Config.screen.width / 2, text_y, Config.colors.orange)
Print.text_center_contour(">", Config.screen.width - 6, text_y, right_arrow_color, false, 1, right_arrow_contour_color)
end
end
--- Updates decision selector.
--- @within Decision
--- @param decisions table A table of decision items.<br/>
--- @param selected_decision_index number The current index of the selected decision.<br/>
--- @return number selected_decision_index The updated index of the selected decision.
--- @return boolean mouse_confirmed True if the user clicked the center to confirm.
function Decision.update(decisions, selected_decision_index)
if Input.left() then
Audio.sfx_beep()
selected_decision_index = Util.safeindex(decisions, selected_decision_index - 1)
elseif Input.right() then
Audio.sfx_beep()
selected_decision_index = Util.safeindex(decisions, selected_decision_index + 1)
end
local bar_h = 16
local bar_y = Config.screen.height - bar_h
local prev_zone = { x = 0, y = bar_y, w = 15, h = bar_h }
local next_zone = { x = Config.screen.width-15, y = bar_y, w = 15, h = bar_h }
local confirm_zone = { x = 15, y = bar_y, w = Config.screen.width-30, h = bar_h }
if Mouse.zone(prev_zone) then
Audio.sfx_beep()
selected_decision_index = Util.safeindex(decisions, selected_decision_index - 1)
elseif Mouse.zone(next_zone) then
Audio.sfx_beep()
selected_decision_index = Util.safeindex(decisions, selected_decision_index + 1)
elseif Mouse.zone(confirm_zone) then
return selected_decision_index, true
end
return selected_decision_index, false
end

View File

@@ -1,15 +0,0 @@
Decision.register({
id = "play_button_mash",
label = "Play Button Mash",
handle = function()
Meter.hide()
MinigameButtonMashWindow.start("game", {
focus_center_x = (Config.screen.width / 2) - 22,
focus_center_y = (Config.screen.height / 2) - 18,
focus_initial_radius = 0,
on_win = function()
Audio.music_play_room_work()
end
})
end,
})

View File

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

View File

@@ -1,26 +0,0 @@
Decision.register({
id = "play_rhythm",
label = "Play Rhythm Game",
handle = function()
Meter.hide()
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,
})

View File

@@ -1,44 +0,0 @@
Decision.register({
id = "sumphore_discussion",
label = function()
if Context.have_met_sumphore then
return "Talk to Sumphore"
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
if level >= 1 and level <= 5 then
Discussion.start("sumphore_disc_asc_" .. level, "game")
elseif level == 6 then
if Context.glitch_conversation_count >= 6 then
Discussion.start("sumphore_disc_asc_6", "game")
else
Discussion.start("sumphore_disc_asc_6_waiting", "game")
end
elseif level == 7 then
local g = math.min(CommuteGlitch.get_level(), 7)
Discussion.start("sumphore_disc_cg_" .. g, "game")
else
Discussion.start("sumphore_disc_asc_" .. level, "game")
end
end,
})

View File

@@ -1,12 +0,0 @@
Decision.register({
id = "talk_to_truth",
label = function()
return "Talk to ????"
end,
condition = function()
return (CommuteGlitch.is_max())
end,
handle = function()
Discussion.start("norman_truth", "game")
end,
})

View File

@@ -1,259 +0,0 @@
-- Sumphore dialogue by commute glitch level (0-7).
-- Used by decision.sumphore_discussion at ascension level 7.
Discussion.register({
id = "sumphore_disc_cg_0",
steps = {
{
question = "These roads have memory. You might want to walk them back sometime.",
answers = {
{ label = "That's a peculiar thing to say.", next_step = nil },
},
},
},
})
Discussion.register({
id = "sumphore_disc_cg_1",
steps = {
{
question = "Walking is good for clearing the mind. Maybe the cache, too, if you're the kind that accumulates.",
answers = {
{ label = "I'm not sure what you mean.", next_step = nil },
},
},
},
})
Discussion.register({
id = "sumphore_disc_cg_2",
steps = {
{
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 },
},
},
},
})
Discussion.register({
id = "sumphore_disc_cg_3",
steps = {
{
question = "Your path sometimes has to go the wrong way before it can go the right way. Do you understand?",
answers = {
{ label = "Not really.", next_step = nil },
},
},
},
})
Discussion.register({
id = "sumphore_disc_cg_4",
steps = {
{
question = "You're starting to see the smudges, aren't you?",
answers = {
{ label = "Everyone seems weird.", next_step = nil },
},
},
},
})
Discussion.register({
id = "sumphore_disc_cg_5",
steps = {
{
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 },
},
},
{
question = "Yes. That's how you know it's right.",
answers = {
{ label = "...", next_step = nil },
},
},
},
})
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.",
answers = {
{ label = "Ok...", next_step = nil },
},
},
},
})
-- True Sumphore at glitch 7 (from walking_to_home screen).
-- Sets talked_to_true_sumphore on final answer.
Discussion.register({
id = "sumphore_disc_cg_7",
steps = {
{
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 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 },
},
},
{
question = "You already know. You just have to wake up.",
answers = {
{ label = "Go home.", next_step = nil, on_select = function()
Context.talked_to_true_sumphore = true
end },
},
},
},
})
-- 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 = {
{
question = "You look tired. You should really rest. Relax. You are good.",
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_4",
steps = {
{
question = "Have you tried going home? You really should. Now.",
answers = {
{ label = "I still have things to do.", next_step = 2 },
},
},
{
question = "We all do. We keep doing them. You can do it tomorrow after sleeping.",
answers = {
{ label = "...", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_cg_5",
steps = {
{
question = "c0ffee. try. w0rk. y0u. ar3. g0. h0me. na0.",
answers = {
{ label = "What?", next_step = 2 },
},
},
{
question = "570p",
answers = {
{ label = "...", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_cg_6",
steps = {
{
question = "You are not ready for the truth. Turn back now, Norman.",
answers = {
{ label = "I'm too far into this.", next_step = nil },
},
},
},
})
-- Norman echo dialogue at glitch 7 (fully corrupted office).
-- Sets talked_to_norman_echo on final answer.
Discussion.register({
id = "norman_truth",
steps = {
{
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 = "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. 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 },
},
},
{
question = "We never do, yes. We always felt like impostors. We never felt we deserved better. That we could be more.",
answers = {
{ label = "I never felt enough.", next_step = 5 },
},
},
{
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 },
},
},
{
question = "But stagnation is not safety. It's a prison. You can see the cracks now, can't you? You can see the truth.",
answers = {
{ label = "I want to move on now.", next_step = 7 },
},
},
{
question = "Fine. Let's do that. Let's wake up and face the truth.",
answers = {
{ label = "Let's wake up.", next_step = nil, on_select = function()
Context.talked_to_norman_echo = true
end },
},
},
},
})

View File

@@ -1,367 +0,0 @@
Discussion.register({
id = "coworker_disc_0",
on_end = Meter.apply_coworker_discussion_reward,
steps = {
{
question = "Good morning Normal, enjoying your coffee as usual, huh?",
answers = {
{ label = "The name is Norman, not Normal", next_step = 2 },
},
},
{
question = "Can't work without some good coffee, no? ",
answers = {
{ label = "Mhmm", next_step = nil },
},
},
},
});
Discussion.register({
id = "coworker_disc_1",
on_end = Meter.apply_coworker_discussion_reward,
steps = {
{
question = "Norman, you look confused, what's up?",
answers = {
{ label = "Just some bugs I noticed", next_step = 2 },
},
},
{
question = "Your coffee also seems whiter than usual!",
answers = {
{ label = "I feel like latte today", next_step = nil },
},
},
},
});
Discussion.register({
id = "coworker_disc_asc_1",
on_end = Meter.apply_coworker_discussion_reward,
steps = {
{
question = "Normann you look weird and unfocused. You are usually locked in and not like this, what's up?",
answers = {
{ label = "Some bugs I noticed, maybe...", next_step = 2 },
},
},
{
question = "Are you fixing bugs nobody noticed before?",
answers = {
{ label = "Maybe", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_2",
on_end = Meter.apply_coworker_discussion_reward,
steps = {
{
question = "Hey Norman, do you have new socks on? That's a weird color!",
answers = {
{ label = "Huh? True ...", next_step = 2 },
},
},
{
question = "You look strange today, Normal, you put your tie on backwards, is everything ok? ",
answers = {
{ label = "Get it right, Norman ... NORMAN!", next_step = 3 },
},
},
{
question = "Yo Normann, text goes from left to right, not right to left, these parts!",
answers = {
{ label = "Uhm...why?", next_step = nil },
},
},
},
});
Discussion.register({
id = "coworker_disc_asc_2",
on_end = Meter.apply_coworker_discussion_reward,
steps = {
{
question = "Normann, are you ok? You were doing weird things while typing?",
answers = {
{ label = "Naw", next_step = 2 },
},
},
{
question = "Oh, it's ok, I'm not wathcing you. Noone really is. *giggle*",
answers = {
{ label = "Huh ?", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_3",
on_end = Meter.apply_coworker_discussion_reward,
steps = {
{
question = "You look so happy, did you catch a bull or something?",
answers = {
{ label = "What do you mean?", next_step = 2 },
},
},
{
question = "Most people catch colds! You are so strange!",
answers = {
{ label = "An apple a day keeps the cold away", next_step = 3 },
},
},
{
question = "You look like you don't really want to work today, are you ok?",
answers = {
{ label = "Oh brother", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_3",
on_end = Meter.apply_coworker_discussion_reward,
steps = {
{
question = "Normal, you should take a break, you don't live up to your name today",
answers = {
{ label = "Norman is the name ...", next_step = 2 },
},
},
{
question = "You aren't as enthusiastic as you were before!",
answers = {
{ label = "Burnout comes for everyone", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_4",
on_end = Meter.apply_coworker_discussion_reward,
steps = {
{
question = "Normaaan! Same spot, same cup, same time. You're like a statue that drinks coffee!",
answers = {
{ label = "I'm a person.", next_step = 2 },
{ label = "Yep. Still here.", next_step = 2 },
},
},
{
question = "I love that about you! So reliable! If the coffee machine breaks we still have Norman!",
answers = {
{ label = "Please don't.", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_4",
on_end = Meter.apply_coworker_discussion_reward,
steps = {
{
question = "Norman, you were seriously locked in today. What on earth were you building?",
answers = {
{ label = "Just some things.", next_step = 2 },
{ label = "Nothing important.", next_step = 2 },
},
},
{
question = "So modest! I heard the seniors literally whispering your name!",
answers = {
{ label = "Concerning.", next_step = 3 },
{ label = "That's... fine.", next_step = 3 },
},
},
{
question = "You should celebrate! Coffee's on me tomorrow!",
answers = {
{ label = "Already have one.", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_5",
on_end = Meter.apply_coworker_discussion_reward,
steps = {
{
question = "Morning! Funny thought — I feel like we do this exact same thing every single day!",
answers = {
{ label = "We do.", next_step = 2 },
{ label = "Yes. We do.", next_step = 2 },
},
},
{
question = "Ha! Routine is such a comfort, right? Same coffee, same smile, same everything!",
answers = {
{ label = "Sure. Very comforting.", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_5",
on_end = Meter.apply_coworker_discussion_reward,
steps = {
{
question = "Norman! You were staring right THROUGH your screen today. What was going on up there?",
answers = {
{ label = "Coffee was cold.", next_step = 2 },
{ label = "Maybe I was.", next_step = 2 },
},
},
{
question = "Were you meditating? Or doing your intense bug-stare thing?",
answers = {
{ label = "Something like that.", next_step = 3 },
{ label = "Bug stare thing?", next_step = 3 },
},
},
{
question = "You're always somewhere else in your head, Norman. Must be nice up there!",
answers = {
{ label = "It's complicated.", next_step = nil },
},
},
},
})
local function _glitch_on_end()
Meter.apply_coworker_discussion_reward()
Context.glitch_conversation_count = Context.glitch_conversation_count + 1
Glitch.hide()
end
Discussion.register({
id = "coworker_disc_asc_6_1",
on_end = _glitch_on_end,
steps = {
{
question = "Hey Norman, good morning! Good morning! Good morning! ...Sorry. I don't know why I keep saying that.",
answers = {
{ label = "Are you feeling ok?", next_step = 2 },
},
},
{
question = "Good morning. Good morning. Good— I can't stop. Why can't I stop?",
answers = {
{ label = "...", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_6_2",
on_end = _glitch_on_end,
steps = {
{
question = "Hey... Marcus. How's it going?",
answers = {
{ label = "My name is Norman.", next_step = 2 },
},
},
{
question = "Right, sorry. Marcus. You look tired today.",
answers = {
{ label = "Norman. It's Norman.", next_step = 3 },
},
},
{
question = "Sure, sure. You should get some rest, Marcus.",
answers = {
{ label = "...", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_6_3",
on_end = _glitch_on_end,
steps = {
{
question = "We've had this conversation before, haven't we? Exact same words. Same coffee. Same spot.",
answers = {
{ label = "I don't think so.", next_step = 2 },
{ label = "Maybe.", next_step = 2 },
},
},
{
question = "Every day. I always say the same thing and you always say that. It's very strange.",
answers = {
{ label = "That's just routine.", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_6_4",
on_end = _glitch_on_end,
steps = {
{
question = "Do you ever look at the walls here? Really look? Sometimes they don't feel... solid.",
answers = {
{ label = "They're just walls.", next_step = 2 },
{ label = "I know what you mean.", next_step = 2 },
},
},
{
question = "Like they're only there because we expect them to be. Like set dressing. Does any of this feel load-bearing to you?",
answers = {
{ label = "I need more coffee.", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_6_5",
on_end = _glitch_on_end,
steps = {
{
question = "Norman, I'm not supposed to— I mean. How are you doing today?",
answers = {
{ label = "What weren't you supposed to say?", next_step = 2 },
},
},
{
question = "...",
answers = {
{ label = "Hello?", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_6_6",
on_end = _glitch_on_end,
steps = {
{
question = "Forget it. You won't remember this conversation anyway. None of us do.",
answers = {
{ label = "What do you mean?", next_step = 2 },
{ label = "That's a strange thing to say.", next_step = 2 },
},
},
{
question = "Tomorrow you'll come back and it'll be like this never happened. Same coffee. Same office. Same Norman.",
answers = {
{ label = "...", next_step = nil },
},
},
},
})

View File

@@ -1,28 +0,0 @@
Discussion.register({
id = "pizza_vendor_disc",
on_end = function()
Context.fast_food_approaching = false
end,
steps = {
{
question = "Hey friend! Hot slice, fresh out of the oven. You look like a man who knows good food!",
answers = {
{
label = "Devour a juicy one",
next_step = nil,
on_select = function()
local max = Meter.get_max()
Meter.add("wpm", math.floor(max * 0.05))
Meter.add("ism", math.floor(max * -0.05))
Meter.add("bm", math.floor(max * -0.05))
Context.fast_food_eaten_today = Context.fast_food_eaten_today + 1
end,
},
{
label = "Stay lean and sharp",
next_step = nil,
},
},
},
},
})

View File

@@ -1,253 +0,0 @@
Discussion.register({
id = "sumphore_disc_asc_1",
on_end = Meter.apply_sumphore_discussion_reward,
steps = {
{
question = "Are you still seeking the ox?",
answers = {
{ label = "Huh? What ox?", next_step = 2 },
{ label = "Are you drunk, old man?", next_step = nil },
},
},
{
question = "Did you never think there would be more to this?",
answers = {
{ label = "I'm not sure what you mean.", next_step = nil },
},
},
},
})
Discussion.register({
id = "sumphore_disc_asc_2",
on_end = Meter.apply_sumphore_discussion_reward,
steps = {
{
question = "How's work? Your face looks strange",
answers = {
{ label = "I just really need to take a break.", next_step = 2 },
{ label = "Not sure what you mean.", next_step = nil },
},
},
{
question = "Are you seeing things?",
answers = {
{ label = "How did you know ?", next_step = 3 },
{ label = "I'm not sure what you mean.", next_step = nil },
},
},
{
question = "Come have a drink, I could tell you some stories.",
answers = {
{ label = "No, drink makes you stupid and I need to be in top shape.", next_step = 4, on_select = function()
Meter.add("ism", 10)
end },
{ label = "I could use a drink.", next_step = nil, on_select = function()
Meter.add("bm", 10)
end },
},
},
{
question = "Always trying to do the right thing, huh? What if you did the left thing instead?",
answers = {
{ label = "I've never thought of that up till now.", next_step = nil, on_select = function()
Meter.add("ism", 5)
end },
{ label = "Silly wordgames, I like them.", next_step = nil, on_select = function()
Meter.add("wpm", 10)
end },
},
},
},
})
Discussion.register({
id = "sumphore_disc_asc_3",
on_end = Meter.apply_sumphore_discussion_reward,
steps = {
{
question = "Do you think it's work you're doing?",
answers = {
{ label = "... that sounds like it's from a movie.", next_step = 2 },
{ label = "Are you drunk, old man?", next_step = nil },
},
},
{
question = "You might just be trying too hard, why dont you just flow for a while?",
answers = {
{ label = "Flow where ?", next_step = 3 },
{ label = "I'm not sure what you mean.", next_step = nil },
},
},
{
question = "Flow carelessly, without any effort",
answers = {
{ label = "Consuming sth other than alcohol ?", next_step = nil },
{ label = "Deja vu", next_step = nil },
},
},
},
})
Discussion.register({
id = "sumphore_disc_asc_4",
on_end = Meter.apply_sumphore_discussion_reward,
steps = {
{
question = "The alarm wakes you every morning without fail, I bet.",
answers = {
{ label = "It's how it works.", next_step = 2 },
{ label = "Sometimes I wish it didn't.", next_step = 2 },
},
},
{
question = "What if the alarm is part of the problem?",
answers = {
{ label = "That doesn't make any sense.", next_step = 3 },
{ label = "Go on.", next_step = 3 },
},
},
{
question = "One morning, Norman. When the choice comes, choose the bed. See what the world does without you in it.",
answers = {
{ label = "You're drunk.", next_step = nil },
{ label = "What choice?", next_step = nil, on_select = function()
Meter.add("ism", 5)
end },
},
},
},
})
Discussion.register({
id = "sumphore_disc_asc_5",
on_end = Meter.apply_sumphore_discussion_reward,
steps = {
{
question = "You saw the seams, didn't you. Good. That means the work is finally wearing thin.",
answers = {
{ label = "Wearing thin how?", next_step = 2 },
{ label = "Maybe.", next_step = 2 },
},
},
{
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 = "You want me to stop trying?", next_step = 3 },
{ label = "I've noticed something odd.", next_step = 3 },
},
},
{
question = "Drain the work out of yourself. When that measure hits nothing, you'll see what was waiting behind it.",
answers = {
{ 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 },
},
},
},
})
Discussion.register({
id = "sumphore_disc_asc_6_waiting",
on_end = Meter.apply_sumphore_discussion_reward,
steps = {
{
question = "You look like a man who has seen something he can't explain.",
answers = {
{ label = "I'm hearing things.", next_step = 2 },
{ label = "Maybe.", next_step = 2 },
},
},
{
question = "Keep looking. The world has a way of showing you what you need to see. Talk to people. You're almost there.",
answers = {
{ label = "Almost where?", next_step = nil },
},
},
},
})
Discussion.register({
id = "sumphore_disc_asc_6",
on_end = function()
Meter.apply_sumphore_discussion_reward()
Context.should_ascend = true
Day.increase()
MysteriousManScreen.start({
text = MysteriousManScreen.get_text_for_level(Ascension.get_level())
})
end,
steps = {
{
question = "You've been seeing the cracks, haven't you? The repetition. The loops. The coworkers who can't quite remember.",
answers = {
{ label = "How do you know that?", next_step = 2 },
},
},
{
question = "Because I was you, once. This isn't a workplace, Norman. It never was. You're in a system. And you've just become aware of it.",
answers = {
{ label = "That can't be true.", next_step = 3 },
{ label = "I knew something was wrong.", next_step = 3 },
},
},
{
question = "It doesn't matter if you believe it. You already know. That's why the cracks are showing. That's why you're still here.",
answers = {
{ label = "What do I do now?", next_step = 4 },
},
},
{
question = "Oh look, is that a squirrel ?",
answers = {
{ label = "Alcoholic ...", next_step = nil },
},
},
},
})
Discussion.register({
id = "homeless_guy",
on_end = Meter.apply_sumphore_discussion_reward,
steps = {
{
question = "Sup bro, how are you?",
answers = {
{ label = "I'm doing great, thanks!", next_step = 2 },
{ label = "Not as good as you", next_step = nil },
},
},
{
question = "What's your name?",
answers = {
{ label = "Norman Reds, nice to meet you.", next_step = 3 },
{ label = "Mom told me not to talk to strangers.", next_step = nil },
},
},
{
question = "That name ... could it be? I know a guy with that name...",
answers = {
{ label = "Never met you before.", next_step = 4 },
{ label = "I'm not sure what you mean.", next_step = nil },
},
},
{
question = "My name is Sumphore, nice to meet you.",
answers = {
{ label = "Nice to meet you, Sumphore.", next_step = 5, on_select = function()
Context.have_met_sumphore = true
end },
},
},
{
question = "You're a good guy, I can tell. You abide by the rules. Life would be so much easier if more people were like you ...",
answers = {
{ label = "Thanks, I try my best.", next_step = nil },
},
},
},
})

View File

@@ -0,0 +1,7 @@
function Item.use()
Print.text("Used item: " .. Context.dialog.active_entity.name)
end
function Item.look_at()
PopupWindow.show_description_dialog(Context.dialog.active_entity, Context.dialog.active_entity.desc)
end

13
inc/entity/entity.npc.lua Normal file
View File

@@ -0,0 +1,13 @@
function NPC.talk_to()
local npc = Context.dialog.active_entity
if npc.dialog and npc.dialog.start then
PopupWindow.set_dialog_node("start")
else
-- if no dialog, go back
GameWindow.set_state(WINDOW_GAME)
end
end
function NPC.fight() end
function NPC.go_back()
GameWindow.set_state(WINDOW_GAME)
end

View File

@@ -1,59 +1,49 @@
Config = {}
--- Return initial data for Config
--- @within Config
function Config.initial_data()
return {
screen = {
width = 240,
height = 136
},
colors = {
black = 0,
light_grey = 2,
dark_grey = 1,
red = 13,
light_blue = 9,
blue = 3,
white = 4,
item = 7,
meter_bg = 1,
transparent = 12,
orange = 7
},
timing = {
minigame_win_duration = 180
}
local DEFAULT_CONFIG = {
screen = {
width = 240,
height = 136
},
colors = {
black = 0,
light_grey = 13,
dark_grey = 14,
green = 6,
npc = 8,
item = 12 -- yellow
},
player = {
sprite_id = 1
},
timing = {
splash_duration = 120
}
end
}
--- Restores default configuration settings.
--- @within Config
function Config.reset()
local initial = Config.initial_data()
Config.screen = initial.screen
Config.colors = initial.colors
Config.timing = initial.timing
end
local Config = {
-- Copy default values initially
screen = DEFAULT_CONFIG.screen,
colors = DEFAULT_CONFIG.colors,
player = DEFAULT_CONFIG.player,
timing = DEFAULT_CONFIG.timing,
}
local CONFIG_SAVE_BANK = 7
local CONFIG_MAGIC_VALUE_ADDRESS = 2
local CONFIG_MAGIC_VALUE = 0xDE
local CONFIG_MAGIC_VALUE = 0xDE -- A magic number to check if config is saved
--- Saves the current configuration.
--- @within Config
function Config.save()
mset(CONFIG_MAGIC_VALUE, CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK)
-- Save physics settings
mset(CONFIG_MAGIC_VALUE, CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK) -- Mark as saved
end
--- Loads saved configuration.
--- @within Config
function Config.load()
if mget(CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK) == CONFIG_MAGIC_VALUE then
return
else
Config.reset()
end
Config.restore_defaults()
-- Check if config has been saved before using a magic value
end
function Config.restore_defaults()
-- Any other configurable items should be reset here
end
-- Load configuration on startup
Config.load()

View File

@@ -2,171 +2,421 @@ local SAVE_GAME_BANK = 6
local SAVE_GAME_MAGIC_VALUE_ADDRESS = 0
local SAVE_GAME_MAGIC_VALUE = 0xCA
--- Global game context.
--- @section Context
Context = {}
local SAVE_GAME_PLAYER_X_ADDRESS = 1
local SAVE_GAME_PLAYER_Y_ADDRESS = 2
local SAVE_GAME_PLAYER_VX_ADDRESS = 3
local SAVE_GAME_PLAYER_VY_ADDRESS = 4
local SAVE_GAME_selectS_ADDRESS = 5
local SAVE_GAME_CURRENT_SCREEN_ADDRESS = 6
--- Gets initial data for Context.
--- @within Context
--- @return result table Initial context data or nil. </br>
--- Fields: </br>
--- * current_menu_item (number) Index of the currently selected menu item.<br/>
--- * popup (table) Popup window state. Contains: `show` (boolean) whether popup is visible, `content` (table) array of strings to display.<br/>
--- * game_in_progress (boolean) Whether a game is currently active.<br/>
--- * minigame_ddr (table) DDR minigame state (see Minigame.get_default_ddr).<br/>
--- * minigame_button_mash (table) Button mash minigame state (see Minigame.get_default_button_mash).<br/>
--- * minigame_rhythm (table) Rhythm minigame state (see Minigame.get_default_rhythm).<br/>
--- * meters (table) Meter values (see Meter.get_initial).<br/>
--- * ascension (table) Ascension meter state (see Ascension.get_initial).<br/>
--- * triggers (table) Active trigger runtime state, keyed by trigger ID.<br/>
--- * stat_screen_active (boolean) Whether the stat screen overlay is currently shown.<br/>
--- * have_met_sumphore (boolean) Whether the player has talked to the homeless guy.<br/>
--- * have_been_to_office (boolean) Whether the player has been to the office.<br/>
--- * have_done_work_today (boolean) Whether the player has done work today.<br/>
--- * toilet_meters_today_morning (boolean) Whether the home (before work) toilet meter delta was already applied this day.<br/>
--- * toilet_meters_today_evening (boolean) Whether the home (after work) toilet meter delta was already applied this day.<br/>
--- * coworker_discussion_meter_applied_today (boolean) Whether the daily coworker discussion meter roll was already applied this day.<br/>
--- * sumphore_discussion_meter_applied_today (boolean) Whether the daily sumphore discussion meter roll was already applied this day.<br/>
--- * game (table) Current game progress state. Contains: `current_screen` (string) active screen ID<br/>
function Context.initial_data()
local VX_VY_OFFSET = 128 -- Offset for negative velocities
-- Helper for deep copying tables
local function clone_table(t)
local copy = {}
for k, v in pairs(t) do
if type(v) == "table" then
copy[k] = clone_table(v)
else
copy[k] = v
end
end
return copy
end
-- This function returns a table containing only the initial *data* for Context
local function get_initial_data()
return {
current_menu_item = 1,
test_mode = false,
mouse_trace = false,
popup = {
show = false,
content = {}
active_window = WINDOW_SPLASH,
intro = {
y = Config.screen.height,
speed = 0.5,
text = "Norman Reds everyday life\nseems ordinary: work,\nmeetings, coffee, and\nendless notifications.\nBut beneath the surface\n— within him, or around\nhim — something is\nconstantly building, and\nit soon becomes clear\nthat there is more going\non than meets the eye."
},
game_in_progress = false,
stat_screen_active = false,
minigame_ddr = {},
minigame_button_mash = {},
minigame_rhythm = {},
meters = Meter.get_initial(),
ascension = Ascension.get_initial(),
timer = Timer.get_initial(),
triggers = {},
home_norman_visible = false,
have_been_to_office = false,
have_done_work_today = false,
toilet_meters_today_morning = false,
toilet_meters_today_evening = false,
coworker_discussion_meter_applied_today = false,
sumphore_discussion_meter_applied_today = false,
glitch_conversation_count = 0,
glitch_conversation_done_today = false,
should_ascend = false,
have_met_sumphore = false,
fast_food_approaching = false,
fast_food_eaten_today = 0,
office_sprites = {},
walking_to_office_sprites = {},
walking_to_home_sprites = {},
game = {
current_screen = "home",
current_screen = 1,
splash_timer = Config.timing.splash_duration,
dialog = {
text = "",
menu_items = {},
selected_menu_item = 1,
active_entity = nil,
showing_description = false,
current_node_key = nil
},
day_count = 1,
delta_time = 0,
last_frame_time = 0,
commute_glitch_level = 0,
talked_to_norman_echo = false,
talked_to_true_sumphore = false,
glitch = {
enabled = false,
state = "active",
timer = 0,
player = {
sprite_id = Config.player.sprite_id
},
_end = {
state = "choice",
selection = 1,
},
discussion = {
active = false,
id = nil,
step = 1,
selected_answer = 1,
scroll_y = 0,
scroll_timer = 0,
auto_scroll = true,
return_window = nil,
ground = {
x = 0,
y = Config.screen.height,
w = Config.screen.width,
h = 8
},
menu_items = {},
selected_menu_item = 1,
game_in_progress = false, -- New flag
screens = clone_table({
{
-- Screen 1
name = "Screen 1",
npcs = {
{
name = "Trinity",
sprite_id = 2,
dialog = {
start = {
text = "Hello, Neo.",
options = {
{label = "Who are you?", next_node = "who_are_you"},
{label = "My name is not Neo.", next_node = "not_neo"},
{label = "...", next_node = "silent"}
}
},
who_are_you = {
text = "I am Trinity. I've been looking for you.",
options = {
{label = "The famous hacker?", next_node = "famous_hacker"},
{label = "Why me?", next_node = "why_me"}
}
},
not_neo = {
text = "I know. But you will be.",
options = {
{label = "What are you talking about?", next_node = "who_are_you"}
}
},
silent = {
text = "You're not much of a talker, are you?",
options = {
{label = "I guess not.", next_node = "dialog_end"}
}
},
famous_hacker = {
text = "The one and only.",
options = {
{label = "Wow.", next_node = "dialog_end"}
}
},
why_me = {
text = "Morpheus believes you are The One.",
options = {
{label = "The One?", next_node = "the_one"}
}
},
the_one = {
text = "The one who will save us all.",
options = {
{label = "I'm just a programmer.", next_node = "dialog_end"}
}
},
dialog_end = {
text = "We'll talk later.",
options = {} -- No options, ends conversation
}
}
},
{
name = "Oracle",
sprite_id = 3,
dialog = {
start = {
text = "I know what you're thinking. 'Am I in the right place?'",
options = {
{label = "Who are you?", next_node = "who_are_you"},
{label = "I guess I am.", next_node = "you_are"}
}
},
who_are_are = {
text = "I'm the Oracle. And you're right on time. Want a cookie?",
options = {
{label = "Sure.", next_node = "cookie"},
{label = "No, thank you.", next_node = "no_cookie"}
}
},
you_are = {
text = "Of course you are. Sooner or later, everyone comes to see me. Want a cookie?",
options = {
{label = "Yes, please.", next_node = "cookie"},
{label = "I'm good.", next_node = "no_cookie"}
}
},
cookie = {
text = "Here you go. Now, what's really on your mind?",
options = {
{label = "Am I The One?", next_node = "the_one"},
{label = "What is the Matrix?", next_node = "the_matrix"}
}
},
no_cookie = {
text = "Suit yourself. Now, what's troubling you?",
options = {
{label = "Am I The One?", next_node = "the_one"},
{label = "What is the Matrix?", next_node = "the_matrix"}
}
},
the_one = {
text = "Being The One is just like being in love. No one can tell you you're in love, you just know it. Through and through. Balls to bones.",
options = {
{label = "So I'm not?", next_node = "dialog_end"}
}
},
the_matrix = {
text = "The Matrix is a system, Neo. That system is our enemy. But when you're inside, you look around, what do you see? The very minds of the people we are trying to save.",
options = {
{label = "I see.", next_node = "dialog_end"}
}
},
dialog_end = {
text = "You have to understand, most of these people are not ready to be unplugged.",
options = {}
}
}
}
},
items = {
{
name = "Key",
sprite_id = 4,
desc = "A rusty old key. It might open something."
}
}
},
{
-- Screen 2
name = "Screen 2",
npcs = {
{
name = "Morpheus",
sprite_id = 5,
dialog = {
start = {
text = "At last. Welcome, Neo. As you no doubt have guessed, I am Morpheus.",
options = {
{label = "It's an honor to meet you.", next_node = "honor"},
{label = "You've been looking for me.", next_node = "looking_for_me"}
}
},
honor = {
text = "No, the honor is mine.",
options = {
{label = "What is this place?", next_node = "what_is_this_place"}
}
},
looking_for_me = {
text = "I have. For some time.",
options = {
{label = "What is this place?", next_node = "what_is_this_place"}
}
},
what_is_this_place = {
text = "This is the construct. It's our loading program. We can load anything from clothing, to equipment, weapons, training simulations. Anything we need.",
options = {
{label = "Right.", next_node = "dialog_end"}
}
},
dialog_end = {
text = "I've been waiting for you, Neo. We have much to discuss.",
options = {} -- Ends conversation
}
}
},
{
name = "Tank",
sprite_id = 6,
dialog = {
start = {
text = "Hey, Neo! Welcome to the construct. I'm Tank.",
options = {
{label = "Good to meet you.", next_node = "good_to_meet_you"},
{label = "This place is incredible.", next_node = "incredible"}
}
},
good_to_meet_you = {
text = "You too! We've been waiting for you. Need anything? Training? Weapons?",
options = {
{label = "Training?", next_node = "training"},
{label = "I'm good for now.", next_node = "dialog_end"}
}
},
incredible = {
text = "Isn't it? The boss's design. We can load anything we need. What do you want to learn?",
options = {
{label = "Show me.", next_node = "training"}
}
},
training = {
text = "Jujitsu? Kung Fu? How about... all of them?",
options = {
{label = "All of them.", next_node = "all_of_them"}
}
},
all_of_them = {
text = "Operator, load the combat training program.",
options = {
{label = "...", next_node = "dialog_end"}
}
},
dialog_end = {
text = "Just holler if you need anything. Anything at all.",
options = {}
}
}
}
},
items = {
{
name = "Potion",
sprite_id = 7,
desc = "A glowing red potion. It looks potent."
}
}
},
{
-- Screen 3
name = "Screen 3",
npcs = {
{
name = "Agent Smith",
sprite_id = 8,
dialog = {
start = {
text = "Mr. Anderson. We've been expecting you.",
options = {
{label = "My name is Neo.", next_node = "name_is_neo"},
{label = "...", next_node = "silent"}
}
},
name_is_neo = {
text = "Whatever you say. You're here for a reason.",
options = {
{label = "What reason?", next_node = "what_reason"}
}
},
silent = {
text = "The silent type. It doesn't matter. You are an anomaly.",
options = {
{label = "What do you want?", next_node = "what_reason"}
}
},
what_reason = {
text = "To be deleted. The system has no place for your kind.",
options = {
{label = "I won't let you.", next_node = "wont_let_you"}
}
},
wont_let_you = {
text = "You hear that, Mr. Anderson? That is the sound of inevitability.",
options = {
{label = "...", next_node = "dialog_end"}
}
},
dialog_end = {
text = "It is purpose that created us. Purpose that connects us. Purpose that pulls us. That guides us. That drives us. It is purpose that defines. Purpose that binds us.",
options = {}
}
}
},
{
name = "Cypher",
sprite_id = 9,
dialog = {
start = {
text = "Well, well. The new messiah. Welcome to the real world.",
options = {
{label = "You don't seem happy.", next_node = "not_happy"},
{label = "...", next_node = "silent"}
}
},
not_happy = {
text = "Happy? Ignorance is bliss, Neo. We've been fighting this war for years. For what?",
options = {
{label = "For freedom.", next_node = "freedom"}
}
},
silent = {
text = "Not a talker, huh? Smart. Less to regret later. Want a drink?",
options = {
{label = "Sure.", next_node = "drink"},
{label = "No thanks.", next_node = "no_drink"}
}
},
drink = {
text = "Good stuff. The little things you miss, you know? Like a good steak.",
options = {
{label = "I guess.", next_node = "dialog_end"}
}
},
no_drink = {
text = "Your loss. More for me.",
options = {
{label = "...", next_node = "dialog_end"}
}
},
freedom = {
text = "Freedom... right. If Morpheus told you you could fly, would you believe him?",
options = {
{label = "He's our leader.", next_node = "dialog_end"}
}
},
dialog_end = {
text = "Just be careful who you trust.",
options = {}
}
}
}
},
items = {}
}
})
}
end
--- Resets game context to initial state.
--- @within Context
function Context.reset()
local initial_data = Context.initial_data()
Context = {}
local function reset_context_to_initial_state()
local initial_data = get_initial_data()
-- Clear existing data properties from Context (but not methods)
for k in pairs(Context) do
if type(Context[k]) ~= "function" then
if type(Context[k]) ~= "function" then -- Only clear data, leave functions
Context[k] = nil
end
end
-- Copy all initial data properties into Context
for k, v in pairs(initial_data) do
Context[k] = v
end
end
--- Starts a new game.
--- @within Context
-- Initially populate Context with data
reset_context_to_initial_state()
-- Now define the methods for Context
function Context.new_game()
Context.reset()
reset_context_to_initial_state()
Context.game_in_progress = true
MenuWindow.refresh_menu_items()
Screen.get_by_id(Context.game.current_screen).init()
MysteriousManScreen.start({
text = [[
Norman was never a bad
simulation engineer,
but
we need to be careful
letting him improve.
We need to distract him.
]],
on_text_complete = function()
Audio.sfx_alarm()
Context.home_norman_visible = false
Util.go_to_screen_by_id("home")
MinigameButtonMashWindow.start("game", {
focus_center_x = (Config.screen.width / 2) - 22,
focus_center_y = (Config.screen.height / 2) - 18,
focus_initial_radius = 0,
target_points = 100,
instruction_text = "Wake up Norman!",
show_progress_text = false,
meter_on_complete = Meter.apply_wakeup_reward,
on_win = function()
Audio.music_play_room_work()
Meter.show()
Window.set_current("game")
end,
})
end,
})
end
--- Saves the current game state.
--- @within Context
function Context.save_game()
if not Context.game_in_progress then return end
mset(SAVE_GAME_MAGIC_VALUE, SAVE_GAME_MAGIC_VALUE_ADDRESS, SAVE_GAME_BANK)
mset(Context.current_screen, SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK)
end
--- Loads a saved game state.
--- @within Context
function Context.load_game()
if mget(SAVE_GAME_MAGIC_VALUE_ADDRESS, SAVE_GAME_BANK) ~= SAVE_GAME_MAGIC_VALUE then
-- No saved game found, start a new one
Context.new_game()
return
end
Context.reset()
reset_context_to_initial_state() -- Reset data, preserve methods
Context.current_screen = mget(SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK)
Context.game_in_progress = true
MenuWindow.refresh_menu_items()
Screen.get_by_id(Context.game.current_screen).init()
end
end

View File

@@ -1,22 +0,0 @@
Window = {}
Util = {}
Meter = {}
Minigame = {}
Decision = {}
Screen = {}
Map = {}
UI = {}
Print = {}
Input = {}
Mouse = {}
Sprite = {}
Audio = {}
Focus = {}
Day = {}
Timer = {}
Trigger = {}
Discussion = {}
RLE = {}
AsciiArt = {}
Ascension = {}
MysteriousManScreen = {}

13
inc/init/init.modules.lua Normal file
View File

@@ -0,0 +1,13 @@
local SplashWindow = {}
local IntroWindow = {}
local MenuWindow = {}
local GameWindow = {}
local PopupWindow = {}
local ConfigurationWindow = {}
local UI = {}
local Print = {}
local Input = {}
local NPC = {}
local Item = {}
local Player = {}

View File

@@ -0,0 +1,6 @@
local WINDOW_SPLASH = 0
local WINDOW_INTRO = 1
local WINDOW_MENU = 2
local WINDOW_GAME = 3
local WINDOW_POPUP = 4
local WINDOW_CONFIGURATION = 7

View File

@@ -1,181 +0,0 @@
--- @section Ascension
local ASCENSION_MAX_LEVEL = 9
local ASCENSION_WORD = "ASCENSION"
local _increased_this_cycle = false
local _flash_active = false
local _flash_timer = 0
local _flash_total = 0
local FLASH_DURATION = 120
local _fade_active = false
local _fade_timer = 0
local FADE_DURATION = 120
local FADE_COLORS = nil
--- Gets initial ascension state.
--- @within Ascension
--- @return result table Initial ascension state. </br>
--- Fields: </br>
--- * level (number) Current ascension level (0-9).
function Ascension.get_initial()
_increased_this_cycle = false
return {
level = 0,
}
end
--- Gets the current ascension level.
--- @within Ascension
--- @return number The current ascension level (0-9).
function Ascension.get_level()
if not Context or not Context.ascension then return 0 end
return Context.ascension.level
end
--- Gets the maximum ascension level.
--- @within Ascension
--- @return number The maximum ascension level.
function Ascension.get_max_level()
return ASCENSION_MAX_LEVEL
end
--- Increases the ascension level by 1, clamped to the max.
--- @within Ascension
function Ascension.increase()
if not Context or not Context.ascension then return end
Context.ascension.level = math.min(ASCENSION_MAX_LEVEL, Context.ascension.level + 1)
_increased_this_cycle = true
end
--- Returns true if ascension was incremented since the last consume call.
--- @within Ascension
--- @return boolean Whether ascension increased this cycle.
function Ascension.did_increase()
return _increased_this_cycle
end
--- Consumes the increase flag, returning its value and resetting it.
--- @within Ascension
--- @return boolean Whether ascension had increased this cycle.
function Ascension.consume_increase()
local result = _increased_this_cycle
_increased_this_cycle = false
return result
end
--- Returns true when the ascension meter is fully complete (level 10).
--- @within Ascension
--- @return boolean Whether the cycle can be broken.
function Ascension.is_complete()
return Ascension.get_level() >= ASCENSION_MAX_LEVEL
end
--- Draws the ascension meter as individual letters of "ASCENSION".
--- Each letter lights up per level. Drawn beneath existing meter bars.
--- @within Ascension
--- @param x number Left x position.
--- @param y number Top y position.
--- @param options table Optional overrides: lit_color, dim_color, spacing.
function Ascension.draw(x, y, options)
if not Context or not Context.ascension then return end
options = options or {}
local level = Context.ascension.level
if level < 1 then return end
local lit_color = options.lit_color or Config.colors.white
local spacing = options.spacing or 5
for i = 1, level do
local ch = ASCENSION_WORD:sub(i, i)
local color
if i == level and _fade_active then
color = Ascension.get_fade_color()
else
color = lit_color
end
Print.text_contour(ch, x + (i - 1) * spacing, y, color, false, 1)
end
end
--- Returns the current fade-in color based on progress through the palette.
--- @within Ascension
--- @return number The palette color index for the current fade step.
function Ascension.get_fade_color()
if not FADE_COLORS then
FADE_COLORS = {
Config.colors.black,
Config.colors.dark_grey,
Config.colors.light_grey,
Config.colors.white,
}
end
if not _fade_active then return Config.colors.white end
local progress = math.min(_fade_timer / FADE_DURATION, 1)
local idx = math.floor(progress * (#FADE_COLORS - 1)) + 1
return FADE_COLORS[idx]
end
--- Starts the fade-in effect for the most recently gained letter.
--- @within Ascension
function Ascension.start_fade()
_fade_active = true
_fade_timer = 0
end
--- Starts the ascension flash effect.
--- @within Ascension
function Ascension.start_flash()
_flash_active = true
_flash_timer = 0
_flash_total = FLASH_DURATION
end
--- Updates and draws the ascension flash overlay.
--- Call once per frame from the main loop.
--- @within Ascension
function Ascension.draw_flash()
if not _flash_active then return end
_flash_timer = _flash_timer + 1
local sw = Config.screen.width
local sh = Config.screen.height
local progress = _flash_timer / FLASH_DURATION
local pulse = math.abs(math.sin(progress * math.pi * 6))
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()
end
end
--- Updates the fade-in timer. Call once per frame from the main loop.
--- @within Ascension
function Ascension.update_fade()
if not _fade_active then return end
_fade_timer = _fade_timer + 1
if _fade_timer >= FADE_DURATION then
_fade_active = false
end
end
--- Returns whether a flash effect is currently active.
--- @within Ascension
--- @return boolean Whether the flash is playing.
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

View File

@@ -1,60 +0,0 @@
--- @section CodeGenerator
CodeGenerator = {}
local SALT = 27471
local BASE36 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
local NAME_LEN = 3
-- Per-position offsets derived from SALT so each character slot
-- maps to a different region of the 2-char base-36 space.
local SALTS = {
SALT % 36,
math.floor(SALT / 36) % 36,
math.floor(SALT / 1296) % 36,
}
--- Encodes a number (0935) as exactly 2 base-36 characters.
--- @within CodeGenerator
function CodeGenerator.encode_pair(n)
return BASE36:sub(math.floor(n / 36) + 1, math.floor(n / 36) + 1)
.. BASE36:sub(n % 36 + 1, n % 36 + 1)
end
--- Decodes 2 base-36 characters back to a number.
--- @within CodeGenerator
function CodeGenerator.decode_pair(s)
local d1 = BASE36:find(s:sub(1, 1), 1, true) - 1
local d2 = BASE36:find(s:sub(2, 2), 1, true) - 1
return d1 * 36 + d2
end
--- Encrypts a player name into a code twice its length.
--- Each input character (A-Z, value 0-25) is encoded as
--- c + SALTS[i] * 26, producing 2 base-36 output characters.
--- @within CodeGenerator
--- @param text string NAME_LEN-character uppercase player name.
--- @return string Encrypted code (2 * NAME_LEN base-36 characters).
function CodeGenerator.encrypt(text)
local result = ""
for i = 1, NAME_LEN do
local c = math.max(0, (string.byte(text, i) or 65) - 65)
result = result .. CodeGenerator.encode_pair(c + SALTS[i] * 26)
end
return result
end
--- Decrypts a personal code back to the original player name.
--- @within CodeGenerator
--- @param encrypted_text string The code to decrypt (2 * NAME_LEN chars).
--- @return string Original player name, or "???" if the code is invalid.
function CodeGenerator.decrypt(encrypted_text)
local t = encrypted_text:upper()
if #t ~= NAME_LEN * 2 then return "???" end
local result = ""
for i = 1, NAME_LEN do
local pair = CodeGenerator.decode_pair(t:sub((i - 1) * 2 + 1, i * 2))
result = result .. string.char(pair % 26 + 65)
end
return result
end

View File

@@ -1,136 +0,0 @@
--- @section CommuteGlitch
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
--- Increments the glitch counter. Called on each office screen init at asc level 7.
--- Caps at 6; use enter_truth() to reach 7.
--- @within CommuteGlitch
function CommuteGlitch.increment()
if not Context then return end
if Context.commute_glitch_level >= 7 then return end
Context.commute_glitch_level = math.min(6, (Context.commute_glitch_level or 0) + 1)
end
--- Resets the glitch counter and hides the glitch overlay. Called when going home.
--- @within CommuteGlitch
function CommuteGlitch.reset()
if not Context then return end
Context.commute_glitch_level = 0
Glitch.hide()
end
--- Jumps to glitch level 7 (full corruption). Called by go_to_truth.
--- @within CommuteGlitch
function CommuteGlitch.enter_truth()
if not Context then return end
Context.commute_glitch_level = 7
Glitch.show()
Ascension.start_flash()
end
--- 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
--- @return number Speed value for music().
function CommuteGlitch.music_speed()
local level = CommuteGlitch.get_level()
if level <= 1 then return 6 end
return 6 + (level - 1) * 2
end
--- Returns a corrupted copy of a sprite drawable list.
--- Applies flip_y and norman_echo id replacements based on glitch level.
--- Entries marked norman_echo should be drawn via draw_sprite_list for palette change handling.
--- @within CommuteGlitch
--- @param list table Drawable sprite list from Sprite.list_randomize.
--- @return table Corrupted copy of the list.
function CommuteGlitch.corrupt_sprite_list(list)
local level = CommuteGlitch.get_level()
if level < 3 or not list then return list end
local result = {}
for i, entry in ipairs(list) do
local e = {}
for k, v in pairs(entry) do e[k] = v end
if level >= 7 then
e.id = "norman_echo"
else
local n_flip = (level >= 5) and 2 or 1
local n_echo = (level >= 5) and 2 or (level >= 4) and 1 or 0
if i <= n_flip then e.flip_y = 1 end
if i > n_flip and i <= n_flip + n_echo then e.id = "norman_echo" end
end
table.insert(result, e)
end
return result
end
-- Palette indices for Norman echo color remap.
-- Implementer: pick ECHO_SRC as one of Norman's main body colors and ECHO_DST
-- as a contrasting or wrong palette color by inspecting the sprite sheet.
local ECHO_SRC = 4
local ECHO_DST = 14
-- Base nibble address of the PALETTE MAP in VRAM.
local PALETTE_MAP_ADDR = 0x03FF0 * 2
--- Draws a sprite list, applying a PALETTE MAP remap for norman_echo entries.
--- Uses poke4 to remap ECHO_SRC → ECHO_DST before drawing echoes, then restores.
--- @within CommuteGlitch
--- @param list table Drawable sprite list (may contain mixed normal and echo entries).
function CommuteGlitch.draw_sprite_list(list)
if not list then return end
local normal, echo = {}, {}
for _, entry in ipairs(list) do
if entry.id == "norman_echo" then
table.insert(echo, entry)
else
table.insert(normal, entry)
end
end
if #normal > 0 then
Sprite.draw_list(normal)
end
if #echo > 0 then
poke4(PALETTE_MAP_ADDR + ECHO_SRC, ECHO_DST)
Sprite.draw_list(echo)
poke4(PALETTE_MAP_ADDR + ECHO_SRC, ECHO_SRC)
end
end
local _flicker_tick = 0
--- Draws a random tile-flicker effect over the background (glitch level 7).
--- Every 3 frames draws 6 random 8x8 rects in random palette colors.
--- @within CommuteGlitch
function CommuteGlitch.draw_background_flicker()
_flicker_tick = (_flicker_tick + 1) % 3
if _flicker_tick ~= 0 then return end
for _ = 1, 6 do
local tx = math.random(0, math.floor(Config.screen.width / 8) - 1) * 8
local ty = math.random(0, math.floor(Config.screen.height / 8) - 1) * 8
local color = math.random(0, 15)
rect(tx, ty, 8, 8, color)
end
end

View File

@@ -1,48 +0,0 @@
--- @section Day
local _day_increase_handlers = {}
--- Increases the day count and triggers registered handlers.
--- @within Day
function Day.increase()
Context.day_count = Context.day_count + 1
if Context.day_count == 3 then
Context.should_ascend = true
end
if Context.day_count >= 100 and not Ascension.is_complete() then
GameOverWindow.show("days")
return
end
for _, handler in ipairs(_day_increase_handlers) do
handler()
end
end
--- Registers a handler to be called when the day increases.
--- @within Day
--- @param handler function The function to call when the day increases.
function Day.register_handler(handler)
table.insert(_day_increase_handlers, handler)
end
Day.register_handler(function()
local m = Context.meters
m.ism = math.max(0, m.ism - METER_DECAY_PER_DAY)
m.wpm = math.max(0, m.wpm - METER_DECAY_PER_DAY)
m.bm = math.max(0, m.bm - METER_DECAY_PER_DAY)
end)
Day.register_handler(function()
Context.toilet_meters_today_morning = false
Context.toilet_meters_today_evening = false
Context.coworker_discussion_meter_applied_today = false
Context.sumphore_discussion_meter_applied_today = false
Context.glitch_conversation_done_today = false
Context.fast_food_eaten_today = 0
end)
Day.register_handler(function()
if Context.should_ascend then
Ascension.increase()
end
Context.should_ascend = false
end)

View File

@@ -1,106 +0,0 @@
--- @section Discussion
local _discussions = {}
--- Registers a discussion definition.
--- @within Discussion
--- @param discussion table The discussion data table.
--- @param discussion.id string Unique discussion identifier.
--- @param discussion.steps table Array of step tables, each with `question` (string) and `answers` (array of {label, next_step} tables).
--- @param[opt] discussion.on_end function Called when the discussion ends. Defaults to noop.
function Discussion.register(discussion)
if not discussion or not discussion.id then
trace("Error: Invalid discussion registered (missing id)!")
return
end
if not discussion.steps or #discussion.steps == 0 then
trace("Error: Discussion '" .. discussion.id .. "' has no steps!")
return
end
if not discussion.on_end then
discussion.on_end = function() end
end
if _discussions[discussion.id] then
trace("Warning: Overwriting discussion with id: " .. discussion.id)
end
_discussions[discussion.id] = discussion
end
--- Gets a discussion by ID.
--- @within Discussion
--- @param id string The discussion ID.
--- @return table|nil result The discussion table or nil.
function Discussion.get_by_id(id)
return _discussions[id]
end
--- Starts a discussion, switching to the discussion window.
--- @within Discussion
--- @param id string The discussion ID to start.
--- @param return_window string The window ID to return to after the discussion.
--- @param[opt] start_step number The step index to start from (defaults to 1).
function Discussion.start(id, return_window, start_step)
local discussion = _discussions[id]
if not discussion then
trace("Error: Discussion not found: " .. tostring(id))
return
end
local step = start_step or 1
if not discussion.steps[step] then step = 1 end
Context.discussion.active = true
Context.discussion.id = id
Context.discussion.step = step
Context.discussion.selected_answer = 1
Context.discussion.scroll_y = 0
Context.discussion.scroll_timer = 0
Context.discussion.auto_scroll = true
Context.discussion.return_window = return_window or "game"
Meter.hide()
Window.set_current("discussion")
end
--- Gets the current step data for the active discussion.
--- @within Discussion
--- @return table|nil result The current step table or nil.
function Discussion.get_current_step()
if not Context.discussion.active or not Context.discussion.id then return nil end
local discussion = _discussions[Context.discussion.id]
if not discussion then return nil end
return discussion.steps[Context.discussion.step]
end
--- Advances to a specific step or ends the discussion.
--- @within Discussion
--- @param next_step number|nil The step index to go to, or nil to end.
function Discussion.go_to_step(next_step)
if not next_step then
Discussion.finish()
return
end
local discussion = _discussions[Context.discussion.id]
if not discussion or not discussion.steps[next_step] then
Discussion.finish()
return
end
Context.discussion.step = next_step
Context.discussion.selected_answer = 1
Context.discussion.scroll_y = 0
Context.discussion.scroll_timer = 0
Context.discussion.auto_scroll = true
end
--- Ends the active discussion and returns to the previous window.
--- @within Discussion
function Discussion.finish()
local discussion = _discussions[Context.discussion.id]
local return_window = Context.discussion.return_window or "game"
Context.discussion.active = false
Context.discussion.id = nil
Context.discussion.scroll_y = 0
Context.discussion.scroll_timer = 0
Context.discussion.auto_scroll = true
Meter.show()
if discussion and discussion.on_end then
discussion.on_end()
end
Window.set_current(return_window)
end

View File

@@ -1,166 +0,0 @@
--- @section Focus
local FOCUS_DEFAULT_SPEED = 5
local active = false
local closing = false
local driven = false
local center_x = 0
local center_y = 0
local radius = 0
local speed = FOCUS_DEFAULT_SPEED
local on_complete = nil
local driven_initial_r = 0
local driven_max_r = 0
local function max_radius(cx, cy)
local dx = math.max(cx, Config.screen.width - cx)
local dy = math.max(cy, Config.screen.height - cy)
return math.sqrt(dx * dx + dy * dy)
end
--- Starts a focus overlay that reveals content through an expanding circle.
--- @within Focus
--- @param cx number The x-coordinate of the circle center.
--- @param cy number The y-coordinate of the circle center.
--- @param[opt] params table Optional parameters: `speed` (number) expansion rate in pixels/frame, `initial_radius` (number) starting radius in pixels (default 0), `on_complete` (function) callback when overlay disperses.
function Focus.start(cx, cy, params)
params = params or {}
active = true
closing = false
driven = false
center_x = cx
center_y = cy
radius = params.initial_radius or 0
speed = params.speed or FOCUS_DEFAULT_SPEED
on_complete = params.on_complete
end
--- Starts a closing focus overlay that hides content by shrinking the visible circle.
--- @within Focus
--- @param cx number The x-coordinate of the circle center.
--- @param cy number The y-coordinate of the circle center.
--- @param[opt] params table Optional parameters: `speed` (number) shrink rate in pixels/frame, `on_complete` (function) callback when screen is fully covered.
function Focus.close(cx, cy, params)
params = params or {}
active = true
closing = true
driven = false
center_x = cx
center_y = cy
radius = max_radius(cx, cy)
speed = params.speed or FOCUS_DEFAULT_SPEED
on_complete = params.on_complete
end
--- Starts a driven focus overlay whose radius is controlled externally via Focus.set_percentage().
--- The radius maps linearly from initial_radius (at 0%) to the screen corner distance (at 100%).
--- @within Focus
--- @param cx number The x-coordinate of the circle center.
--- @param cy number The y-coordinate of the circle center.
--- @param[opt] params table Optional parameters: `initial_radius` (number) radius at 0% (default 0).
function Focus.start_driven(cx, cy, params)
params = params or {}
active = true
closing = false
driven = true
center_x = cx
center_y = cy
driven_initial_r = params.initial_radius or 0
driven_max_r = max_radius(cx, cy)
radius = driven_initial_r
on_complete = nil
end
--- Sets the visible radius as a percentage of the full screen extent.
--- Only has effect when the overlay is in driven mode (started via Focus.start_driven).
--- @within Focus
--- @param pct number A value from 0 to 1 (0 = initial_radius, 1 = full screen).
function Focus.set_percentage(pct)
if not driven then return end
radius = driven_initial_r + pct * (driven_max_r - driven_initial_r)
end
--- Checks whether the focus overlay is currently active.
--- @within Focus
--- @return boolean Whether the focus overlay is active.
function Focus.is_active()
return active
end
--- Stops the focus overlay immediately.
--- @within Focus
function Focus.stop()
active = false
closing = false
driven = false
radius = 0
on_complete = nil
end
--- Updates the focus overlay animation. No-op in driven mode.
--- @within Focus
function Focus.update()
if not active then return end
if driven then return end
if closing then
radius = radius - speed
if radius <= 0 then
local cb = on_complete
Focus.stop()
if cb then cb() end
end
else
radius = radius + speed
if radius >= max_radius(center_x, center_y) then
local cb = on_complete
Focus.stop()
if cb then cb() end
end
end
end
--- Draws the focus overlay (black screen with circular cutout).
--- Must be called after all other drawing to appear on top of every visual layer.
--- @within Focus
function Focus.draw()
if not active then return end
local cx = center_x
local cy = center_y
local r = radius
local w = Config.screen.width
local h = Config.screen.height
local color = Config.colors.black
if closing and r <= 0 then
rect(0, 0, w, h, color)
return
end
local top = math.max(0, math.floor(cy - r))
local bottom = math.min(h - 1, math.ceil(cy + r))
if top > 0 then
rect(0, 0, w, top, color)
end
if bottom < h - 1 then
rect(0, bottom + 1, w, h - bottom - 1, color)
end
for y = top, bottom do
local dy = y - cy
local half_w = math.sqrt(math.max(0, r * r - dy * dy))
local left = math.floor(cx - half_w)
local right = math.ceil(cx + half_w)
if left > 0 then
rect(0, y, left, 1, color)
end
if right < w then
rect(right, y, w - right, 1, color)
end
end
end

View File

@@ -1,58 +0,0 @@
--- @section Glitch
Glitch = {}
--- Shows the glitch effect.
--- @within Glitch
function Glitch.show()
if Context and Context.glitch then
Context.glitch.enabled = true
end
end
--- Hides the glitch effect.
--- @within Glitch
function Glitch.hide()
if Context and Context.glitch then
Context.glitch.enabled = false
end
end
--- Draws the glitch effect if active.
--- @within Glitch
function Glitch.draw()
if not Context or not Context.glitch or not Context.glitch.enabled then return end
-- Update state timer
Context.glitch.timer = Context.glitch.timer - 1
if Context.glitch.timer <= 0 then
if Context.glitch.state == "active" then
Context.glitch.state = "waiting"
Context.glitch.timer = math.random(20, 60) -- Time to stay fixed
else
Context.glitch.state = "active"
Context.glitch.timer = math.random(40, 100) -- Time to stay glitchy
end
end
-- Draw stripes only when active
if Context.glitch.state == "active" then
for _ = 1, 15 do
local rx = math.random(0, Config.screen.width - 1)
local ry = math.random(0, Config.screen.height - 1)
-- Sample color at the random point
local color = pix(rx, ry)
-- Determine random length for the stripe (2-40)
local length = math.random(2, 40)
-- Draw the vertical stripe
for sy = 0, length - 1 do
local dy = ry + sy
if dy < Config.screen.height then
pix(rx, dy, color)
end
end
end
end
end

View File

@@ -1,334 +0,0 @@
--- @section Meter
local METER_MAX = 1000
local BM_METER_DEFAULT = 200
local ISM_METER_DEFAULT = 500
local WPM_METER_DEFAULT = 200
local METER_GAIN_PER_CHORE = 100
local METER_DECAY_PER_DAY = 20
local COMBO_BASE_BONUS = 0.02
local COMBO_MAX_BONUS = 0.16
local COMBO_TIMEOUT_FRAMES = 600
local METER_FLASH_DURATION = 2.0
local FLASH_COLOR = 4
-- Internal meters for tracking game progress and player stats.
Meter.COLOR_ISM = Config.colors.orange
Meter.COLOR_WPM = Config.colors.blue
Meter.COLOR_BM = Config.colors.red
Meter.COLOR_BG = Config.colors.meter_bg
Meter.COLOR_CONTOUR = Config.colors.white
local _flash = {
wpm = { timer = 0, delta = 0 },
ism = { timer = 0, delta = 0 },
bm = { timer = 0, delta = 0 },
}
--- Gets initial meter values.
--- @within Meter
--- @return result table Initial meter values. </br>
--- Fields: </br>
--- * ism (number) Initial ISM meter value.<br/>
--- * wpm (number) Initial WPM meter value.<br/>
--- * bm (number) Initial BM meter value.<br/>
--- * combo (number) Current combo count.<br/>
--- * combo_timer (number) Frames since last combo action.<br/>
--- * hidden (boolean) Whether meters are hidden.
function Meter.get_initial()
return {
ism = ISM_METER_DEFAULT,
wpm = WPM_METER_DEFAULT,
bm = BM_METER_DEFAULT,
combo = 0,
combo_timer = 0,
hidden = false,
}
end
--- Hides meters.
--- @within Meter
function Meter.hide()
if Context and Context.meters then Context.meters.hidden = true end
end
--- Shows meters.
--- @within Meter
function Meter.show()
if Context and Context.meters then Context.meters.hidden = false end
end
--- Gets max meter value.
--- @within Meter
--- @return number The maximum meter value.
function Meter.get_max()
return METER_MAX
end
--- Sets the decay amount applied to all meters per day.
--- @within Meter
--- @param amount number Amount to subtract from each meter.
function Meter.set_decay(amount)
METER_DECAY_PER_DAY = amount
end
--- Gets the meter decay as a percentage of the max meter value.
--- @within Meter
--- @return number The decay percentage per day.
function Meter.get_decay_percentage()
return math.floor(METER_DECAY_PER_DAY / METER_MAX * 100)
end
--- Gets combo multiplier.
--- @within Meter
--- @return number The current combo multiplier.
function Meter.get_combo_multiplier()
if not Context or not Context.meters then return 1 end
local combo = Context.meters.combo
if combo == 0 then return 1 end
return 1 + math.min(COMBO_MAX_BONUS, COMBO_BASE_BONUS * (2 ^ (combo - 1)))
end
--- Updates all meters.
--- @within Meter
function Meter.update()
if not Context or not Context.game_in_progress or not Context.meters then return end
local m = Context.meters
local in_minigame = string.find(Window.get_current_id(), "^minigame_") ~= nil
if not in_minigame then
if m.combo > 0 then
m.combo_timer = m.combo_timer + 1
if m.combo_timer >= COMBO_TIMEOUT_FRAMES then
m.combo = 0
m.combo_timer = 0
end
end
end
local dt = Context.delta_time or 0
for _, key in ipairs({ "wpm", "ism", "bm" }) do
if _flash[key].timer > 0 then
_flash[key].timer = _flash[key].timer - dt
end
end
end
--- Adds amount to a meter.
--- @within Meter
--- @param key string The meter key (e.g., "wpm", "ism", "bm").
--- @param amount number The amount to add.
function Meter.add(key, amount)
if not Context or not Context.meters then return end
local m = Context.meters
if m[key] ~= nil then
if amount > 0 and (key == "ism" or key == "bm") and m[key] >= METER_MAX then
GameOverWindow.show(key)
return
end
local prev_wpm = (key == "wpm") and m.wpm or nil
local old_val = m[key]
m[key] = math.max(0, math.min(METER_MAX, m[key] + amount))
local actual_delta = m[key] - old_val
if actual_delta ~= 0 and _flash[key] then
_flash[key].delta = actual_delta
_flash[key].timer = METER_FLASH_DURATION
end
if prev_wpm and prev_wpm > 0 and m.wpm == 0 and Context.game_in_progress
and Ascension.get_level() == 5 then
Context.should_ascend = true
end
end
end
--- Called on minigame completion.
--- @within Meter
--- @param is_work boolean If true (work-style minigame), apply combo to WPM/ISM/BM and advance combo. DDR uses `Meter.apply_ddr_reward` instead. Otherwise flat equal gain, combo unchanged.
function Meter.on_minigame_complete(is_work)
local m = Context.meters
if is_work then
local mult = Meter.get_combo_multiplier()
local wpm_delta = math.floor(METER_GAIN_PER_CHORE / mult)
local ism_bm_delta = math.floor(METER_GAIN_PER_CHORE * mult)
Meter.add("wpm", wpm_delta)
Meter.add("ism", ism_bm_delta)
Meter.add("bm", ism_bm_delta)
m.combo = m.combo + 1
m.combo_timer = 0
else
local flat = METER_GAIN_PER_CHORE
Meter.add("wpm", flat)
Meter.add("ism", flat)
Meter.add("bm", flat)
end
end
--- Meter changes after DDR: uses max-meter percentages; combo advances like other work minigames.
--- 0 mistakes: WPM 10%, ISM +5%, BM +5%. 13: WPM 5%, ISM +10%, BM +10%. More than 3: WPM unchanged, ISM +10%, BM +10%.
--- @within Meter
--- @param mistake_count number Total mistakes (missed arrows, wrong inputs, and special-mode rule violations).
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
elseif mistake_count <= 3 then
wpm_pct, ism_pct, bm_pct = -0.05, 0.10, 0.10
else
wpm_pct, ism_pct, bm_pct = 0, 0.10, 0.10
end
if wpm_pct ~= 0 then
Meter.add("wpm", math.floor(max * wpm_pct))
end
if ism_pct ~= 0 then
Meter.add("ism", math.floor(max * ism_pct))
end
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
--- Meter changes for the wake-up button mash: faster completion is better for WPM.
--- Perfect: under 2s — WPM +20%. Good: 23s — WPM +10%, ISM +5%, BM +5%. Bad: over 3s — WPM 5%, ISM +10%, BM +10%.
--- @within Meter
--- @param elapsed_sec number Seconds from minigame start until the bar was filled.
function Meter.apply_wakeup_reward(elapsed_sec)
if not Context or not Context.meters then return end
local max = Meter.get_max()
local wpm_pct, ism_pct, bm_pct
if elapsed_sec < 2 then
wpm_pct, ism_pct, bm_pct = 0.20, 0, 0
elseif elapsed_sec <= 3 then
wpm_pct, ism_pct, bm_pct = 0.10, 0.05, 0.05
else
wpm_pct, ism_pct, bm_pct = -0.05, 0.10, 0.10
end
if wpm_pct ~= 0 then
Meter.add("wpm", math.floor(max * wpm_pct))
end
if ism_pct ~= 0 then
Meter.add("ism", math.floor(max * ism_pct))
end
if bm_pct ~= 0 then
Meter.add("bm", math.floor(max * bm_pct))
end
end
--- Random single meter shift after finishing a coworker discussion: ISM +10%, WPM 10%, or BM +10%.
--- @within Meter
function Meter.apply_coworker_discussion_reward()
if not Context or not Context.meters then return end
if Context.coworker_discussion_meter_applied_today then return end
local max = Meter.get_max()
local delta = math.floor(max * 0.10)
local roll = math.random(1, 3)
if roll == 1 then
Meter.add("ism", delta)
elseif roll == 2 then
Meter.add("wpm", -delta)
else
Meter.add("bm", delta)
end
Context.coworker_discussion_meter_applied_today = true
end
--- After finishing a sumphore discussion: reduce whichever of ISM / WPM / BM is highest by 10% of max (stable tie to ISM, then WPM, then BM).
--- @within Meter
function Meter.apply_sumphore_discussion_reward()
if not Context or not Context.meters then return end
if Context.sumphore_discussion_meter_applied_today then return end
local m = Context.meters
local max = Meter.get_max()
local delta = math.floor(max * 0.10)
local biggest_val_key = "ism"
local biggest_val = m.ism
for _, key in ipairs({ "wpm", "bm" }) do
if m[key] > biggest_val then
biggest_val = m[key]
biggest_val_key = key
end
end
Meter.add(biggest_val_key, -delta)
Context.sumphore_discussion_meter_applied_today = true
end
--- Draws meters.
--- @within Meter
function Meter.draw()
if not Context or not Context.game_in_progress or not Context.meters then return end
if Context.meters.hidden then return end
local m = Context.meters
local max = Meter.get_max()
local screen_w = Config.screen.width
local screen_h = Config.screen.height
local bar_w = screen_w * 0.25
local bar_h = 2
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 meter_list = {
{ key = "wpm", label = "WPM", color = Meter.COLOR_WPM, row = 0 },
{ key = "ism", label = "ISM", color = Meter.COLOR_ISM, row = 1 },
{ key = "bm", label = "BM", color = Meter.COLOR_BM, row = 2 },
}
for _, meter in ipairs(meter_list) do
local label_y = start_y + meter.row * line_h
local bar_y = label_y
local fill_w = math.max(0, math.floor((m[meter.key] / max) * bar_w))
rect(bar_x - 1, bar_y - 1, bar_w + 2, bar_h + 2, Meter.COLOR_CONTOUR)
rect(bar_x, bar_y, bar_w, bar_h, Meter.COLOR_BG)
local flash = _flash[meter.key]
if flash and flash.timer > 0 then
local old_val = m[meter.key] - flash.delta
local old_fill_w = math.max(0, math.floor((old_val / max) * bar_w))
local stable_w = math.min(fill_w, old_fill_w)
if stable_w > 0 then
rect(bar_x, bar_y, stable_w, bar_h, meter.color)
end
if flash.delta > 0 then
local hi_w = fill_w - stable_w
if hi_w > 0 then
rect(bar_x + stable_w, bar_y, hi_w, bar_h, FLASH_COLOR)
end
else
local hi_w = old_fill_w - fill_w
if hi_w > 0 then
rect(bar_x + fill_w, bar_y, hi_w, bar_h, FLASH_COLOR)
end
end
elseif fill_w > 0 then
rect(bar_x, bar_y, fill_w, bar_h, meter.color)
end
---print(meter.label, label_x, label_y, meter.color, false, 1, true)
end
local ascension_y = start_y + 3 * line_h + 1
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

View File

@@ -1,20 +0,0 @@
-- Manages minigame configurations and initial states.
--- @section Minigame
--- Draws a unified win message overlay.
--- @within Minigame
function Minigame.draw_win_overlay(win_text)
local text = win_text or "SUCCESS"
local tw = #text * 6
local th = 6
local padding = 4
local box_w = tw + padding * 2
local box_h = th + padding * 2
local box_x = (Config.screen.width - box_w) / 2
local box_y = (Config.screen.height - box_h) / 2
local text_x = Config.screen.width / 2
rect(box_x, box_y, box_w, box_h, Config.colors.dark_grey)
rectb(box_x, box_y, box_w, box_h, Config.colors.white)
Print.text_center_contour(text, text_x, box_y + padding, Config.colors.black, false, 1, Config.colors.white)
end

View File

@@ -1,87 +0,0 @@
--- @section Timer
local timer_duration = 1800
--- Gets initial timer values.
--- @within Timer
--- @return result table Initial timer values. </br>
--- Fields: </br>
--- * progress (number) Clock timer revolution progress (0 to 1).
function Timer.get_initial()
return {
progress = 0,
}
end
--- Sets the number of frames for one full timer revolution.
--- @within Timer
--- @param frames number Frames per revolution.
function Timer.set_duration(frames)
timer_duration = frames
end
--- Updates the timer and handles revolution events.
--- @within Timer
function Timer.update()
if not Context or not Context.game_in_progress or not Context.meters or not Context.timer then return end
local t = Context.timer
local in_minigame = string.find(Window.get_current_id(), "^minigame_") ~= nil
if not in_minigame then
t.progress = t.progress + (1 / timer_duration)
if t.progress >= 1 then
t.progress = t.progress - 1
end
end
end
--- Draws the clock timer indicator as a circular progress bar.
--- @within Timer
function Timer.draw()
if not Context or not Context.game_in_progress or not Context.meters or not Context.timer then return end
if Context.meters.hidden and not Context.stat_screen_active then return end
local cx = 10
local cy = 8
local r_outer = 5
local r_inner = 3
local progress = Context.timer.progress
local fg_color
if progress <= 0.25 then
fg_color = Config.colors.dark_grey
elseif progress <= 0.5 then
fg_color = Config.colors.light_blue
elseif progress <= 0.75 then
fg_color = Config.colors.blue
elseif progress <= 1 then
fg_color = Config.colors.red
end
local bg_color = Config.colors.white
local start_angle = -math.pi * 0.5
local progress_angle = progress * 2 * math.pi
local r_outer_sq = r_outer * r_outer
local r_inner_sq = r_inner * r_inner
for dy = -r_outer, r_outer do
for dx = -r_outer, r_outer do
local dist_sq = dx * dx + dy * dy
if dist_sq <= r_outer_sq and dist_sq > r_inner_sq then
local angle = math.atan(dy, dx)
local relative = angle - start_angle
if relative < 0 then relative = relative + 2 * math.pi end
if relative <= progress_angle then
pix(cx + dx, cy + dy, fg_color)
else
pix(cx + dx, cy + dy, bg_color)
end
end
end
end
local hand_angle = start_angle + progress_angle
local hand_x = math.floor(cx + math.cos(hand_angle) * (r_inner - 1) + 0.5)
local hand_y = math.floor(cy + math.sin(hand_angle) * (r_inner - 1) + 0.5)
line(cx, cy, hand_x, hand_y, Config.colors.white)
end

View File

@@ -1,135 +0,0 @@
--- @section Trigger
local triggers = {}
--- @within Trigger
--- @param trigger table The trigger data table.
--- @param trigger.id string Unique trigger identifier.
--- @param trigger.duration number Duration in frames before the trigger fires.
--- @param[opt] trigger.on_start function Called when the trigger starts. Defaults to noop.
--- @param[opt] trigger.on_stop function Called when the trigger fires or is manually stopped. Defaults to noop.
--- @param[opt] trigger.repeating boolean If true, trigger restarts after firing. Defaults to false.
function Trigger.register(trigger)
if not trigger or not trigger.id then
trace("Error: Invalid trigger registered (missing id)!")
return
end
if not trigger.duration or trigger.duration <= 0 then
trace("Error: Invalid trigger registered (missing or invalid duration)!")
return
end
if not trigger.on_start then
trigger.on_start = function() end
end
if not trigger.on_stop then
trigger.on_stop = function() end
end
if trigger.repeating == nil then
trigger.repeating = false
end
if triggers[trigger.id] then
trace("Warning: Overwriting trigger with id: " .. trigger.id)
end
triggers[trigger.id] = trigger
end
--- @within Trigger
--- @param id string The trigger ID.
--- @return table|nil result The trigger definition or nil.
function Trigger.get_by_id(id)
return triggers[id]
end
--- @within Trigger
--- @return table result All trigger definitions keyed by ID.
function Trigger.get_all()
return triggers
end
--- @within Trigger
--- @param id string The trigger ID.
--- @return boolean active True if the trigger is running.
function Trigger.is_active(id)
if not Context or not Context.triggers then return false end
return Context.triggers[id] ~= nil
end
--- If already active, restarts from 0.
--- @within Trigger
--- @param id string The trigger ID.
function Trigger.start(id)
if not Context or not Context.triggers then return end
local trigger = triggers[id]
if not trigger then
trace("Error: Cannot start unknown trigger: " .. tostring(id))
return
end
Context.triggers[id] = { elapsed = 0 }
trigger.on_start()
end
--- @within Trigger
--- @param id string The trigger ID.
function Trigger.stop(id)
if not Context or not Context.triggers then return end
local trigger = triggers[id]
if not trigger then
trace("Error: Cannot stop unknown trigger: " .. tostring(id))
return
end
if not Context.triggers[id] then return end
Context.triggers[id] = nil
trigger.on_stop()
end
--- Resets elapsed time to 0 without calling handlers. No-op if inactive.
--- @within Trigger
--- @param id string The trigger ID.
function Trigger.reset(id)
if not Context or not Context.triggers then return end
if not triggers[id] then
trace("Error: Cannot reset unknown trigger: " .. tostring(id))
return
end
if not Context.triggers[id] then return end
Context.triggers[id].elapsed = 0
end
--- Pauses during minigames.
--- @within Trigger
function Trigger.update()
if not Context or not Context.game_in_progress or not Context.triggers then return end
local in_minigame = string.find(Window.get_current_id(), "^minigame_") ~= nil
if in_minigame then return end
local fired = {}
for id, state in pairs(Context.triggers) do
local trigger = triggers[id]
if trigger then
state.elapsed = state.elapsed + 1
if state.elapsed >= trigger.duration then
table.insert(fired, id)
end
else
table.insert(fired, id)
end
end
for _, id in ipairs(fired) do
local trigger = triggers[id]
if trigger then
trigger.on_stop()
if trigger.repeating then
Context.triggers[id] = { elapsed = 0 }
else
Context.triggers[id] = nil
end
else
Context.triggers[id] = nil
end
end
end

View File

@@ -1,9 +1,19 @@
Map.register({
id = "bedroom",
from_x = 0,
from_y = 0,
width = 30,
height = 17,
to_x = 0,
to_y = 0,
})
MapBedroom = {
"10101010101010101010101010101010",
"10141410101010101010101010101010",
"10141410101010101010101010101010",
"10101010101010101010101010101010",
"10101010101010101010101010101010",
"10101010101010101010101010101010",
"10101010101010101010101010101010",
"10101010101010101010101010101010",
"10101010101010101010101010101010",
"11111111111111111111111111111111",
"11111111111111111111111111111111",
"11111111111111111111111111111111",
"11111516111213111111111111111111",
"11111111111111111111111111111111",
"11111111111111111111111111111111",
"11111111111111111111111111111111",
"11111111111111111111111111111111"
}

View File

@@ -1,75 +0,0 @@
-- Manages game maps.
--- @section Map
local _maps = {}
--- Gets all registered maps as an array.
--- @within Map
--- @return result table An array of registered map data. </br>
--- Fields: </br>
--- * id (string) Unique map identifier.<br/>
--- * from_x (number) Source tile X coordinate in the map sheet.<br/>
--- * from_y (number) Source tile Y coordinate in the map sheet.<br/>
--- * width (number) Width in tiles.<br/>
--- * height (number) Height in tiles.<br/>
--- * to_x (number) Destination X coordinate on screen.<br/>
--- * to_y (number) Destination Y coordinate on screen.<br/>
function Map.get_maps_array()
local maps_array = {}
for _, map_data in pairs(_maps) do
table.insert(maps_array, map_data)
end
return maps_array
end
--- Registers a map definition.
--- @within Map
--- @param map_data table The map data table.
--- @param map_data.id string Unique map identifier.<br/>
--- @param map_data.from_x number Source tile X coordinate in the map sheet.<br/>
--- @param map_data.from_y number Source tile Y coordinate in the map sheet.<br/>
--- @param map_data.width number Width in tiles.<br/>
--- @param map_data.height number Height in tiles.<br/>
--- @param map_data.to_x number Destination X coordinate on screen.<br/>
--- @param map_data.to_y number Destination Y coordinate on screen.<br/>
function Map.register(map_data)
if _maps[map_data.id] then
trace("Warning: Overwriting map with id: " .. map_data.id)
end
_maps[map_data.id] = map_data
end
--- Gets a map by ID.
--- @within Map
--- @param map_id string The ID of the map.
--- @return result table The map data table or nil. </br>
--- Fields: </br>
--- * id (string) Unique map identifier.<br/>
--- * from_x (number) Source tile X coordinate in the map sheet.<br/>
--- * from_y (number) Source tile Y coordinate in the map sheet.<br/>
--- * width (number) Width in tiles.<br/>
--- * height (number) Height in tiles.<br/>
--- * to_x (number) Destination X coordinate on screen.<br/>
--- * to_y (number) Destination Y coordinate on screen.<br/>
function Map.get_by_id(map_id)
return _maps[map_id]
end
--- Draws a map.
--- @within Map
--- @param map_id string The ID of the map to draw.
function Map.draw(map_id)
local map_data = Map.get_by_id(map_id)
if not map_data then
return
end
map(
map_data.from_x,
map_data.from_y,
map_data.width,
map_data.height,
map_data.to_x,
map_data.to_y
)
end

View File

@@ -1,9 +0,0 @@
Map.register({
id = "office",
from_x = 60,
from_y = 0,
width = 30,
height = 17,
to_x = 0,
to_y = 0,
})

View File

@@ -1,9 +0,0 @@
Map.register({
id = "street",
from_x = 30,
from_y = 0,
width = 30,
height = 17,
to_x = 0,
to_y = 0,
})

View File

@@ -1,578 +1,280 @@
-- <PALETTE>
-- 000:0404005f574fc2c3c71d2b53fff1e8ab52367e2553ffa30000875129adff83769c00e436fa77a8ff004dc3c3c7ffccaa
-- 000:ab53375f574f0101017f2553c3c3c71d2b53fff1e929adff83779d3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
-- </PALETTE>
-- <TILES>
-- 000:0000000006666660066060600636366006606060060636600666666000000000
-- 001:0000000006666666060600600666666606000600066666660633633300000000
-- 002:0000000066666666006000066666666660060006666666666333363300000000
-- 004:1111111111111111111111111111111111111111111111111111111111111111
-- 005:1111111111111111111111111111111111111111111111111111111100000000
-- 006:0222222200010010033055010130150603305501013015060330550100000000
-- 007:2222222001022220601022206001022060201020602201006022201000000000
-- 008:0000000003330333033303330333000003330111033301110333011103330111
-- 009:0000000033333333333333330000000011100111111001111110011111100111
-- 010:0000000033303330333033300000333011103330111033301110333011103330
-- 011:111111111111111111111100111110a511110a51111015a51101515111000000
-- 012:111111111111111101111111101111115a01111115a011115151011100000111
-- 013:1000000004444444044444440444444404444444044444440444444404444444
-- 014:0000000144444240444424204444424044442420444442404444242044444240
-- 015:0000000004244444024444440424400002444444042424240242424200000000
-- 016:0000000044444420444442400004442044444240e42424204242424000000000
-- 017:0000000002424240042424200244444004244440024404400424044002440440
-- 018:0000000002424240042424200444424004444420044042400440442004404240
-- 019:0222222200010012055016020150660205501602015066020550160200000000
-- 020:2222222022221000222203302222013022220330222201302222033000000000
-- 021:0333011103330111033301110333011103330111033300000333011103330111
-- 022:1110011111100111111001111110011111100111000000001110011111100111
-- 023:1110333011103330111033301110333011103330000033301110333011103330
-- 024:1111111011111110111111101111111011111110111111101111111011111110
-- 025:0444444404444444044444440444444404444444044044440440444404404444
-- 026:4444242044444240444424204444424044442420444442404444242044444240
-- 027:3333111133331111333311113333111111113333111133331111333311113333
-- 028:0424044002440440042404400244444004244440024242400424242000000000
-- 029:0440442004404240044044200444424004444420024242400424242000000000
-- 030:0000000006666660036060600360666006606060036660600360666003606060
-- 031:0222222200010010033033030130130103303303013013010330330300000000
-- 032:2222222001001220303302203013022030330220301302203033022000000000
-- 033:0333011103330111033301110333011103330111033301110333011100000000
-- 034:1110011111100111111001111110011111100111111001111110011100000000
-- 035:1110333011103330111033301110333011103330111033301110333000000000
-- 036:0440444404404444044044440444444404444444044444440444444404444444
-- 037:0000000002222222022000220201110202011102020111020220002202222222
-- 038:0000000022222220220002202011102020111020201110202200022022222220
-- 039:0000000002222222022222220222222202222222022222220222222202222222
-- 040:0000000022222220222222202222222022222220222222202222222022222220
-- 041:0000000002222222022200000220111102201121020112000201120402012200
-- 042:0000000022222220000002201111102021211020000210203430102000430020
-- 043:0666606003606060036066600366606003606060066060600360606003666660
-- 044:0222222202222222022222220222222202222222000000000001111100011111
-- 045:2222222022222220222222202222222022222220000000001111100011111000
-- 046:1111111111111111111111111000000002222222022222220222222202222222
-- 047:1111111111111111111111110000000022222222222222222222222222222222
-- 048:1111111111111111111111110000100022220444e222044422220444e2220444
-- 049:1111111111111111111111111100001100222201022222200222222002222220
-- 050:1111111011111110111111101111111011111110111111101111111011110000
-- 051:1111111111111111111111111111111111111111111111111111111100011111
-- 052:0444444404444444044444440444444404444444044444440444444410000000
-- 053:4444242044444240444424204444424044442420444442404444242000000001
-- 054:0222222202222222022000220201110202011102020111020220002202222222
-- 055:2222222022222220220002202011102020111020201110202200022022222220
-- 056:0222222202222222022222220222222202222222022222220222222200000000
-- 057:2222222022222220222222202222222022222220222222202222222000000000
-- 058:0201122202012222020112220201222202201222022022220222000002222222
-- 059:2204302022204020202200202000002020222020222220200000022022222220
-- 060:5555555555555555555555556666111155555555555555555555555566616666
-- 061:5555555555555555555555556666666655551555555515555555655566666611
-- 062:0222222202222222022222220222222202222222022222220222222202222222
-- 063:2222222222222222222222222222222222222222222222222222222222222222
-- 064:22220444e222044422220444e222044422220444e222044422220444e2220444
-- 065:0222222002222220020000206022220002222220022222200200002000111100
-- 066:5000000004444444044044440440444404404444044044440444444460000000
-- 067:0000000544444240444424204444424044442420444442404444242000000001
-- 068:0000000001111111011111110111111101111111011111110111111100000000
-- 069:0000000011111110111111101111111011111110111111101111111000000000
-- 070:5555555555555555555555551111666655555555555555555555555511666111
-- 071:000000000222220002222090022209900220999002099d900099199009919790
-- 072:0000000022222220222222202222222022220000222011002212000021222220
-- 073:5555555555555555555555556611166655555551555555515555555166666111
-- 074:0000000001111111000000001111666655555555555555555555555511666111
-- 075:0000000011111111000000006611166655555551555555515555555166666111
-- 076:0000000011111111000000001111666655555555555555555555555511666111
-- 077:0000000011111110000000006611166655555551555555515555555166666111
-- 078:1111111111111111111100001110919111101010110191011019191000919190
-- 079:1111111111111111000000009191919000000010111110901111101000000090
-- 080:0919199009919902091990200999020209902020090202020020202000000000
-- 081:0000000002020200202020200200022020002220000222202022222002222220
-- 082:5555555555555055555501056666010155550105555501055555010566610106
-- 083:5555555555555555000000000222222202222222022222220222222202222222
-- 084:5555555555555555000000002222222022222220222222202222222022222220
-- 085:5555000055500220550202206022022002220220022202200222022002220220
-- 086:1000000110333301103333011033330110333301103333011033330110333301
-- 087:3010101033331111333311113333111111113333111133331111333311113333
-- 088:3333101033331090333310103333109011113010111010901110201011101090
-- 089:2222222022222220222222202222222022222220222222202222222022222220
-- 090:5555550055555022555502226611022255550222555502225555501266666100
-- 091:0005010522200105222201052222010622220105222201052210505500066111
-- 092:5000000002222222022222220222222202222222022222220222222202222222
-- 093:0222022002220220022202200222022002220220022202200222022002220220
-- 094:1033330110333301103333011033330110333301103333011033330110000001
-- 095:3333101033331090333310103333109011113010111130901111301011113000
-- 096:0000000001111111000000000106666601051555010515550105655500066611
-- 097:0000000011111110000000006666101055555010555550105555501066616000
-- 098:5555555555555550555555056666116655550055555500555555655566666611
-- 099:0555555500555555050555550661111105500555115005550055555500616666
-- 100:0222222202222222022222220000000000000000501055555010555560006666
-- 101:2222222022222220222222200000000000000000555501055555010566660001
-- 102:0222022002220220022202200222022002220220022202200222022012220220
-- 103:1000000103333330033333300333333003333330033333300333333010000001
-- 104:5500000050222222022222220222222202222222022222220222222202222222
-- 105:0222222222222222222222222222222222222222222222222222222222222222
-- 106:1222000012201110120111101011111001111110011111100111111001111110
-- 107:0000000001111111011111110111111101111111011111115000000066000066
-- 108:0000000011111111111111111111111111111111111111110000000066666611
-- 109:1111111011111110111111101111111011111110111111100000000566000066
-- 110:1111111111111111111111111111010011101044111010441110104400000010
-- 111:1111111111111111111111110011111144011111440111114401111110000000
-- 112:1111111111111111111111111111111111111111111111111111011100004000
-- 113:3333104433331044333310003333111111113333111133331111333311113333
-- 114:4403111144031111000311113333111111110000111044441104444411014444
-- 115:3304440133044401330444013304440100104033440103334440333344103333
-- 116:3330111133020000330124243330111111110111111130111111301111113300
-- 117:1103111100401111242011111240111111203333124033331120333300013333
-- 118:1111111100000000033333330333333303333333033333330000000011111111
-- 119:1111111100000000333333303333333033333330333333300000000011111111
-- 120:1a12222222a222222212222222a222222212222222a222222212222222222222
-- 121:1a8b888822888b8b228b888822b8bb8b22888b88228b88882288b88b228b8b88
-- 122:8b8b8888b8b88b8bb88b888888b8bb8b8b888b8888bb88888b88b88bb88b8b88
-- 123:8b8b888800000000055555550555555500000000055555550555555500000000
-- 124:8b8b888800000000555555555555555500000000555555555555555500000000
-- 125:8b8b888800000000555555505555555000000000555555505555555000000000
-- 126:0000000007777777070000000703333307033333070333330703333307033333
-- 127:0000000077777777000000003333333033333330333333303333333033333330
-- 128:000000007777777700000000e222222222222222e222222222222222e2222222
-- 129:0000000077777770000000702222207022222070222220702222207022222070
-- 130:3000000300ffff000ddddff00dddddf00dddddf00dddddf000dddd0000000000
-- 131:0555555505555555055555550555555500000000050b88880508b88b000b8b88
-- 132:555555555555555555555555555555550000000088bb88888b88b88bb88b8b88
-- 133:555555505555555055555550555555500000000088bb80508b88b050b88b8000
-- 134:0703333307033333070333330703333307033333070333330703333307033333
-- 135:3333333033333330333333303333333033333330333333303333333033333330
-- 136:2222207022222070222220702222207022222070222220702222207022222070
-- 137:4444444444444444444444444444444444444444444444444444444444444444
-- 138:4442222244422222444222224442222244422222444222224442222244422222
-- 139:0000000000bbbb0008888bb0088888b0088888b0088888b00088880030000003
-- 140:0000000007777777076666770767776707677776076777760767777607766667
-- 141:0000000077777770777777707777777077777770777777777677777777777777
-- 142:0000000066666666666666666666666600000000777777777777777777777777
-- 143:0000000066666070666660706666607000000070777777707777777077777770
-- 144:1a80088be2800b8822800888e280088b22800b88e220022221a001a2ea100a12
-- 145:88888888b88b8b888b8888b8888b888888888b8b12222222a1a1a1a21a1a1a12
-- 146:0776777707767777077677770776777707677777076777770777777700000000
-- 147:7677666676777776767777677677767767776777677666667777777700000000
-- 148:7766667777777677777767767776777677677776766666767777777700000000
-- 149:7667777067767770777767706666677077776770777767707777777000000000
-- 150:2220022222200222222002222220022222200222222002222220022222200222
-- 151:1a12222222a222222212222222a222222212222222222222a1a1a1a21a1a1a12
-- 152:2222222222222222222222222222222222222222444444444444444444444444
-- 153:222002222220022222200222222002222220022212200222a000000200000000
-- 154:222222222222222222222222222222222222222212222222a1a1a1a21a1a1a12
-- 155:11111000111100331110a033110a903310a9a0330a9a903309a9a0330a9a9033
-- 156:0000000033333333333333333333333333333333333333333333333333333333
-- 157:000000000a9a9a9a09a9a9a90a9a9a9a09a9a9a90a9a9a9a09a9a9a90a9a9a9a
-- 158:001111119a001111a9a904449a9a9004a9a9a9a09a9a9a9aa9a9a9a99a9a9a9a
-- 159:111111111111111111111111111111111111111100111111090111110a900111
-- 160:1111111111111111444444444444444444444444444444441111111111111111
-- 161:09a9a0330a9a903309a9a0330000000003333333033333330333333303333333
-- 162:3333333333333333333333330000000033333333333333333000000301111110
-- 163:3333333333333333333333330000000033333333333333333333333333333333
-- 164:09a9a9a90a9a9a9a09a9a9a90000000003333333033333330333333303333333
-- 165:a9a9a9a99a9a9a9aa9a9a9a90000000033333333333333333333333333333333
-- 166:09a9a0110a9a9a0109a9a9a00000000033333333333333333000000301111110
-- 167:1111111111111111111111110011111133001111333301113333300133333330
-- 168:0333333003333301033333010333330103333301000000011111111111111111
-- 169:1100001110555501055005500506605005066050055005501055550111000011
-- 170:0333333310333333103333331033333310333333100000001111111111111111
-- 171:0333333303333333033333330333333303333333000000001111111111111111
-- 172:3333333033333301333333013333330133333301000000011111111111111111
-- 173:0333333010333330103333301033333010333330100000001111111111111111
-- 174:111111111111111111111111111111111111111112222222a1a1a1a21a1a1a12
-- 175:1111111111111111111111111111111111111111444444444444444444444444
-- 176:0000000001111110011010100131311001101010010131100111111000000000
-- 177:0000000001111111010100100111111101000100011111110133133300000000
-- 178:0000000011111111001000011111111110010001111111111333313300000000
-- 179:11111111111111111111111111111111111100001110b161110b100010810101
-- 180:1111111111111111111111111111111101111111b01111110001111101011111
-- 181:111111111000111000880110080880100b08801008b080100b80801010b80010
-- 182:081b01010081b00011001b1b11110000111111111111110111111080111108b0
-- 183:01011111000111111b0111110011111111111111111111111111111111111111
-- 184:1111111111111100111110081111108011111010111110811111101811111101
-- 185:110b80100110801080110000b80111108b80111008b801100b1b011080b18010
-- 186:11108b801108b801008b80111100011111111111111111101111100111110818
-- 187:1111111111111111111111111111111111111111000111118001111108011111
-- 188:0000000004444444040010040440404404404004044040440440400404444444
-- 189:0000000044444444044001000440440404400404044044040040040444444444
-- 190:0000000044444444404040044040404044004004444040444000404444444444
-- 191:0000000044444444400410444044044440040004404444044004001444444444
-- 192:0000000044444440444444404444444044444440444444404444444044444440
-- 193:0000000002222222020000000200000002000000020000000200000002000000
-- 194:0000000022222222000000000000000000000000000000000000000000000000
-- 195:0000000022222220000000100000001000000010000000100000001000000010
-- 196:3333999933339999333399993000000002222222000000000444444404111111
-- 197:3333999933339999333399990000000922222203000000004440d6d011201110
-- 198:3333999933339999333399993333999999993333999933339999333399993333
-- 199:0000000001111110031010100310111001101010031110100310111003101010
-- 200:1111110811111101111111101111111011111111111111111111111111111111
-- 201:180b8010810180101810b0108180101008180010108100101108101011108010
-- 202:111081801110180811018081110810b8101801801080b8011008101100100111
-- 203:8b011111b8011111801111110111111111111111111111111111111111111111
-- 204:0440000404404444044040040440440404440014044444440444444404444444
-- 205:4001404404404004000040400440404404404044444444444444444444400000
-- 206:4040041000404404404004004040444440400400444444444404444400044444
-- 207:4444000044440222044402220444022214440222444402224444000044444444
-- 208:00000040e222204022222040e222204022222040e22220400000004044444440
-- 209:0200000002000000020000000200000002000000020000000200000002000000
-- 210:0000001000000010000000100000001000000010000000100000001000000010
-- 211:0444444404111111044444440411111104444444000000000111111100000000
-- 212:4440222011201110444024201120444044402420000000001111111000000000
-- 213:0111101003101010031011100311101003101010011010100310101003111110
-- 214:1000000000606000060606060000000010565656106565651106565611100000
-- 215:0000000100606060060606000000000055555501555555015555501100000111
-- 216:0444444404444044044404040440440004404404044044440444444400000000
-- 217:4404444440444444044444404444440444444440444444444444444400000000
-- 218:4404444440444004044404404444044400004440444444444444444400000000
-- 219:4444440044444402404444000044441044044444444444104444444400000000
-- 220:0000444022204440000044401101444000444440440144404444444000000000
-- 221:0200000002000000020000000200000002000000020000000211111100000000
-- 222:0000000000000000000000000000000000000000000000001111111100000000
-- 223:000000000000000000000000000000000000000000000000111dd11100000000
-- 224:0000001000000010000000100000001000000010000000101111111000000000
-- 225:3333333333333333333333333333333313131313313131311313131331313131
-- 226:0203333302033333020333330203333302031313020131310203131300013131
-- 227:3333302033333020333330203333302013131020313130201313102031313000
-- 228:0000000011111111111111111111111111111111111111111111111100000000
-- 229:3000000004444444044044440440444404404444044044440444444430000000
-- 230:0000000344444240444424204444424044442420444442404444242000000001
-- 231:333333303333330033333090333309901310999031099d901099199009919790
-- 232:3333333333333333333333333333333313130000313011001313000331313131
-- 233:0000000002222222022222220222200202220440022204440220444402204414
-- 234:0000000022222220100001202100002002222220400222204440022044444020
-- 235:3333333333333033333301033333010313130103313101011313010331310101
-- 236:0204414402200441022220040222222002222222022222220222200002220111
-- 237:1444402044140220444402204144022000402220220222200222222010222220
-- 238:3333330033333022333302223333022213130222313102221313101231313100
-- 239:0003010322200103222201032222010322220103222201012210101300013131
-- 240:0222011102220011022200000222033302220333022220000222222202222222
-- 241:1002222001202220002022203002222031222220022222202222222022222220
-- 242:3333333333333330333333033333113313130013313100311313131331313131
-- 243:0333333300333333030333330331133303100313113001310013131300313131
-- 244:022222200222220002222090022209900220999002099d900099199009919790
-- 245:2222222022222220222222202222222022220000222011002212000021222220
-- 246:0000000001111111000000000103333301031313010131310103131300013131
-- 247:0000000011111110000000003333301013131010313130101313101031313000
-- 000:2222222223333332233232322353533223323232232353322333333222222222
-- 001:2222222223333333232322322333333323222322233333332355355522222222
-- 002:2222222233333333223222233333333332232223333333333555535522222222
-- 003:2222222223333333232322322333333323222322233333332355355522222222
-- 004:2222222233333333223222233333333332232223333333333555535522222222
-- 005:2222222223333333232322322333333323222322233333332355355522222222
-- 006:2222222233333333223222233333333332232223333333333555535522222222
-- 007:2222222223333333232322322333333323222322233333332355355522222222
-- 008:2222222233333333223222233333333332232223333333333555535522222222
-- 009:2222222223333333232322322333333323222322233333332355355522222222
-- 010:2222222233333333223222233333333332232223333333333555535522222222
-- 011:2222222223333333232322322333333323222322233333332355355522222222
-- 012:2222222233333333223222233333333332232223333333333555535522222222
-- 013:2222222223333333232322322333333323222322233333332355355522222222
-- 014:2222222233333333223222233333333332232223333333333555535522222222
-- 015:2222222223333333232322322333333323222322233333332355355522222222
-- 016:2222222223333332233232322353533223323232232353322333333222222222
-- 017:1111111111111111111111111111111111111111111111111111111111111111
-- 018:1111111111111111111111111111111111111111111111111111111122222222
-- 019:1111111111111111111111111111111111111111111111111111111122222222
-- 020:1111111111111111111111111111111111111111111111111111111111111111
-- 021:1111111111111111111111111111111111111111111111111111111111111111
-- 022:1111111111111111111111111111111111111111111111111111111111111111
-- 023:1111111111111111111111111111111111111111111111111111111111111111
-- 024:1111111111111111111111111111111111111111111111111111111111111111
-- 025:1111111111111111111111111111111111111111111111111111111111111111
-- 026:1111111111111111111111111111111111111111111111111111111111111111
-- 027:1111111111111111111111111111111111111111111111111111111111111111
-- 028:1111111111111111111111111111111111111111111111111111111111111111
-- 029:1111111111111111111111111111111111111111111111111111111111111111
-- 030:1111111111111111111111111111111111111111111111111111111111111111
-- 031:1111111111111111111111111111111111111111111111111111111111111111
-- 032:2222222223333332233232322353533223323232232353322333333222222222
-- 033:1111111111111111111111111111111111111111111111111111111111111111
-- 034:2444444422212212255200212152102325520021215210232552002122222222
-- 035:4444444221244442321244423221244232421242324421223244421222222222
-- 036:1111111111111111111111111111111111111111111111111111111111111111
-- 037:2222222225552555255525552555222225552111255521112555211125552111
-- 038:2222222255555555555555552222222211122111111221111112211111122111
-- 039:2222222255525552555255522222555211125552111255521112555211125552
-- 040:1111111111111111111111111111111111111111111111111111111111111111
-- 041:1111111111111111111111221111128011112801111210801121010111222222
-- 042:1111111111111111211111111211111108211111108211110101211122222111
-- 043:1222222226666666266666662666666626666666266666662666666626666666
-- 044:2222222166666462666646426666646266664642666664626666464266666462
-- 045:2222222226466666246666662646622224666666264646462464646422222222
-- 046:2222222266666642666664622226664266666462464646426464646222222222
-- 047:2222222226466666246666662646622224666666264646462464646422222222
-- 048:2222222223333332233232322353533223323232232353322333333222222222
-- 049:1111111111111111111111111111111111111111111111111111111111111111
-- 050:2444444422212214200213242102332420021324210233242002132422222222
-- 051:4444444244441222444425524444215244442552444421524444255222222222
-- 052:1111111111111111111111111111111111111111111111111111111111111111
-- 053:2555211125552111255521112555211125552111255522222555211125552111
-- 054:1112211111122111111221111112211111122111222222221112211111122111
-- 055:1112555211125552111255521112555211125552222255521112555211125552
-- 056:1111111111111111111111111111111111111111111111111111111111111111
-- 057:1111111211111112111111121111111211111112111111121111111211111112
-- 058:1111111111111111111111111111111111111111111111111111111111111111
-- 059:2666666626666666266666662666666626666666266266662662666626626666
-- 060:6666464266666462666646426666646266664642666664626666464266666462
-- 061:5555111155551111555511115555111111115555111155551111555511115555
-- 062:5555111155551111555511115555111111115555111155551111555511115555
-- 063:5555111155551111555511115555111111115555111155551111555511115555
-- 064:2222222223333332253232322532333223323232253332322532333225323232
-- 065:1111111111111111111111111111111111111111111111111111111111111111
-- 066:2444444422212212255255252152152125525525215215212552552522222222
-- 067:4444444221221442525524425215244252552442521524425255244222222222
-- 068:1111111111111111111111111111111111111111111111111111111111111111
-- 069:2555211125552111255521112555211125552111255521112555211122222222
-- 070:1112211111122111111221111112211111122111111221111112211122222222
-- 071:1112555211125552111255521112555211125552111255521112555222222222
-- 072:1111111111111111111111111111111111111111111111111111111111111111
-- 073:1111111211111112111111121111111211111112111111121111111211111112
-- 074:1111111111111111111111111111111111111111111111111111111111111111
-- 075:2662666626626666266266662666666626666666266666662666666626666666
-- 076:6666464266666462666646426666646266664642666664626666464266666462
-- 077:2222222224444444244222442421112424211124242111242442224424444444
-- 078:2222222244444442442224424211124242111242421112424422244244444442
-- 079:2222222224444444244444442444444424444444244444442444444424444444
-- 080:2333323225323232253233322533323225323232233232322532323225333332
-- 081:1111111111111111111111111111111111111111111111111111111111111111
-- 082:2444444424444444244444442444444424444444222222222221111122211111
-- 083:4444444244444442444444424444444244444442222222221111122211111222
-- 084:1111111111111111111111111111111111111111111111111111111111111111
-- 085:1111111111111111111111111222222224444444244444442444444424444444
-- 086:1111111111111111111111112222222244444444444444444444444444444444
-- 087:1111111111111111111111112222122244442666444426664444266644442666
-- 088:1111111111111111111111111122221122444421244444422444444224444442
-- 089:1111111211111112111111121111111211111112111111121111111211112222
-- 090:1111111111111111111111111111111111111111111111111111111122211111
-- 091:2666666626666666266666662666666626666666266666662666666612222222
-- 092:6666464266666462666646426666646266664642666664626666464222222221
-- 093:2444444424444444244222442421112424211124242111242442224424444444
-- 094:4444444244444442442224424211124242111242421112424422244244444442
-- 095:2444444424444444244444442444444424444444244444442444444422222222
-- 096:2222222223333332253232322532333223323232253332322532333225323232
-- 097:0000000000000000000000003333111100000000000000000000000033313333
-- 098:0000000000000000000000003333333300001000000010000000300033333311
-- 099:0000000000000000000000003333111100000000000000000000000033313333
-- 100:0000000000000000000000003333333300001000000010000000300033333311
-- 101:2444444424444444244444442444444424444444244444442444444424444444
-- 102:4444444444444444444444444444444444444444444444444444444444444444
-- 103:4444266644442666444426664444266644442666444426664444266644442666
-- 104:2444444224444442242222423244442224444442244444422422224222111122
-- 105:0000000000000000000000003333111100000000000000000000000033313333
-- 106:0000000000000000000000003333333300001000000010000000300033333311
-- 107:0222222226666666266266662662666626626666266266662666666632222222
-- 108:2222222066666462666646426666646266664642666664626666464222222221
-- 109:2222222221111111211111112111111121111111211111112111111122222222
-- 110:2222222211111112111111121111111211111112111111121111111222222222
-- 111:2222222223333333232322322333333323222322233333332355355522222222
-- 112:2333323225323232253233322533323225323232233232322532323225333332
-- 113:0000000000000000000000001111333300000000000000000000000011333111
-- 114:2222222224444422244442722444277224427772242770722277177227717072
-- 115:2222222244444442444444424444444244442222444211224414222241444442
-- 116:0000000000000000000000003311133300000001000000010000000133333111
-- 117:2222222221111111222222221111333300000000000000000000000011333111
-- 118:2222222211111111222222223311133300000001000000010000000133333111
-- 119:2222222211111111222222221111333300000000000000000000000011333111
-- 120:2222222211111112222222223311133300000001000000010000000133333111
-- 121:0000000000000000000000001111333300000000000000000000000011333111
-- 122:0000000000000000000000003311133300000001000000010000000133333111
-- 123:0000000000000000000000001111333300000000000000000000000011333111
-- 124:0000000000000000000000003311133300000001000000010000000133333111
-- 125:0000000000000000000000001111333300000000000000000000000011333111
-- 126:0000000000000000000000003311133300000001000000010000000133333111
-- 127:2222222223333332233232322353533223323232232353322333333222222222
-- 128:2222222223333332253232322532333223323232253332322532333225323232
-- 129:0000000000000000000000003333111100000000000000000000000033313333
-- 130:2717177227717724271772422777242427724242272424242242424222222222
-- 131:2222222224242422424242422422244242224442222444424244444224444442
-- 132:0000000000000000000000003333333300001000000010000000300033333311
-- 133:0000000000000200000021203333212100002120000021200000212033312123
-- 134:0000000000000000000000003333333300001000000010000000300033333311
-- 135:0000000000000000222222222444444424444444244444442444444424444444
-- 136:0000000000000000222222224444444244444442444444424444444244444442
-- 137:0000000000000000000000003333111100000000000000000000000033313333
-- 138:0000000000000000000000003333333300001000000010000000300033333311
-- 139:0000222200022442002424423244244224442442244424422444244224442442
-- 140:0000000000000000000000003333333300001000000010000000300033333311
-- 141:0000000000000000000000003333111100000000000000000000000033313333
-- 142:0000000000000000000000003333333300001000000010000000300033333311
-- 143:1222222112555521125555211255552112555521125555211255552112555521
-- 144:2333323225323232253233322533323225323232233232322532323225333332
-- 145:0000000000000000000000001111333300000000000000000000000011333111
-- 146:2444444424444444244444442444444424444444244444442444444424444444
-- 147:4444444244444442444444424444444244444442444444424444444244444442
-- 148:0000002200000244000024443311244400002444000024440000021433333122
-- 149:2220212044422120444421204444212344442120444421204412020022233111
-- 150:0000000000000000000000003311133300000001000000010000000133333111
-- 151:2444444424444444244444442444444424444444244444442444444424444444
-- 152:4444444244444442444444424444444244444442444444424444444244444442
-- 153:0000000000000000000000001111333300000000000000000000000011333111
-- 154:0222222224444444244444442444444424444444244444442444444424444444
-- 155:2444244224442442244424422444244224442442244424422444244224442442
-- 156:0000000000000000000000003311133300000001000000010000000133333111
-- 157:0000000000000000000000001111333300000000000000000000000011333111
-- 158:0000000000000000000000003311133300000001000000010000000133333111
-- 159:1255552112555521125555211255552112555521125555211255552112222221
-- 160:2222222223333332253232322532333223323232253332322532333225323232
-- 161:0000000000000000000000003333111100000000000000000000000033313333
-- 162:2222222221111111222222222123333321201000212010002120300022233311
-- 163:2222222211111112222222223333121200000212000002120000021233313222
-- 164:0000000000000002000000203333113300002200000022000000300033333311
-- 165:2000000022000000202000002331111120022000110220002200000022313333
-- 166:0000000000000000000000003333333300001000000010000000300033333311
-- 167:2444444424444444244444442222222222222222021200000212000032223333
-- 168:4444444244444442444444422222222222222222000021200000212033332221
-- 169:0000000000000000000000003333111100000000000000000000000033313333
-- 170:2444444424444444244444442444444424444444244444442444444424444444
-- 171:2444244224442442244424422444244224442442244424422444244214442442
-- 172:0000000000000000000000003333333300001000000010000000300033333311
-- 173:0000000000000000000000003333111100000000000000000000000033313333
-- 174:0000000000000000000000003333333300001000000010000000300033333311
-- 175:1222222125555552255555522555555225555552255555522555555212222221
-- 176:2333323225323232253233322533323225323232233232322532323225333332
-- 177:0000000000000000000000001111333300000000000000000000000011333111
-- 178:0000000000000000000000003311133300000001000000010000000133333111
-- 179:0000000000000000000000001111333300000000000000000000000011333111
-- 180:0000000000000000000000003311133300000001000000010000000133333111
-- 181:0000000000000000000000001111333300000000000000000000000011333111
-- 182:0000000000000000000000003311133300000001000000010000000133333111
-- 183:0000000000000000000000001111333300000000000000000000000011333111
-- 184:0000000000000000000000003311133300000001000000010000000133333111
-- 185:0022222202444444244444442444444424444444244444442444444424444444
-- 186:2444444444444444444444444444444444444444444444444444444444444444
-- 187:1444222214421112142111121211111221111112211111122111111221111112
-- 188:0000000000000000000000003311133300000001000000010000000133333111
-- 189:0000000000000000000000001111333300000000000000000000000011333111
-- 190:0000000000000000000000003311133300000001000000010000000133333111
-- 191:2222222223333333232322322333333323222322233333332355355522222222
-- 192:2222222223333332253232322532333223323232253332322532333225323232
-- 193:0000000000000000000000003333111100000000000000000000000033313333
-- 194:2222222224444444244444442444444424444444244444442444444424444444
-- 195:2222222244444442444444424444444244444442444444424444444244444442
-- 196:0000000000000000000000003333333300001000000010000000300033333311
-- 197:0000000000000000000000003333111100000000000000000000000033313333
-- 198:0000000000000000000000003333333300001000000010000000300033333311
-- 199:0000000000000000000000003333111100000000000000000000000033313333
-- 200:0000000000000000000000003333333300001000000010000000300033333311
-- 201:2222222221111111211111112111111121111111211111110222222233222233
-- 202:2222222211111111111111111111111111111111111111112222222233333311
-- 203:1111111211111112111111121111111211111112111111122222222033222233
-- 204:0000000000000000000000003333333300001000000010000000300033333311
-- 205:0000000000000000000000003333111100000000000000000000000033313333
-- 206:0000000000000000000000003333333300001000000010000000300033333311
-- 207:2222222223333332233232322353533223323232232353322333333222222222
-- 208:2333323225323232253233322533323225323232233232322532323225333332
-- 209:0000000000000000000000001111333300000000000000000000000011333111
-- 210:2444444424444444244444442444444424444444244444442444444424444444
-- 211:4444444244444442444444424444444244444442444444424444444244444442
-- 212:0000000000000000000000003311133300000001000000010000000133333111
-- 213:0000000000000000000000001111333300000000000000000000000011333111
-- 214:0000000000000000000000003311133300000001000000010000000133333111
-- 215:0000000000000000000000001111333300000000000000000000000011333111
-- 216:0000000000000000000000003311133300000001000000010000000133333111
-- 217:0000000000000000000000001111333300000000000000000000000011333111
-- 218:0000000000000000000000003311133300000001000000010000000133333111
-- 219:0000000000000000000000001111333300000000000000000000000011333111
-- 220:0000000000000000000000003311133300000001000000010000000133333111
-- 221:0000000000000000000000001111333300000000000000000000000011333111
-- 222:0000000000000000000000003311133300000001000000010000000133333111
-- 223:1222222112555521125555211255552112555521125555211255552112555521
-- 224:2222222223333332253232322532333223323232253332322532333225323232
-- 225:0000000000000000000000003333111100000000000000000000000033313333
-- 226:2222222221111111211111112111111121111111211111112111111122222222
-- 227:2222222211111112111111121111111211111112111111121111111222222222
-- 228:0000000000000000000000003333333300001000000010000000300033333311
-- 229:0000000000000000000000003333111100000000000000000000000033313333
-- 230:0000000000000000000000003333333300001000000010000000300033333311
-- 231:0000000000000000000000003333111100000000000000000000000033313333
-- 232:0000000000000000000000003333333300001000000010000000300033333311
-- 233:0000000000000000000000003333111100000000000000000000000033313333
-- 234:0000000000000000000000003333333300001000000010000000300033333311
-- 235:0000000000000000000000003333111100000000000000000000000033313333
-- 236:0000000000000000000000003333333300001000000010000000300033333311
-- 237:0000000000000000000000003333111100000000000000000000000033313333
-- 238:0000000000000000000000003333333300001000000010000000300033333311
-- 239:1255552112555521125555211255552112555521125555211255552112222221
-- 240:2333323225323232253233322533323225323232233232322532323225333332
-- 241:0000000000000000000000001111333300000000000000000000000011333111
-- 242:0000000000000000000000003311133300000001000000010000000133333111
-- 243:0000000000000000000000001111333300000000000000000000000011333111
-- 244:0000000000000000000000003311133300000001000000010000000133333111
-- 245:0000000000000000000000001111333300000000000000000000000011333111
-- 246:0000000000000000000000003311133300000001000000010000000133333111
-- 247:0000000000000000000000001111333300000000000000000000000011333111
-- 248:0000000000000000000000003311133300000001000000010000000133333111
-- 249:0000000000000000000000001111333300000000000000000000000011333111
-- 250:0000000000000000000000003311133300000001000000010000000133333111
-- 251:0000000000000000000000001111333300000000000000000000000011333111
-- 252:0000000000000000000000003311133300000001000000010000000133333111
-- 253:0000000000000000000000001111333300000000000000000000000011333111
-- 254:0000000000000000000000003311133300000001000000010000000133333111
-- 255:1222222125555552255555522555555225555552255555522555555212222221
-- </TILES>
-- <SPRITES>
-- 000:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 001:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 002:cccccccccccc0000ccc04444cc044444cc044444c044ffffc04fffffc04f3333
-- 003:cccccccc0000cccc44160ccc424260cc442410ccff4260ccfff410cc00f260cc
-- 004:ccccc000cccc0555ccc06555cc065555cc0555ffcc056fffcc065f0fcc055fff
-- 005:00cccccc550ccccc5560cccc55560cccff550cccfff50cccf0f60cccfff50ccc
-- 006:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 007:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 008:cccc0000ccc05a55ccc055a5ccc0a55accc05a57ccc05533cc0575f3ccc0757f
-- 009:00000ccca55a50cc5a55a0cc5755550cfff7a50c3f335a0cfff3f50cfffffa0c
-- 010:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 011:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 012:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 013:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 014:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 015:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 016:cccccccccccccccccccccccccccccc0cccccc050cccc0535ccc05351cc053535
-- 017:cccccccccccccccccccccccc0ccccccc500ccccc1510cccc51510ccc151510cc
-- 018:c04f99ffcc0fffffccc0ff00cccc0fffcccc0666ccc05555cc056525c0160555
-- 019:99f410ccfff260ccff600cccf60ccccc600ccccc55500ccc555550cc5550550c
-- 020:cc056f6fcc0655f6c065511f0650122f0501222f06022122c5022322c0022322
-- 021:f6f50ccc6f560cccf11560ccf22150ccf22260cc221230cc223210cc221210cc
-- 022:cccccc00ccccc051cccc0155ccc05515ccc0157fccc05513cc057f0fcc017fff
-- 023:000ccccc5550cccc51550ccc155150ccff7f510cff31f20cfff0ff0c00fff20c
-- 024:cccc057fccccc005cccc0555ccc05599cc055911c0559911c0551199c0551199
-- 025:f3ff50ccfff50ccc5f50cccc441500cc7799510c4499150c7711955044119550
-- 026:ccccc000ccc00133cc013313cc031333c0133333c031f7f7c0113339c031999f
-- 027:0000cccc13310ccc131330cc3333130c3333310cf7f1130c3337310c9997530c
-- 028:ccccccccccccc000cccc0a77ccc05777cc057777c0577777c05775a5c077a7ff
-- 029:cccccccc0000cccc777a0ccc777750cc7777750c77777750a5a57750fff7a770
-- 030:ccccc000cccc0515ccc01151cc055555cc01517fcc0557ffcc01ff0fcc07ff1f
-- 031:00cccccc150ccccc5110cccc5550ccccf7110cccff750cccf0f10cccf1f50ccc
-- 032:cc035351cc053500cc0351ffcc051f0fccc0ff0fccc0ffffcccc0ff0ccc020ff
-- 033:555550cc005550ccff1550ccf0f150ccf0ff0cccffff0ccc0ff0ccccff020ccc
-- 034:c0613525c0163555c0613525c0163311c0003311c0f03333cc003333ccc03333
-- 035:5556550c5553550c5556550c1330550c1330000c3330ff0c333000cc3330cccc
-- 036:cc022322cc022333cc03331acc0ff31acc0ff3a1ccc003a1cccc0a1acccc0a1a
-- 037:223210cc333210cc111300cc111f0ccc11100ccc1110cccc1110cccc1110cccc
-- 038:ccc007ffcc01117fc016171f0155175501561755055117550f7157550ff15755
-- 039:fffff70c000f20ccffff710c555571105555716055557110555575f0555575f0
-- 040:c0559911c0559911c0ff5333c0fff511c07fff11cc07f511ccc03110cccc0110
-- 041:479915504499155047333f7014113ff011113f701111000c00110cccc0110ccc
-- 042:c035777fcc017766ccc05777cc088855c08bb85508bbb84508bbb8440bb8b844
-- 043:7777530c677500cc7750cccc55880ccc558b80cc548bb80c228bbb80428b8b80
-- 044:cc0a7f3fc075ff3f0777ffff07757fdf077a57fd0575357fc05a66a7cc06aa6f
-- 045:ff3f7a0cff3ff70cfffff770ffdf7570ddf75a70ff75157077a66a50ff6aa60c
-- 046:cc05ffffccc05ff3cccc05ffcc007755c0242777c0212171c0242771c0212171
-- 047:fff50ccc3f50ccccf50ccccc5770cccc77720ccc17120ccc17720ccc17120ccc
-- 048:cc020123cc020143cc020143cc010309cc0f0300ccc0030ccccc030cccc0000c
-- 049:324030cc344030cc344030cc903010cc0030f0ccc0300cccc030ccccc0000ccc
-- 050:ccc0330cccc0330cccc0330cccc0330cccc0350ccc03110ccc01110ccc00000c
-- 051:0330cccc0330cccc0330cccc0330cccc0530cccc01130ccc01110ccc00000ccc
-- 052:cccc01a1cccc01a1cccc0a1acccc0a1acccc0000ccccc0f0ccccc0f0ccccc00c
-- 053:1110cccc1110cccc1110cccc1110cccc0000ccccc0f0ccccc0f0ccccc00ccccc
-- 054:0ff7646607f59999c0099100cc03990ccc09910ccc01330ccc01220ccc0000cc
-- 055:666646f0999993f00099100cc03990ccc09910ccc01330ccc01220ccc0000ccc
-- 056:cccc0110cccc0110cccc0110cccc0110cccc0110cccc0330cccc0650cccc0000
-- 057:c0110cccc0110cccc0110cccc0110cccc0110cccc0330cccc0650cccc0000ccc
-- 058:0888b8440f78b8440f58884405031300c0c0310cccc0130cccc0350cccc0000c
-- 059:218b8880428b87f0218885f000130050c0310c0cc0130cccc0350cccc0000ccc
-- 060:c056aa66c01f6aaac01fa666c07f6aaacc0f6aaaccc0a666cccc07d7cccc0000
-- 061:666aa650aaaa6f106666af10aaaa6f70aaaa600c6666a0cc007d70cccc0000cc
-- 062:c0242777c0ff6111c0fff333c05f5333cc003300cccc030ccccc030ccccc000c
-- 063:77720ccc116f0ccc335ff0cc333f0ccc0330cccc030ccccc030ccccc0000cccc
-- 064:ccccccccccccc000cccc0111ccc03311cc011117cc03317fcc0117ffc0553333
-- 065:cccccccc00cccccc110ccccc1110cccc77110cccff710cccff770ccc13330ccc
-- 066:cccccc00ccccc033cccc0333ccc03333ccc033f7ccc035ffcc0535ffcc07f333
-- 067:0ccccccc3000cccc33330ccc33330ccc37f30cccffff0cccffff0cccf333f0cc
-- 068:ccccccccccccccccccccccccccccccccccccc000cccc0122ccc01244cc01447f
-- 069:cccccccccccccccccccccccccccccccc000ccccc2210cccc44210cccf74410cc
-- 070:ccccccccccccccccccccc000cccc0888ccc08888cc088880c0838801c038805f
-- 071:cccccccccccccccc0000cccc88880ccc880080cc0055080c1666530cf7ff730c
-- 072:cccccccccccccc00ccccc055cccc0555ccc05577ccc057ffccc07f55cc0fff33
-- 073:cccccccc00000ccc555550cc5555550c77777550ffffff507f755f703f333ff0
-- 074:cccccccccccccc00ccccc013cccc0131ccc01313ccc03131cc031377cc0131ff
-- 075:cccccccc000ccccc1310cccc31310ccc13130ccc313130ccffff70ccfffff0cc
-- 076:cccccccccccccc00ccccc055cccc0555ccc05577ccc057ffcc07ff55cc0fff99
-- 077:cccccccc0000cccc55550ccc555550cc7577550cfffff50c5f555f0c3f993f0c
-- 078:cccccccccccccccccccccccccccccccccccc0000cccc0444cccc0444cccc0555
-- 079:cccccccccccccccccccccccccccccccc000ccccc440ccccc440ccccc550ccccc
-- 080:c077500fcc0ff77fcc05fff5ccc05ff7cccc015fccc0f315cc0ff3ffc0ff3333
-- 081:f00f0cccf77f0ccc5ff0cccc7f50ccccf50ccccc53f0ccccf3f50ccc333f0ccc
-- 082:cc0fff00cc05ffffccc057f5cccc007fccc03335cc01313fc031313303113133
-- 083:ff00f0ccffff10cc55f10cccff70cccc5530ccccff310ccc33310ccc333110cc
-- 084:cc0447ffcc0fffffcc07f33fcc0ff11fcc0f7fffccc0ff75cccc0424cccc0142
-- 085:ff7440ccffff10ccf33f70ccf11f50ccfff7f0cc57ff0ccc4240cccc44110ccc
-- 086:c0838039c038803bc0838013c038307fc083805fcc083805ccc08383cccc0333
-- 087:9953990cbb33bb0c9951390cfffff50cff66f10c7fff50cc15550ccc33330ccc
-- 088:cc0f7ff0ccc05fffcccc07ffccccc07fcccc021fccc02441cc024444c0124244
-- 089:fff0ff70f1ffff50fffff70c111f70ccffff10cc1111420c4244442042442410
-- 090:cc0f5733cc0f1f23ccc00fffcccc077fccc01117cc024421c0444142c0421444
-- 091:ff33f0ccff23f0ccfffff0cc11ff0ccc7770cccc11240ccc444240cc4244140c
-- 092:ccc05fffcccc05ffccccc01fcccccc01ccccc014cccc0814ccc01814ccc08114
-- 093:fffff50c111f50ccfff50ccc1110cccc42410ccc424180cc424180cc424110cc
-- 094:cccc0fffcccc0f3fcccc0f0fcccc0fffccccc0f3cccc0377ccc031ffcc0f1f11
-- 095:ff0ccccc3f0ccccc0f0cccccff0cccccf0cccccc730cccccf130cccc1f1f0ccc
-- 096:c0ff3333c033133305f333330ff31333033333330f5313330ff1311107f13333
-- 097:333f0ccc333f50cc333f50cc333ff0cc333330cc3335f50c111ff50c3337f0cc
-- 098:03113133031131330333313303113131033331320555313107ff313307ff3131
-- 099:333110cc333110cc333330cc213110cc423330cc21317f0c33317f501131750c
-- 100:ccc04414cc044441c0444424c042442404424424044144240441442404414424
-- 101:414420cc1444420c424444204244422042444240424441404244414042444140
-- 102:ccc03333cc03a353cc02a353cc0a3323c0a23333c02a3333c0a23333c02a3333
-- 103:33330ccc553530cc553530cc2232320c3333310c3333320c3333313033333230
-- 104:c0144144c0144244c0144144c0144124c017f311c01ff133c01fff33cc07f330
-- 105:4244142042442410424414204244122012113f7031333ff033333ff000033f0c
-- 106:c0421444c0441444c0421444c0421444c0111111c0ff1a5ac0f755a5c0755a50
-- 107:4444140c4244140c4444140c4244140c1111110c5a5a55f0a5a5a5f0001a5170
-- 108:ccc01814ccc08114ccc01814ccc08113cc001333c05fff33cc0f7733cc055330
-- 109:4241810c4241180c4241810c2223180c1113310c33335ff03333f7700033550c
-- 110:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 111:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 112:05533333c0033330ccc0330ccc03330cc03330ccc03330ccc01110ccc0000ccc
-- 113:33000ccc030ccccc030ccccc030ccccc030ccccc030ccccc0110cccc0000cccc
-- 114:05f53131c0003131c0331131c0111131c0111130cc01110cccc0330cccc0000c
-- 115:113110cc113110cc113110cc113110cc003110cccc0110cccc0330cccc0000cc
-- 116:0fff242407f72211c0552400cc00240cccc0240cccc0240cccc0220cccc0000c
-- 117:424441f0112227f000442550c044200cc04420ccc04420ccc02220ccc00000cc
-- 118:0333333307ff333307f73300c070030ccc0c030ccccc030ccccc010ccccc000c
-- 119:33333570333331f000033570cc03300ccc030ccccc030ccccc010ccccc0000cc
-- 120:ccc00330ccccc030ccccc030ccccc030ccccc030ccccc030ccccc030ccccc000
-- 121:cc0300cccc030ccccc030ccccc030ccccc030ccccc030ccccc030ccc0c0000cc
-- 122:c05015a0cc0c0a50cccc05a0cccc0a50cccc05a0cccc0250cccc0990cccc0000
-- 123:c015a150cc0a500ccc05a0cccc0a50cccc05a0cccc0250cccc0990cccc0000cc
-- 124:ccc00330ccccc030ccccc030ccccc030ccccc030ccccc030ccccc030ccccc000
-- 125:c03000ccc030ccccc030ccccc030ccccc030ccccc030ccccc030ccccc0000ccc
-- 126:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 127:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 128:ccccccccccccccccccccccccccccccccccccccccccccccccccccccc0ccccccc0
-- 129:ccccccccccccccccccccccccccccccccc000000c011515101151515151515151
-- 130:cccccccccccccccccccccccccccccccccccccccccccccccc0ccccccc10cccccc
-- 131:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 132:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 133:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 134:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 135:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 136:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 137:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 138:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 139:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 140:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 141:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 142:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 143:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 144:cccccc05ccccc015ccccc015cccc057fcccc05ffcccc017fccc03317cc073331
-- 145:1ffffff7ff0fff0fff0fff0fffffffffff7555fffffffffffff111ff57fffff7
-- 146:10cccccc710cccccf50cccccff0cccccff50ccccff510ccc751310cc5133370c
-- 147:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 148:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 149:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 150:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 151:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 152:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 153:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 154:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 155:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 156:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 157:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 158:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 159:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 160:c077333303331333033313330331333303313333055133330ff513330ff71111
-- 161:1111111133333333333337333333373333333733333337333333373311117711
-- 162:1333370c3733130c7773130c333331303333313033333150333337f011111750
-- 163:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 164:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 165:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 166:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 167:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 168:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 169:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 170:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 171:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 172:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 173:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 174:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 175:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 176:07753333c0031333ccc03313cccc0333cccc0333cccc0333ccccc033ccccc000
-- 177:33357733333335333333333313131313000000000cccccc00cccccc00cccccc0
-- 178:333335703333100c33100ccc1330cccc3330cccc3330cccc3330cccc0000cccc
-- 179:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 180:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 181:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 182:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 183:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 184:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 185:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 186:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 187:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 188:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 189:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 190:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 191:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 192:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 193:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 194:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 195:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 196:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 197:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 198:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 199:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 200:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 201:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 202:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 203:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 204:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 205:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 206:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 207:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 208:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 209:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 210:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 211:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 212:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 213:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 214:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 215:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 216:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 217:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 218:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 219:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 220:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 221:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 222:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 223:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 224:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 225:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 226:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 227:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 228:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 229:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 230:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 231:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 232:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 233:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 234:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 235:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 236:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 237:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 238:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 239:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 240:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 241:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 242:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 243:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 244:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 245:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 246:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 247:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 248:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 249:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 250:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 251:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 252:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 253:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 254:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 255:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- </SPRITES>
-- <MAP>
-- 000:20102010200010201020102010201020102010201020102000102010201040404040404087f3f3f3f397a7b7c7d7a7e7f70818a7b7c7d7a7b7c7d7a70b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 001:40404040400040404040404040404040404040404040404000404040404040404040404087f3f3f3f328a7384858a76878f388a7384858a7384858a70b40403b4b4040404040404040404040404040404040404040404040400b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 002:408090a04000406070408090a040b0c0d0e0f001f001112100408090a040984098409840a8f3f3f3f3b8a7a7a7a7a7c8d8e8f8a7a7a7a7a7a7a7a7a70b405b6b7b4040404040404040404040404040d0e0f001f001f00111210b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 003:4051617140004031414051617140814091a1b1b1b1b1c1d1004051617140984098409840a8f3f3f3f3091919191919293949591919191919191919190b8b9babbb4040cbdbebfb0c401c2c2c2c3c4091a14c5c6c6c6c6cc1d10b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 004:4012223240e140f1024012223240814042a15262728292a2e14012223240984098409840a8f3f3f3f369f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f37c8c9cacbc7282ccdcecfc0d401d3030302d4042a13d4d7282728292a27c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 005:4040404040b240c2d240e2f203132333435363738393a3b3b24040404040984098409840a8f3f3f3f369f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f35d406d7d40e3958d9dadbdcd40ddedfded0e404353839383938393a3b35d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 006:d3c3d3c3d3e1c3d3c3d3e3f30414c3d32434445410201020e1c3d3c3d3c3404040404040798989898999a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a97c1e1e1e1e44542e1e1e1e3e1e444e4e4e541e243444544454445444547c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 007:9464354594b264748494a4b4c4d46494649464940040e4f4b264354594644040404040404040404040404040404040404040404040404040404040405d1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 008:d3c3e395d3e1c30515d325d33545c3d355d3c3d365b17585e1c3e395d3254040404040404098989898404040404040404040404040404040404040407c1e7e8e1e1e1e9eae1ebe1e1e1e1e1e1e1e1e72821ebe1e72821ebe1e7c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 009:9464465694b264e395a5b594e39564c5d5946494e5b1b1f5b264e395a5b54040404040404040404040404040404040404040404040404040404040405d1e05151ebe1ecedeeefe1e1e1e1e1e1e1e1ee395eefe1ee395eefe1e5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 010:d3c3d3c3d3e1c306162636d34656c3e3d5d3c3d376b1b1b1e1c3e3952636404040404040409898989840b9c9c9d9e9f90a0a0a0a4040400a0a0a0a407c1ee395eefe1e0f1f2f3f1e1e1e1e1e1e1e1ee3952f3f1ee3952f3f1e7c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 011:9464946494b264946494649464948696a694649410201020b264e395d3254040404040404040404040401a2a3a4a5a6a7a40404040404040404040405d1ee3952f3f1e4f5f1ebe1e1e1e1e1e1e1e1ee3951ebe1ee3951ebe1e5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 012:d3c37282d3e1c37282d3c3d3c3d3b6c6d6d3c3d300e6f607e1c3e395a5b54040404040404098989898408a9aaabaca9ada40404040404040404040407c1e4f5f1ebe1e0515eefe1e1e1e1e1e1e1e1ee395eefe1ee395eefe1e7c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 013:9464e39594b264e395946494649464946494649465172737b26406162636eaeaeaeaeaeaeafafafafaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaea5d1e0515eefe1e6f7f2f3f1e1e1e1e1e1e1e1e6f7f2f3f1e6f7f2f3f1e5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 014:d3c34454d3e1c34454d3c3d3c3d3c3d3c3d3c3d3e5b14757e1c3d3c3d3c3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f37c1e6f7f2f3f1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e7c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 015:9464946494b2649464946494649464946494649476b1b1b1b26494649464f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f35d1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 016:201020102000102010207667770010201020102010201020001020102010f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f30b1b2b1b2b7667776777761b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 000:111111001020102010201020102010201020102010201020102000111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 001:111111001111111111111111111111111111111111111111111100111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 002:11111100112232115262721192a2b2c21111111111111111111100111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 003:1111110011233311536373119411b3c31111111111111111111100111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 004:1111110011243411546474119411b4c41111111111111111111100111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 005:11111100112535115565758595a5b5c51111111111111111111100111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 006:1111110016261626566676861626b6c61626162616261626162600111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 007:111111001727372b57677787172b172b172b172b172b172b172b00111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 008:1111110016283826582678881626b8261626162616261626162600111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 009:1111110017293949592b798917a9b92b172b172b172b172b172b00111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 010:11111100162a3a4a5a267a8a16aaba261626162616261626162600111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 011:11111100172b172b172b172b9babbb2b172b172b172b172b172b00111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 012:11111100162c3c26162616269cacbc261626162616261626162600111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 013:11111100172d3d2b172b172b172b172b172b172b172b172b172b00111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 014:11111100162e3e2616261626162616261626162616261626162600111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 015:11111100172b172b172b172b172b172b172b172b172b172b172b00111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 016:111111001020102010201020102010201020102010201020102000111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- </MAP>
-- <SFX>
-- 016:00000000000000400040004000700070007000400040004000700070007000c000c000c000c000c000c000c000c000c000c000c000c000c000c000c0470000000000
-- 017:030003000300030003000300036003600360036003600360136023c033c053c063c083c0a3c0b3c0c3c0c3c0d3c0d3c0e3c0f3c0f3c0f3c0f3c0f3c0400000000000
-- 018:03c003c003c003c003c003c0036003600360036003600360230033005300630083009300a300b300c300d300d300e300e300e300f300f300f300f300400000000000
-- 019:0300030003000300030003d013d013d023d033d053d073d093d0b3d0c3d0e3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0400000000000
-- 020:090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900500000000000
-- 021:01000100010001000100f10001100110011001100110f11001200120012001200120f1201130113011302130213021302130313041308130a130d130580000000000
-- 022:03b003100300030003000300130063009300b300c300d300d300e300e300e300f300f300f300f300f300f300f300f300f300f300f300f300f300f300400000000000
-- 032:010001100100011001000110010001100100010001000100010001000100010001000100010001000100010001000100010001000100010001000100400000000800
-- 033:000000010002000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c40000000004
-- 034:02000240020002000200020002000200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f2004700000f0200
-- 044:0600f6000620f6000600f6000610f600f600f6000600f600f600f600f6000600060006000600060006000600060006000600060006000600060006001600000f0f00
-- 045:0000f0000020f0000000f0000010f000f000f0000000f000f000f000f0000000000000000000000000000000000000000000000000000000000000005600000f0f00
-- 048:090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900400000000000
-- 056:4100510061406140717081709100b100c100d100e100e100e100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100480000000600
-- 057:000000010002000300020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000840000000004
-- 058:41004110410041104100411041004110c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100003000080800
-- 059:03000300030003000300030003000300030003000300030003000300030003000300030003000300030003000300030003000300030003000300030000b000000000
-- 060:220022002200820082008200820082008200820082008200820082008200820082008200820082008200820082008200820082008200820082008200500000000000
-- 061:9f009f00bf00df00df00ef00ef00ef00ef00ef00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00100000000000
-- 062:00000100010001000100510081008100910091009100a100a100a100a100a100b100b100b100b100c100c100c100d100d100d100e100e100e100f100580000000000
-- 063:00b000100000000000000000100060009000b000c000d000d000e000e000e000f000f000f000f000f000f000f000f000f000f000f000f000f000f000500000000000
-- </SFX>
-- <WAVES>
-- 000:bcceefceedddddc84333121268abaa99
-- 001:6789bdd96adc83248dd6334adda7578b
-- 002:0123456789abcdef0123456789abcdef
-- 003:224578acdeeeeddcba95434567653100
-- 004:00000000ffffffff00000000ffffffff
-- 005:0123456789abcdeffedcba9876543210
-- 006:0123456789abcdef0123456789abcdef
-- 007:76543210123456789abcdefedcba9878
-- 008:0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f
-- 009:fff000fff000fff000fff000fff000ff
-- </WAVES>
-- <PATTERNS>
-- 000:4008b50000000000000000001008c10000004008b50000001008c1000000000000000000e008b30000004008b50000001008c10000000008c10000000008c10000000000000000000000000000000000000000000000000000000000000000004008b50000000000000000001008c10000004008b50000001008c10000000008c1000000e008b30000004008b50000001008c10000000008c10000000008c10000000008c10000000008c10000000008c1000000000000000000000000000000
-- 001:4008b50000000000000000001008c10000004008b50000001008c1000000000000000000e008b30000004008b50000001008c10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007008b50000007008b50000001008c10000007008b50000001008c10000000008c10000007008b50000009008b50000001008c10000009008b50000001008c10000009008b50000009008b50000001008c10000009008b50000001008c1000000
-- 003:4008d30000000000000000000000000000000000000000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000004008d30000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000000000000000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000004008d30000004008d9000000000000000000000000000000000000000000
-- 004:43398d000000e0088b000000b0088b000881e0088b00000040088d000000e0088b000881b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e00889000000
-- 005:455981000000000881000000000881000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000
-- 008:4aa9b30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005008b30000000000000000000000000000000000000000000008b10000000000000000000008910000000000000000004008b30000000000000000000000000000000000000000000000000000000000000000000008b1000000000000000000f008b1000000000000000000000000000000000000000000000891000000000000000000000000000000000000000000
-- 009:4779d30000000000000000004008d30000000000000000004008db0000000000000000004008d30000000000000000004008d30000000000000000004008d30000000000000000004008db0000000000000000004008d30000000000000000004008d30000000000000000004008d30000000000000000004008db0000000000000000004008d30000000000000000004008d30000000000000000004008d30000000000000000004008db0000000000000000004008d3000000000000000000
-- </PATTERNS>
-- <TRACKS>
-- 000:1000012000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff
-- 001:581000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 002:900082000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- </TRACKS>

View File

@@ -1,8 +1,8 @@
-- title: Definitely not an Impostor
-- name: impostor
-- author: Teletype Games
-- desc: Life of a programmer
-- desc: Life of a programmer in the Vector
-- site: https://git.teletype.hu/games/impostor
-- license: MIT License
-- version: 1.0
-- version: 0.1
-- script: lua

View File

@@ -1,28 +0,0 @@
Screen.register({
id = "home",
name = "Home",
decisions = {
"go_to_toilet",
"go_to_walking_to_office",
"go_to_sleep",
},
init = function()
if CommuteGlitch.is_max() then
Audio.music_play_mystery()
Glitch.show()
else
Audio.music_play_room_work()
end
end,
background = "bedroom",
draw = function()
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
})

View File

@@ -1,60 +0,0 @@
--- @section Screen
local _screens = {}
--- Registers a screen definition.
--- @within Screen
--- @param screen_data table The screen data table.
--- @param screen_data.id string Unique screen identifier.
--- @param screen_data.name string Display name of the screen.
--- @param screen_data.decisions table Array of decision ID strings available on this screen.
--- @param screen_data.background string Map ID used as background.
--- @param[opt] screen_data.init function Called when the screen is entered. Defaults to noop.
--- @param[opt] screen_data.update function Called each frame while screen is active. Defaults to noop.
--- @param[opt] screen_data.draw function Called after the focus overlay to draw screen-specific overlays. Defaults to noop.
function Screen.register(screen_data)
if _screens[screen_data.id] then
trace("Warning: Overwriting screen with id: " .. screen_data.id)
end
if not screen_data.init then
screen_data.init = function() end
end
if not screen_data.exit then
screen_data.exit = function() end
end
if not screen_data.update then
screen_data.update = function() end
end
if not screen_data.draw then
screen_data.draw = function() end
end
_screens[screen_data.id] = screen_data
end
--- Gets a screen by ID.
--- @within Screen
--- @param screen_id string The ID of the screen.
--- @return table|nil screen The screen table or nil. </br>
--- Fields: </br>
--- * id (string) Unique screen identifier.<br/>
--- * name (string) Display name.<br/>
--- * decisions (table) Array of decision ID strings.<br/>
--- * background (string) Map ID used as background.<br/>
--- * init (function) Called when the screen is entered.<br/>
--- * update (function) Called each frame while screen is active.
function Screen.get_by_id(screen_id)
return _screens[screen_id]
end
--- Gets all registered screens.
--- @within Screen
--- @return result table A table containing all registered screen data, indexed by their IDs or nil. </br>
--- Fields: </br>
--- * id (string) Unique screen identifier.<br/>
--- * name (string) Display name of the screen.<br/>
--- * decisions (table) Array of decision ID strings available on this screen.<br/>
--- * background (string) Map ID used as background.<br/>
--- * init (function) Called when the screen is entered.<br/>
--- * update (function) Called each frame while screen is active.<br/>
function Screen.get_all()
return _screens
end

View File

@@ -1,501 +0,0 @@
--- @section MysteriousManScreen
local STATE_TEXT = "text"
local STATE_DAY = "day"
local STATE_CHOICE = "choice"
local ASC_01_TEXT = [[
Normann seems to be in line,
and stays seeking for oxes
within the confines.
Very good.
]]
local ASC_12_TEXT = [[
We have a problem!
Normann formed his first thought.
He saw the tracks.
]]
local ASC_23_TEXT = [[
Not good, not terrible.
Normann caught his glimpse
of another way
- quite literally -
if this continues,
we will lose control.
]]
local ASC_34_TEXT = [[
There is no turning back now for Norman.
He caught on.
I hoped it would never come to this...
]]
--[[ Norman speaks for the first time during MM screen ]]
local ASC_45_TEXT = [[
Wait, who are you?
*silence*
Why am I seeing this?
*silence*
]]
local ASC_56_TEXT = [[
Norman is not as productive as he should be.
Can we distract him?
We need to keep him busy.
We need
More
Time
]]
local ASC_67_TEXT = [[
He knows.
Norman has broken through the first veil.
The simulation is compromised.
This was not supposed to happen.
Not yet.
]]
local ASC_78_TEXT = [[
The situation has reached
critical levels.
Norman is fully aware...
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 = {
[1] = ASC_01_TEXT,
[2] = ASC_12_TEXT,
[3] = ASC_23_TEXT,
[4] = ASC_34_TEXT,
[5] = ASC_45_TEXT,
[6] = ASC_56_TEXT,
[7] = ASC_67_TEXT,
[8] = ASC_78_TEXT,
[9] = ASC_89_TEXT,
}
function MysteriousManScreen.get_text_for_level(level)
return ascension_texts[level] or ASC_01_TEXT
end
local state = STATE_TEXT
local text_y = Config.screen.height
local text_speed = 12 -- pixels per second
local day_timer = 0
local day_display_seconds = 2
local text_done = false
local text_done_timer = 0
local TEXT_DONE_HOLD_SECONDS = 2
local selected_choice = 1
local text = ASC_01_TEXT
local day_text_override = nil
local on_text_complete = nil
local show_mysterious_screen = true
local trigger_flash_on_wake = false
local break_mode = false
MysteriousManScreen.choices = {
{
label = "Wake Up",
},
{
label = "Stay in Bed",
},
}
-- Draws the background image
-- @within MysteriousManScreen
function MysteriousManScreen.draw_background()
local img_values = {0,1,0,1,0,1,0,1,0,1,0,1,2,1,4,1,0,2,4,1,0,2,4,1,0,2,4,1,0,2,4,1,0,2,4,2,1,4,2,1,4,1,0,2,4,1,0,1,4,2,1,2,1,0,1,2,4,1,0,2,4,2,1,0,1,2,1,2,1,0,1,4,1,0,2,4,2,0,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,4,1,0,2,4,2,0,1,2,4,1,0,2,4,1,0,1,0,4,1,0,2,4,0,1,0,1,0,1,0,4,1,0,2,4,0,1,4,1,0,2,4,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,0,4,1,0,2,4,1,0,1,0,4,1,0,2,4,1,0,1,0,1,4,1,0,2,4,1,2,1,0,1,4,1,0,2,4,2,1,0,1,2,4,1,0,2,4,2,1,0,1,2,4,1,0,2,4,1,2,0,1,0,1,0,1,2,1,2,1,0,2,4,1,0,2,4,0,1,0,1,2,1,0,1,0,2,4,1,0,2,4,2,1,0,1,0,1,4,1,0,2,4,2,1,0,1,0,2,4,2,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,2,1,0,1,0,2,0,1,4,1,0,2,4,1,0,1,0,1,2,0,1,4,1,0,2,4,2,1,0,1,0,1,4,1,0,2,4,2,1,0,2,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,0,1,4,1,0,2,4,0,1,0,1,0,1,0,1,4,1,0,2,4,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,2,4,1,0,2,4,2,1,0,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,0,1,4,1,0,2,4,2,4,1,0,1,0,1,0,1,0,1,0,1,2,1,4,2,4,1,0,2,4,2,1,0,1,0,1,0,1,0,1,0,1,2,1,4,2,4,1,0,2,4,2,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,4,1,0,2,4,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,2,1,4,1,0,2,4,2,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,2,1,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,2,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,2,0,1,0,1,0,4,1,0,2,4,0,1,0,1,0,2,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,0,2,0,1,4,1,0,2,4,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,2,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,2,4,1,0,2,4,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,2,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,2,4,1,0,2,4,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,2,4,1,0,2,4,2,0,1,0,1,4,1,0,2,4,2,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,0,1,0,1,4,2,1,0,1,2,1,2,1,0,1,2,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,0,1,2,0,1,0,2,1,0,1,2,0,1,0,2,0,1,0,1,0,1,2,0,1,0,1,0,1,0,2,0,1,0,1,0,1,0,2,0,1,0,1,0,2,1,0,1,0,1,2,0,1,0,2,0,2,1,0,1,2,0,2,1,0,1,0,1,2,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,0,2,1,0,1,0,1,0,1,0,1,0,2,1,0,1,2,0,1,0,1,2,1,0,1,0,1,0,1,0,2,1,0,2,0,1,0,1,0,2,0,1,0,1,0,2,0,1,0,2,0,1,0,1,0,2,1,0,1,2,0,1,0,1,0,1,0,1,0,2,0,1,0,2,0,1,0,2,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0}
local img_runs = {1480,1,151,1,87,1,1,150,1,1,86,1,2,148,1,2,87,2,148,2,88,2,148,2,88,2,148,2,88,2,148,2,88,2,70,1,2,9,1,2,63,2,88,2,69,1,2,3,5,1,1,1,1,1,1,1,61,2,88,2,68,1,1,3,1,2,1,3,2,3,2,61,2,88,2,68,1,6,1,4,1,5,1,1,60,2,88,2,67,1,1,17,2,60,2,88,2,67,1,19,1,1,59,2,88,2,67,1,19,1,1,59,2,88,2,67,4,1,11,1,1,1,3,59,2,88,2,67,21,1,59,2,88,2,67,21,1,59,2,88,2,66,1,21,2,58,2,88,2,66,1,21,1,1,58,2,88,2,66,4,17,2,1,58,2,88,2,65,2,5,1,15,2,58,2,88,2,63,1,1,2,22,4,55,2,88,2,57,2,2,34,2,2,49,2,88,2,55,1,2,39,3,1,47,2,88,2,55,5,1,3,2,2,1,25,1,1,1,1,1,1,1,47,2,88,2,57,1,2,1,29,1,4,1,1,1,1,49,2,88,2,59,1,2,8,15,9,2,52,2,88,2,62,2,1,1,1,24,1,1,1,54,2,88,2,66,1,22,1,58,2,88,2,66,1,21,1,59,2,88,2,66,2,20,1,59,2,88,2,65,3,20,2,58,2,88,2,63,2,2,1,20,1,1,2,56,2,88,2,61,2,4,1,20,1,2,2,55,2,88,2,59,1,1,6,1,19,1,4,2,54,2,88,2,58,2,7,2,17,1,1,6,2,52,3,87,2,56,1,2,9,1,26,2,51,3,87,2,55,1,1,40,1,50,2,88,2,54,2,41,1,50,2,88,2,53,2,42,2,49,2,88,2,52,2,44,2,48,2,88,2,51,2,46,2,47,2,88,2,50,2,16,1,31,1,47,2,88,2,50,3,1,14,1,29,1,1,2,46,2,88,2,50,18,1,29,1,2,1,46,2,88,2,50,4,14,1,33,1,45,2,88,2,53,1,3,11,2,15,1,13,1,2,46,2,88,2,56,1,1,11,1,15,1,10,2,1,49,2,88,2,55,2,3,10,2,12,2,8,8,46,2,88,2,50,1,1,4,4,1,9,2,11,2,8,2,4,2,1,2,1,1,42,2,88,2,48,2,3,8,1,9,2,9,3,7,2,8,1,1,2,1,1,40,2,88,2,45,3,2,1,1,1,1,8,1,7,1,1,3,6,4,6,1,8,1,1,1,1,4,2,38,2,88,2,45,1,2,2,22,4,4,4,6,1,10,1,1,2,1,2,1,2,37,2,88,2,42,1,1,1,4,1,2,19,1,1,4,1,5,21,1,2,6,35,2,88,2,42,2,1,1,1,1,15,2,8,3,4,3,6,1,17,1,1,2,1,1,1,34,2,88,2,41,3,1,1,15,2,9,3,5,2,8,1,21,1,1,34,2,88,2,41,1,1,1,16,2,11,2,5,2,9,2,18,2,1,34,2,88,2,41,1,2,1,14,2,13,1,5,1,12,1,17,1,2,34,2,88,2,41,1,16,1,17,1,2,1,14,2,17,1,1,33,2,88,2,41,1,2,1,12,1,17,1,3,2,15,1,14,1,1,1,1,33,2,88,2,41,15,1,22,1,17,1,1,11,1,3,1,33,2,88,2,41,1,14,2,40,1,15,1,33,2,88,2,41,1,15,2,35,1,3,1,10,1,4,1,33,2,88,2,41,1,3,1,12,2,36,2,11,1,4,1,33,2,88,2,40,1,1,18,1,35,1,12,1,4,1,1,32,2,88,2,40,2,4,1,14,1,33,1,12,1,6,1,32,2,88,2,40,1,6,1,14,1,31,1,13,1,6,1,32,2,88,2,39,2,22,2,28,1,21,1,1,31,2,88,2,39,2,24,1,26,1,22,1,1,31,2,88,2,39,2,25,1,24,1,23,1,1,31,2,88,2,39,1,27,1,22,2,23,1,1,31,2,88,2,39,1,49,2,24,3,30,2,88,2,39,1,48,1,27,1,1,30,2,88,2,39,1,47,2,27,1,1,30,2,88,2,37,1,1,47,2,28,1,1,30,2,88,2,37,1,1,46,1,30,2,1,29,2,88,2,37,1,47,1,30,2,30,2,88,2,37,1,1,2,43,1,33,30,3,87,2,37,1,45,1,31,1,1,2,29,3,87,2,37,45,1,35,1,29,1,1,88,2,1,35,1,1,80,2,1,26,1,3,86,1,1,2,1,21,1,4,4,2,1,3,80,30,1,2,125,2,80,2,156,2,79,2,1,156,2,1,77,4,156,1,3,76,3,1,140,1,15,1,2,2,74,2,1,1,1,5,3,129,1,18,1,82,1,7,1,129,1,18,1,82,1,9,1,123,3,20,1,1,80,2,11,1,1,119,1,22,1,1,1,2,76,4,1,154,2,2,63,1,14,3,1,126,1,27,2,65,1,17,1,17,2,132,4,66,1,17,4,113,39,66,2,16,31,191,1,1,1,15,1,153,2,15,3,48,2,16,2,152,1,16,1,1,1,48,2,16,2,152,1,1,2,2,1,10,1,1,1,48,1,13,6,153,1,3,13,1,1,48,1,15,2,1,1,153,3,64,1,16,2,154,1,1,83,1,154,1,17,1,66,1,154,1,17,1,66,1,155,1,82,1,156,1,82,1,156,1,1,80,1,1,157,2,78,2,158,2,78,2,158,1,80,1,158,1,79,2,158,1,79,1,159,1,79,1,160,1,77,2,160,1,77,1,162,1,74,1,1,1,162,1,75,1,239,1,165,1,72,1,166,1,240,1,69,1,412,1,63,1,175,1,63,2,238,1,175,1,63,1,239,1,807}
RLE.draw(img_values, img_runs)
end
function MysteriousManScreen.draw_day_switch_background()
local img_values = {1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1}
local img_runs = {86,2,238,2,225,1,10,5,224,1,8,8,221,4,5,2,1,8,219,6,2,13,217,1,2,19,1,1,213,2,4,19,2,1,211,2,5,19,3,1,208,1,7,20,4,1,204,2,7,2,1,19,5,1,201,2,7,2,3,19,6,1,198,2,7,2,5,9,2,1,2,5,7,1,195,2,7,2,7,5,7,7,8,1,192,2,7,2,9,5,8,6,9,1,188,3,7,2,11,5,9,5,10,1,186,2,7,2,13,5,9,5,11,1,182,2,7,3,15,5,9,5,12,1,179,2,8,2,17,5,9,5,13,1,176,2,7,3,19,5,9,5,14,1,173,2,7,3,21,5,9,5,15,1,169,3,7,2,24,5,9,5,16,1,166,2,8,2,26,5,9,5,3,1,177,2,8,3,27,5,9,5,4,1,13,1,159,3,7,3,30,5,8,6,5,1,170,3,7,3,32,5,7,7,6,1,13,1,153,2,8,2,35,19,7,1,164,2,7,3,37,19,8,1,160,3,7,3,39,19,9,1,156,3,8,3,41,19,10,1,153,3,8,2,44,19,25,1,136,3,8,2,46,19,160,2,8,3,48,19,157,3,7,4,50,7,6,6,15,1,139,2,9,3,52,6,7,6,16,1,12,1,122,3,8,3,55,6,7,6,17,1,132,4,7,3,57,6,7,6,159,2,60,6,7,6,32,1,123,3,62,6,7,6,33,1,119,4,64,6,7,6,22,1,128,3,67,6,7,6,150,2,1,1,67,6,7,6,153,1,67,6,7,6,25,1,11,1,115,1,67,6,7,6,26,1,126,1,67,6,7,6,28,1,124,1,67,6,7,6,29,1,11,1,111,1,67,6,7,6,30,1,11,1,110,1,66,7,6,8,42,1,109,1,66,21,32,1,10,1,71,4,33,1,66,21,115,1,2,1,33,1,66,21,119,1,32,1,66,21,36,1,115,1,66,21,48,1,66,1,2,1,33,1,66,21,38,1,10,1,65,4,33,1,66,21,152,1,66,21,152,1,66,8,6,7,152,1,66,7,7,7,114,6,32,1,66,7,7,7,114,6,32,1,66,7,7,7,57,1,56,2,2,2,32,1,66,7,7,7,58,1,18,1,35,7,32,1,12,1,49,1,3,7,7,7,19,1,1,1,2,2,24,1,8,2,46,1,4,8,32,1,66,8,6,7,12,1,1,1,4,3,2,2,2,2,2,1,18,2,7,3,44,1,3,9,32,1,12,1,1,1,51,8,6,7,14,1,1,2,1,1,1,1,2,3,1,3,1,1,3,1,2,2,11,4,1,1,2,5,41,1,3,10,32,1,12,3,51,8,5,8,7,3,2,1,1,1,1,2,1,1,1,1,1,4,1,6,1,2,2,2,1,2,1,3,1,2,1,6,1,2,1,4,39,3,2,3,1,6,32,1,8,1,2,6,2,1,4,1,2,1,7,1,30,8,5,8,2,2,3,1,1,1,6,2,1,11,1,6,2,2,1,2,1,6,1,15,24,5,1,3,1,7,2,3,1,10,28,1,14,1,9,2,1,1,38,8,1,1,2,9,1,3,10,1,1,6,10,1,2,2,1,1,3,9,1,4,1,6,1,7,8,47,26,1,27,1,7,1,18,1,4,1,4,13,1,9,1,3,3,1,1,1,1,4,1,1,2,3,2,5,2,7,1,2,1,6,1,7,1,16,1,53,25,1,66,8,1,1,3,8,1,3,3,1,1,2,3,1,1,1,1,4,2,2,2,1,7,1,2,1,1,1,1,4,1,3,1,31,7,2,1,15,6,2,1,7,1,2,25,1,66,7,7,7,29,1,2,1,3,1,6,1,16,1,2,3,6,14,5,16,2,6,10,2,25,1,66,7,7,7,57,1,7,6,1,36,9,8,1,1,25,1,66,7,7,7,68,35,7,11,5,1,25,1,66,7,7,7,69,2,1,3,1,22,7,16,2,1,2,1,25,1,66,8,6,7,75,19,6,21,2,1,2,1,92,21,79,4,1,7,4,26,2,1,2,1,2,1,89,21,91,12,9,1,1,7,2,1,28,1,66,21,87,12,13,9,98,21,84,11,19,6,99,21,80,12,22,2,3,1,32,1,66,21,70,1,5,13,25,6,99,21,73,13,28,6,2,1,29,1,66,21,69,14,31,7,31,1,66,21,58,2,6,14,39,1,99,8,4,9,55,1,6,15,42,1,99,8,5,8,51,2,6,15,145,8,5,8,48,2,5,17,147,8,5,8,43,3,6,17,150,8,5,8,40,2,6,18,153,8,5,8,36,3,5,20,155,8,5,8,32,4,5,19,1,2,156,8,5,8,29,3,6,19,95,1,66,8,5,8,25,4,5,21,97,1,66,8,5,8,22,4,5,21,167,8,5,8,18,5,5,21,170,8,5,8,14,6,4,23,105,1,66,8,5,8,11,4,6,23,108,1,66,8,5,8,8,4,6,23,111,1,66,8,5,8,4,4,6,24,114,1,66,8,5,14,7,23,116,1,66,8,6,8,2,1,4,25,119,1,66,8,5,8,3,1,2,24,122,1,66,8,3,38,124,1,65,24,1,22,127,1,63,1,1,44,130,1,63,7,1,5,1,30,132,1,58,3,1,42,135,1,58,14,2,27,138,1,56,10,1,32,140,1,53,8,4,27,1,3,143,1,49,9,1,1,2,1,1,24,2,2,147,1,47,7,5,32,148,1,30,1,12,8,5,32,151,1,28,1,1,1,3,1,4,4,1,3,6,35,151,1,22,10,1,10,1,1,4,39,151,1,20,25,2,41,151,1,18,47,1,22,151,1,18,70,148,2,1,1,17,71,148,4,17,71,148,4,14,61,2,10,149,6,10,63,2,10,149,8,4,66,4,9,149,80,2,9,149,91,149,92,78,3,67,92,148,80,3,9,148,92,148,92,148,92,148,92,74,1,3,2,68,92,73,3,2,3,67,92,69,1,3,3,2,2,68,94,66,2,1,5,2,3,67,95,58,15,2,1,69,96,55,21,68}
-- pal = {184,167,183,121,70,74}
-- pal = b8a7b779464a
RLE.draw(img_values, img_runs)
end
-- Transitions from the text phase to the day display phase, starting the timer for how long the day label is shown.
-- @within MysteriousManScreen
function MysteriousManScreen.go_to_day_state()
if on_text_complete then
on_text_complete()
on_text_complete = nil
end
if Context.game.current_screen ~= "mysterious_man" then
return
end
state = STATE_DAY
day_timer = day_display_seconds
end
-- Norman chooses to wake up, starting the button mash minigame and flash, then going to the next day.
-- @within MysteriousManScreen
function MysteriousManScreen.wake_up()
Context.home_norman_visible = false
Util.go_to_screen_by_id("home")
MinigameButtonMashWindow.start("game", {
focus_center_x = (Config.screen.width / 2) - 22,
focus_center_y = (Config.screen.height / 2) - 18,
focus_initial_radius = 0,
target_points = 100,
instruction_text = "Wake up Norman!",
show_progress_text = false,
meter_on_complete = Meter.apply_wakeup_reward,
on_win = function()
Audio.music_play_wakingup()
Meter.show()
if trigger_flash_on_wake then
trigger_flash_on_wake = false
Ascension.start_flash()
end
Window.set_current("game")
end,
})
end
-- Norman chooses to stay in bed, skipping the minigame and flash, and going straight to the next day.
-- At ascension level 4, staying in bed triggers 4->5: shows the ascension text then wakes with flash.
-- @within MysteriousManScreen
function MysteriousManScreen.stay_in_bed()
if Ascension.get_level() == 4 then
Context.should_ascend = true
Day.increase()
Ascension.consume_increase()
trigger_flash_on_wake = true
show_mysterious_screen = true
text = MysteriousManScreen.get_text_for_level(Ascension.get_level())
text_y = Config.screen.height
text_done = false
text_done_timer = 0
state = STATE_TEXT
else
Day.increase()
state = STATE_DAY
day_timer = day_display_seconds
end
end
--- Starts the mysterious man screen.
--- @param[opt] options table Optional configuration.</br>
--- Fields: </br>
--- * text (string) Override for the scrolling text.<br/>
--- * day_text (string) Override for the centered day label.<br/>
--- * on_text_complete (function) Callback fired once when the text phase ends.<br/>
--- * skip_text (boolean) If true, skip the text phase and go straight to day display.<br/>
function MysteriousManScreen.start(options)
options = options or {}
day_timer = 0
text_done = false
text_done_timer = 0
selected_choice = 1
text = options.text or ASC_01_TEXT
text_y = Config.screen.height
day_text_override = options.day_text
on_text_complete = options.on_text_complete
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
show_mysterious_screen = false
state = STATE_DAY
day_timer = day_display_seconds
else
show_mysterious_screen = true
state = STATE_TEXT
end
Util.go_to_screen_by_id("mysterious_man")
Window.set_current("game")
end
--- Sets the scrolling text content.
--- @param new_text string The text to display.
function MysteriousManScreen.set_text(new_text)
text = new_text
end
Screen.register({
id = "mysterious_man",
name = "Mysterious Man",
decisions = {},
background_color = Config.colors.black,
init = function()
Audio.music_play_mystery()
end,
exit = function()
Audio.music_stop()
end,
update = function()
if state == STATE_TEXT then
if not text_done then
text_y = text_y - (text_speed * Context.delta_time)
local lines = 1
for _ in string.gmatch(text, "\n") do
lines = lines + 1
end
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 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 (Ascension.get_level() ~= 8 and Input.select()) then
MysteriousManScreen.go_to_day_state()
end
end
elseif state == STATE_DAY then
day_timer = day_timer - Context.delta_time
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
selected_choice = 1
end
end
elseif state == STATE_CHOICE then
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.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 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
if state == STATE_TEXT then
local screen_w = Config.screen.width
local line_y = text_y
for line in (text .. "\n"):gmatch("(.-)\n") do
local line_w = print(line, 0, -6, 0, false, 1)
local line_x = math.floor((screen_w - line_w) / 2)
Print.text_contour(line, (line_x - 8), line_y, Config.colors.black, false, 1)
line_y = line_y + 8
end
elseif state == STATE_DAY then
MysteriousManScreen.draw_day_switch_background()
local day_text = day_text_override or ("day " .. Context.day_count)
Print.text_center_contour(
day_text,
Config.screen.width / 2,
Config.screen.height / 2 - 3,
Config.colors.white
)
elseif state == STATE_CHOICE then
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,
})

View File

@@ -1,75 +0,0 @@
Screen.register({
id = "office",
name = "Office",
decisions = {
"do_work",
"go_to_walking_to_home",
"have_a_coffee",
"talk_to_truth",
},
init = function()
Context.have_been_to_office = true
local possible_sprites = {
"dev_project_manager",
"dev_hr_girl",
"dev_introvert",
"dev_extrovert",
"dev_guru",
"dev_operator",
{id="dev_buddy", y_correct=1 * 8},
{id="dev_boy", y_correct=1 * 8},
{id="dev_girl", y_correct=1 * 8}
}
local possible_positions = {
{x = 6 * 8, y = 4 * 8},
{x = 10 * 8, y = 11 * 8 + 4},
{x = 12 * 8, y = 4 * 8},
{x = 15 * 8, y = 9 * 8},
{x = 16 * 8, y = 4 * 8},
{x = 17 * 8, y = 8 * 8},
{x = 17 * 8, y = 11 * 8},
{x = 20 * 8, y = 4 * 8},
{x = 23 * 8, y = 5 * 8},
{x = 22 * 8, y = 10 * 8 + 4},
{x = 27 * 8, y = 10 * 8 + 4},
{x = -4 + 5 * 8, y = 9 * 8}
}
if CommuteGlitch.is_max() then
Audio.music_play_mystery()
Context.office_sprites = { "norman_echo" }
else
Audio.music_play_room_work(CommuteGlitch.music_speed())
Context.office_sprites = Sprite.list_randomize(possible_sprites, possible_positions)
if CommuteGlitch.is_active() then
Context.office_sprites = CommuteGlitch.corrupt_sprite_list(Context.office_sprites)
end
end
end,
background = function()
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_max() then
Sprite.draw_at("norman_echo", 15 * 8, 9 * 8)
CommuteGlitch.draw_background_flicker()
else
CommuteGlitch.draw_sprite_list(Context.office_sprites)
end
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
})

View File

@@ -1,105 +0,0 @@
Screen.register({
id = "toilet",
name = "Toilet",
decisions = {
"go_to_home",
},
background = "bedroom",
init = function()
Audio.music_play_mystery()
Context.stat_screen_active = true
Meter.hide()
local cx = Config.screen.width * 0.75
local cy = Config.screen.height * 0.75
Focus.start_driven(cx, cy)
Focus.set_percentage(0.15)
end,
update = function()
if not Context.stat_screen_active then return end
if Input.select() or Input.select() then
Focus.stop()
Context.stat_screen_active = false
Meter.show()
Util.go_to_screen_by_id("home")
end
end,
draw = function()
if not Context.stat_screen_active then return end
local sw = Config.screen.width
local cx = sw / 2
local norman_x = math.floor(sw * 0.75)
local norman_y = math.floor(Config.screen.height * 0.75)
local bar_w = math.floor(sw * 0.75)
local bar_x = math.floor((sw - bar_w) / 2)
local bar_h = 4
Sprite.draw_at("norman", norman_x, norman_y)
Print.text_center_contour("day " .. Context.day_count, cx, 10, Config.colors.black)
local narrative = "reflecting on my past and present...\nboth eventually flushed..."
local wrapped = UI.word_wrap(narrative, 38)
local text_y = 24
for _, line in ipairs(wrapped) do
Print.text_center_contour(line, cx, text_y, Config.colors.black)
text_y = text_y + 8
end
local m = Context.meters
local max_val = Meter.get_max()
local decay_pct = Meter.get_decay_percentage()
local decay_text = string.format("-%d%%", decay_pct)
local combo_mult = Meter.get_combo_multiplier()
local ism_bm_combo_pct = math.floor((combo_mult - 1) * 100)
local wpm_combo_pct = math.floor((1 / combo_mult - 1) * 100 + 0.5)
local meter_start_y = text_y + 10
local meter_list = {
{ key = "wpm", label = "Work Productivity Meter", color = Meter.COLOR_WPM },
{ key = "ism", label = "Impostor Syndrome Meter", color = Meter.COLOR_ISM },
{ key = "bm", label = "Burnout Meter", color = Meter.COLOR_BM },
}
for i, meter in ipairs(meter_list) do
local y = meter_start_y + (i - 1) * 20
Print.text_center_contour(meter.label, cx, y, meter.color, false, 1, Config.colors.white)
local bar_y = y + 8
local fill_w = math.max(0, math.floor((m[meter.key] / max_val) * bar_w))
rect(bar_x, bar_y, bar_w, bar_h, Meter.COLOR_BG)
if fill_w > 0 then
rect(bar_x, bar_y, fill_w, bar_h, meter.color)
end
local mult_text
if meter.key == "wpm" then
mult_text = string.format("%+d%%", wpm_combo_pct)
else
mult_text = string.format("+%d%%", ism_bm_combo_pct)
end
local decay_w = print(decay_text, 0, -6, 0, false, 1)
Print.text_contour(decay_text, bar_x - decay_w - 4, bar_y, Config.colors.light_blue, false, 1, Config.colors.white)
Print.text_contour(mult_text, bar_x + bar_w + 4, bar_y, Config.colors.light_blue, false, 1, Config.colors.white)
end
if Ascension.get_level() > 0 then
local asc_y = meter_start_y + #meter_list * 20
local asc_letter_y = asc_y + 10
local asc_spacing = 8
local asc_total_w = Ascension.get_level() * asc_spacing
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,
})

View File

@@ -1,86 +0,0 @@
Screen.register({
id = "walking_to_home",
name = "Walking to home",
decisions = {
"go_to_home",
"go_to_office",
"sumphore_discussion",
"eating_fast_food",
"go_to_truth",
},
init = function()
local possible_sprites = {
"matrix_trinity",
"matrix_neo",
{id="matrix_oraculum", y_correct=1 * 8},
"matrix_architect"
}
local possible_positions = {
{x = 5 * 8, y = 11 * 8},
{x = 7 * 8, y = 11 * 8},
{x = 9 * 8, y = 11 * 8},
{x = 11 * 8, y = 11 * 8},
{x = 13 * 8, y = 11 * 8},
{x = 15 * 8, y = 11 * 8},
{x = 18 * 8, y = 11 * 8},
{x = 21 * 8, y = 11 * 8},
{x = 24 * 8, y = 11 * 8},
{x = 27 * 8, y = 11 * 8},
}
if CommuteGlitch.is_max() then
Audio.music_play_mystery()
Context.walking_to_home_sprites = {}
else
Audio.music_play_room_work(CommuteGlitch.music_speed())
Context.walking_to_home_sprites = Sprite.list_randomize(possible_sprites, possible_positions)
if CommuteGlitch.is_active() then
Context.walking_to_home_sprites = CommuteGlitch.corrupt_sprite_list(Context.walking_to_home_sprites)
end
end
end,
background = function()
return CommuteGlitch.is_max() and "" or "street"
end,
draw = function()
local w = Window.get_current_id()
if w ~= "game" and w ~= "discussion" then
return
end
local show_sumphore = Ascension.get_level() ~= 8
if CommuteGlitch.is_max() then
Sprite.draw_at("norman", 7 * 8, 3 * 8)
if show_sumphore then
Sprite.draw_at("sumphore", 9 * 8, 2 * 8)
end
CommuteGlitch.draw_sprite_list(Context.walking_to_home_sprites)
Glitch.draw()
else
local norman_x = Context.fast_food_approaching and (19 * 8) or (7 * 8)
Sprite.draw_at("norman", norman_x, 3 * 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
Sprite.draw_at("dev_guard", 22 * 8, 2 * 8)
CommuteGlitch.draw_sprite_list(Context.walking_to_home_sprites)
if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 6 then
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
})

View File

@@ -1,63 +0,0 @@
Screen.register({
id = "walking_to_office",
name = "Walking to office",
decisions = {
"go_to_home",
"go_to_office",
"sumphore_discussion",
"eating_fast_food",
},
init = function()
local possible_sprites = {
"matrix_trinity",
"matrix_neo",
{id="matrix_oraculum", y_correct=1 * 8},
"matrix_architect"
}
local possible_positions = {
{x = 5 * 8, y = 11 * 8},
{x = 7 * 8, y = 11 * 8},
{x = 9 * 8, y = 11 * 8},
{x = 11 * 8, y = 11 * 8},
{x = 13 * 8, y = 11 * 8},
{x = 15 * 8, y = 11 * 8},
{x = 18 * 8, y = 11 * 8},
{x = 21 * 8, y = 11 * 8},
{x = 24 * 8, y = 11 * 8},
{x = 27 * 8, y = 11 * 8},
}
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,
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)
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
Sprite.draw_at("dev_guard", 22 * 8, 3 * 8)
Sprite.draw_list(Context.walking_to_office_sprites)
end
end
})

View File

@@ -1,6 +0,0 @@
Screen.register({
id = "work",
name = "Work",
decisions = {},
background_color = Config.colors.blue,
})

View File

@@ -1,4 +0,0 @@
Sprite.register({
id = "dev_boy",
sprites = Sprite.generate_table(2, 3, 278, -4, -4, 8, 8)
})

View File

@@ -1,4 +0,0 @@
Sprite.register({
id = "dev_buddy",
sprites = Sprite.generate_table(2, 3, 286, -4, -4, 8, 8)
})

View File

@@ -1,4 +0,0 @@
Sprite.register({
id = "dev_extrovert",
sprites = Sprite.generate_table(2, 4, 330, -4, -4, 8, 8)
})

View File

@@ -1,4 +0,0 @@
Sprite.register({
id = "dev_girl",
sprites = Sprite.generate_table(2, 3, 284, -4, -4, 8, 8)
})

View File

@@ -1,4 +0,0 @@
Sprite.register({
id = "dev_guard",
sprites = Sprite.generate_table(3, 4, 384, -4, -4, 8, 8)
})

View File

@@ -1,4 +0,0 @@
Sprite.register({
id = "dev_guru",
sprites = Sprite.generate_table(2, 4, 264, -4, -4, 8, 8)
})

View File

@@ -1,4 +0,0 @@
Sprite.register({
id = "dev_hr_girl",
sprites = Sprite.generate_table(2, 4, 260, -4, -4, 8, 8)
})

View File

@@ -1,4 +0,0 @@
Sprite.register({
id = "dev_introvert",
sprites = Sprite.generate_table(2, 4, 332, -4, -4, 8, 8)
})

View File

@@ -1,4 +0,0 @@
Sprite.register({
id = "dev_operator",
sprites = Sprite.generate_table(2, 4, 326, -4, -4, 8, 8)
})

View File

@@ -1,4 +0,0 @@
Sprite.register({
id = "dev_project_manager",
sprites = Sprite.generate_table(2, 4, 328, -4, -4, 8, 8)
})

View File

@@ -1,216 +0,0 @@
--- @section Sprite
local _sprites = {}
local _active_sprites = {}
local function draw_sprite_instance(sprite_data, params)
local colorkey = params.colorkey or sprite_data.colorkey or Config.colors.transparent
local scale = params.scale or sprite_data.scale or 1
local flip_x = params.flip_x or sprite_data.flip_x or 0
local flip_y = params.flip_y or sprite_data.flip_y or 0
local rot = params.rot or sprite_data.rot or 0
if sprite_data.sprites then
for i = 1, #sprite_data.sprites do
local sub_sprite = sprite_data.sprites[i]
spr(
sub_sprite.s,
params.x + (sub_sprite.x_offset or 0),
params.y + (sub_sprite.y_offset or 0),
sub_sprite.colorkey or colorkey,
sub_sprite.scale or scale,
sub_sprite.flip_x or flip_x,
sub_sprite.flip_y or flip_y,
sub_sprite.rot or rot
)
end
else
spr(sprite_data.s, params.x, params.y, colorkey, scale, flip_x, flip_y, rot)
end
end
--- Registers a sprite definition.
--- @within Sprite
--- @param sprite_data table A table containing the sprite definition.
--- @param sprite_data.id string Unique sprite identifier.<br/>
--- @param[opt] sprite_data.s number Sprite index for single-sprite mode.<br/>
--- @param[opt] sprite_data.colorkey number Default color index for transparency.<br/>
--- @param[opt] sprite_data.scale number Default scaling factor.<br/>
--- @param[opt] sprite_data.flip_x number Set to 1 to flip horizontally by default.<br/>
--- @param[opt] sprite_data.flip_y number Set to 1 to flip vertically by default.<br/>
--- @param[opt] sprite_data.rot number Default rotation in degrees.<br/>
--- @param[opt] sprite_data.sprites table Array of sub-sprite tables for composite sprites. Each entry has: `s` (number) sprite index, `x_offset` (number) horizontal offset, `y_offset` (number) vertical offset, and optional `colorkey`, `scale`, `flip_x`, `flip_y`, `rot` overrides.<br/>
function Sprite.register(sprite_data)
if not sprite_data or not sprite_data.id then
trace("Error: Invalid sprite object registered (missing id)!")
return
end
if _sprites[sprite_data.id] then
trace("Warning: Overwriting sprite with id: " .. sprite_data.id)
end
_sprites[sprite_data.id] = sprite_data
end
--- Generates a sprites table for a rectangular composite sprite.
--- @within Sprite
--- @param width number The number of sprites wide.<br/>
--- @param height number The number of sprites tall.<br/>
--- @param starting_s number The sprite index of the top-left tile.<br/>
--- @param x_base number The base x-offset for the leftmost column.<br/>
--- @param y_base number The base y-offset for the topmost row.<br/>
--- @param x_step number The x-offset increment per column.<br/>
--- @param y_step number The y-offset increment per row.<br/>
--- @return table The sprites table array.
function Sprite.generate_table(width, height, starting_s, x_base, y_base, x_step, y_step)
local sprites = {}
for row = 0, height - 1 do
for col = 0, width - 1 do
local s = starting_s + row * 16 + col
local x_offset = x_base + col * x_step
local y_offset = y_base + row * y_step
table.insert(sprites, { s = s, x_offset = x_offset, y_offset = y_offset })
end
end
return sprites
end
--- Immediately draws a list of sprites
--- @within Sprite
--- @param sprite_list table An array of tables, each containing: `id` (string) sprite identifier, `x` (number) x-coordinate, `y` (number) y-coordinate, and optional `colorkey`, `scale`, `flip_x`, `flip_y`, `rot` parameters.
function Sprite.draw_list(sprite_list)
for _, sprite_info in ipairs(sprite_list) do
local sprite_data = _sprites[sprite_info.id]
if not sprite_data then
trace("Error: Attempted to draw non-registered sprite with id: " .. tostring(sprite_info.id))
else
draw_sprite_instance(sprite_data, sprite_info)
end
end
end
--- Given a list of sprite IDs (or sprite entries with correction offsets) and a list of possible positions, randomly assigns each sprite to a unique position and returns a drawable list.
--- @within Sprite
--- @param sprite_ids table An array of sprite identifier values or tables.
--- Each entry may be either:
--- - string: sprite ID to draw.
--- - table: { sprite_id = string, x_correct = number, y_correct = number }.
--- @param positions table An array of tables, each containing `x` and `y` fields for possible sprite positions.
function Sprite.list_randomize(sprite_ids, positions)
if #sprite_ids > #positions then
trace("Error: More sprite IDs than available positions in Sprite.draw_randomized")
return
end
local shuffled_positions = {}
for i, pos in ipairs(positions) do
shuffled_positions[i] = pos
end
for i = #shuffled_positions, 2, -1 do
local j = math.random(i)
shuffled_positions[i], shuffled_positions[j] = shuffled_positions[j], shuffled_positions[i]
end
local drawable_list = {}
for i, sprite_entry in ipairs(sprite_ids) do
local sprite_id = sprite_entry
local x_correct = 0
local y_correct = 0
if type(sprite_entry) == "table" then
sprite_id = sprite_entry.sprite_id or sprite_entry.id
x_correct = sprite_entry.x_correct or 0
y_correct = sprite_entry.y_correct or 0
end
local sprite_data = _sprites[sprite_id]
if not sprite_data then
trace("Error: Attempted to draw non-registered sprite with id: " .. tostring(sprite_id))
else
local pos = shuffled_positions[i]
table.insert(drawable_list, {
id = sprite_id,
x = pos.x + x_correct,
y = pos.y + y_correct
})
end
end
return drawable_list
end
--- Schedules a sprite for drawing.
--- @within Sprite
--- @param id string The unique identifier of the sprite.<br/>
--- @param x number The x-coordinate.<br/>
--- @param y number The y-coordinate.<br/>
--- @param[opt] colorkey number The color index for transparency.<br/>
--- @param[opt] scale number The scaling factor.<br/>
--- @param[opt] flip_x number Set to 1 to flip horizontally.<br/>
--- @param[opt] flip_y number Set to 1 to flip vertically.<br/>
--- @param[opt] rot number The rotation in degrees.<br/>
function Sprite.show(id, x, y, colorkey, scale, flip_x, flip_y, rot)
if not _sprites[id] then
trace("Error: Attempted to show non-registered sprite with id: " .. id)
return
end
_active_sprites[id] = {
id = id,
x = x,
y = y,
colorkey = colorkey,
scale = scale,
flip_x = flip_x,
flip_y = flip_y,
rot = rot,
}
end
--- Hides a displayed sprite.
--- @within Sprite
--- @param id string The unique identifier of the sprite.<br/>
function Sprite.hide(id)
_active_sprites[id] = nil
end
--- Draws a sprite immediately without scheduling it.
--- @within Sprite
--- @param id string The unique identifier of the sprite.<br/>
--- @param x number The x-coordinate.<br/>
--- @param y number The y-coordinate.<br/>
--- @param[opt] colorkey number The color index for transparency.<br/>
--- @param[opt] scale number The scaling factor.<br/>
--- @param[opt] flip_x number Set to 1 to flip horizontally.<br/>
--- @param[opt] flip_y number Set to 1 to flip vertically.<br/>
--- @param[opt] rot number The rotation in degrees.<br/>
function Sprite.draw_at(id, x, y, colorkey, scale, flip_x, flip_y, rot)
local sprite_data = _sprites[id]
if not sprite_data then
trace("Error: Attempted to draw non-registered sprite with id: " .. id)
return
end
draw_sprite_instance(sprite_data, {
x = x,
y = y,
colorkey = colorkey,
scale = scale,
flip_x = flip_x,
flip_y = flip_y,
rot = rot,
})
end
--- Draws all scheduled sprites.
--- @within Sprite
function Sprite.draw()
for id, params in pairs(_active_sprites) do
local sprite_data = _sprites[id]
if not sprite_data then
trace("Error: Sprite id " .. id .. " in _active_sprites is not registered.")
_active_sprites[id] = nil
end
if sprite_data then
draw_sprite_instance(sprite_data, params)
end
end
end

View File

@@ -1,4 +0,0 @@
Sprite.register({
id = "matrix_architect",
sprites = Sprite.generate_table(2, 4, 324, -4, -4, 8, 8)
})

View File

@@ -1,4 +0,0 @@
Sprite.register({
id = "matrix_neo",
sprites = Sprite.generate_table(2, 4, 322, -4, -4, 8, 8)
})

View File

@@ -1,4 +0,0 @@
Sprite.register({
id = "matrix_oraculum",
sprites = Sprite.generate_table(2, 3, 282, -4, -4, 8, 8)
})

View File

@@ -1,4 +0,0 @@
Sprite.register({
id = "matrix_trinity",
sprites = Sprite.generate_table(2, 4, 320, -4, -4, 8, 8)
})

View File

@@ -1,23 +0,0 @@
Sprite.register({
id = "norman",
sprites = {
{ s = 272, x_offset = -4, y_offset = -4 },
{ s = 273, x_offset = 4, y_offset = -4 },
{ s = 288, x_offset = -4, y_offset = 4 },
{ s = 289, x_offset = 4, y_offset = 4 },
{ s = 304, x_offset = -4, y_offset = 12 },
{ s = 305, x_offset = 4, y_offset = 12 }
}
})
Sprite.register({
id = "sleeping_norman",
sprites = {
{ s = 272, x_offset = 12, y_offset = -4, flip_y = 1 },
{ s = 273, x_offset = 12, y_offset = 4, flip_y = 1 },
{ s = 288, x_offset = 4, y_offset = -4, flip_y = 1 },
{ s = 289, x_offset = 4, y_offset = 4, flip_y = 1 },
{ s = 304, x_offset = -4, y_offset = -4, flip_y = 1 },
{ s = 305, x_offset = -4, y_offset = 4, flip_y = 1 }
}
})

View File

@@ -1,14 +0,0 @@
-- Norman echo: same tile indices as norman.
-- Color remap is applied by CommuteGlitch.draw_sprite_list via pal().
-- Implementer: set ECHO_SRC/ECHO_DST in logic.commute_glitch.lua after inspecting the palette.
Sprite.register({
id = "norman_echo",
sprites = {
{ s = 272, x_offset = -4, y_offset = -4 },
{ s = 273, x_offset = 4, y_offset = -4 },
{ s = 288, x_offset = -4, y_offset = 4 },
{ s = 289, x_offset = 4, y_offset = 4 },
{ s = 304, x_offset = -4, y_offset = 12 },
{ s = 305, x_offset = 4, y_offset = 12 },
}
})

View File

@@ -1,4 +0,0 @@
Sprite.register({
id = "pizza_vendor",
sprites = Sprite.generate_table(2, 2, 334, -1, -8, 8, 8)
})

View File

@@ -1,4 +0,0 @@
Sprite.register({
id = "sumphore",
sprites = Sprite.generate_table(2, 4, 258, -4, -4, 8, 8)
})

View File

@@ -1,63 +0,0 @@
--- @section AsciiArt
AsciiArt = {}
--- Draws ASCII art text using rectangles.
--- @param text string The ASCII art text to draw.
--- @param options table Configuration options (char_w, char_h, line_gap, word_gap, color, x, y).
function AsciiArt.draw(text, options)
options = options or {}
local char_w = options.char_w or 4
local char_h = options.char_h or 5
local line_gap = options.line_gap or 0
local word_gap = options.word_gap or 6
local color = options.color or Config.colors.light_blue
local lines = {}
local max_len = 0
-- Get all lines and find max length
for line in (text .. "\n"):gmatch("(.-)\n") do
table.insert(lines, line)
if #line > max_len then max_len = #line end
end
-- Clean up empty lines from the start/end
while #lines > 0 and lines[1]:gsub("%s+", "") == "" do table.remove(lines, 1) end
while #lines > 0 and lines[#lines]:gsub("%s+", "") == "" do table.remove(lines, #lines) end
if #lines == 0 then return end
local total_h = 0
for _, line in ipairs(lines) do
if line:find("#") then
total_h = total_h + char_h + line_gap
else
total_h = total_h + word_gap
end
end
total_h = total_h - line_gap
local current_y = options.y or (Config.screen.height - total_h) / 2
local x_offset = options.x or (Config.screen.width - (max_len * char_w)) / 2
for _, line in ipairs(lines) do
if line:find("#") then
for j = 1, #line do
local char = line:sub(j, j)
if char == "#" then
rect(x_offset + (j - 1) * char_w, current_y, char_w, char_h, color)
end
end
current_y = current_y + char_h + line_gap
else
current_y = current_y + word_gap
end
end
return {
x = x_offset,
y = options.y or (Config.screen.height - total_h) / 2,
width = max_len * char_w,
height = total_h,
bottom = (options.y or (Config.screen.height - total_h) / 2) + total_h
}
end

View File

@@ -1,65 +0,0 @@
-- Debug helper: start the game at a specific ascension level.
-- Set enabled = true and asc_level = 0..Ascension.get_max_level() before launching.
ContextDebug = {
enabled = false,
asc_level = 0,
}
local _level_overrides = {
[0] = {
day_count = 1,
home_norman_visible = true,
have_been_to_office = false,
have_done_work_today = false,
have_met_sumphore = false,
},
}
for i = 1, Ascension.get_max_level() do
_level_overrides[i] = {
day_count = i + 3,
home_norman_visible = true,
have_been_to_office = false,
have_done_work_today = false,
have_met_sumphore = true,
}
end
--- Returns Context.initial_data() overridden for the given ascension level.
--- @within Context
--- @param level number Target ascension level (0..Ascension.get_max_level()).
--- @return table Debug-patched initial context data.
function Context.initial_data_debug_asc(level)
local data = Context.initial_data()
data.test_mode = false
data.game_in_progress = true
data.ascension = { level = level }
local overrides = _level_overrides[level] or _level_overrides[0]
for k, v in pairs(overrides) do
data[k] = v
end
return data
end
for i = 0, Ascension.get_max_level() do
Context["initial_data_debug_asc_" .. i] = function()
return Context.initial_data_debug_asc(i)
end
end
--- Starts the game at the given ascension level (defaults to ContextDebug.asc_level).
--- Wire this to a key or call it directly; do not use Context.new_game() when debugging.
--- @within Context
--- @param level number|nil Target ascension level.
function Context.new_game_debug(level)
ContextDebug.enabled = true
ContextDebug.asc_level = level or ContextDebug.asc_level
local data = Context["initial_data_debug_asc_" .. ContextDebug.asc_level]()
for k in pairs(Context) do
if type(Context[k]) ~= "function" then Context[k] = nil end
end
for k, v in pairs(data) do Context[k] = v end
MenuWindow.refresh_menu_items()
Screen.get_by_id(Context.game.current_screen).init()
end

Some files were not shown because too many files have changed in this diff Show More