Compare commits
75 Commits
1913d7d7d4
...
feature/ot
| Author | SHA1 | Date | |
|---|---|---|---|
| 272a54ea87 | |||
| 14b14ffc0c | |||
| 7e87a78a15 | |||
| 62d4863a1a | |||
| d9febf16e0 | |||
| 6f5b17147c | |||
| 76964f872d | |||
| 3b137fd48e | |||
| b7791fb9ce | |||
| 7e1dd28808 | |||
| 0b25ecc793 | |||
| f08e4ad1d4 | |||
| 9ae6c12582 | |||
| 4e35cd4bd3 | |||
| ed2354b0fa | |||
| 7854dc8a9f | |||
| 3b9b67e2fa | |||
| 1a4565428d | |||
| f02bb75e4f | |||
|
|
e0c3b446af | ||
|
|
8832b6c833 | ||
|
|
9014e36014 | ||
|
|
7b263bb454 | ||
| 0640964ee4 | |||
| 1cf09de1fb | |||
| 6303781534 | |||
| e2bd1711c0 | |||
| 60a6c73a32 | |||
| c88562ae69 | |||
| 1d06376826 | |||
| 7a57c15eeb | |||
| 3922f51c8e | |||
| f589b9bbca | |||
| 3abe426e3a | |||
| 9eef8fd6e0 | |||
| 0357eb415e | |||
| 11b92f64d6 | |||
| d1720a0a50 | |||
| 31b98dcdf6 | |||
| ec84f23ad5 | |||
| 99b178262f | |||
| d20ef85ceb | |||
| ef4876b083 | |||
| 9a65891afa | |||
| 971acb02ca | |||
| 9fff21826b | |||
| 06d257a335 | |||
|
|
f553b09004 | ||
| facf37dc96 | |||
| bbe24cebf2 | |||
| 5872a27535 | |||
| cd279803ac | |||
|
|
c9db82cce7 | ||
| 3dc28849c4 | |||
| 8a6214e893 | |||
| d943b6deaa | |||
| b3b2159d75 | |||
| ae56cf3555 | |||
| 2fc241fee7 | |||
| 4e0145982f | |||
| 1c987fa08b | |||
| b6d0823875 | |||
| ffa82e8f92 | |||
| cfc07afe59 | |||
| 3fbce5aced | |||
| b3158bdc37 | |||
| 4907c9cadf | |||
| 45f2e746d6 | |||
| 8e8d181c34 | |||
| 5379e30cf3 | |||
| d3fb12703c | |||
| f9c539a854 | |||
| a777241d7a | |||
| 34340d9664 | |||
| 6a39128962 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1 +1,4 @@
|
||||
mranderson.lua
|
||||
.local
|
||||
impostor.lua
|
||||
prompts
|
||||
docs
|
||||
|
||||
63
.luacheckrc
Normal file
63
.luacheckrc
Normal file
@@ -0,0 +1,63 @@
|
||||
-- .luacheckrc
|
||||
-- Configuration for luacheck
|
||||
|
||||
globals = {
|
||||
"Util",
|
||||
"Decision",
|
||||
"Situation",
|
||||
"Screen",
|
||||
"Sprite",
|
||||
"UI",
|
||||
"Print",
|
||||
"Input",
|
||||
"Audio",
|
||||
"Config",
|
||||
"Context",
|
||||
"Meter",
|
||||
"Minigame",
|
||||
"Window",
|
||||
"SplashWindow",
|
||||
"IntroWindow",
|
||||
"MenuWindow",
|
||||
"GameWindow",
|
||||
"PopupWindow",
|
||||
"ConfigurationWindow",
|
||||
"AudioTestWindow",
|
||||
"MinigameButtonMashWindow",
|
||||
"MinigameRhythmWindow",
|
||||
"MinigameDDRWindow",
|
||||
"mset",
|
||||
"mget",
|
||||
"btnp",
|
||||
"keyp",
|
||||
"music",
|
||||
"sfx",
|
||||
"spr",
|
||||
"rect",
|
||||
"rectb",
|
||||
"circ",
|
||||
"circb",
|
||||
"cls",
|
||||
"tri",
|
||||
"Songs",
|
||||
"frame_from_beat",
|
||||
"beats_to_pattern",
|
||||
"MapBedroom",
|
||||
"TIC",
|
||||
"exit",
|
||||
"trace",
|
||||
"index_menu",
|
||||
"Map",
|
||||
"map",
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- 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
|
||||
14
.vscode/settings.json
vendored
14
.vscode/settings.json
vendored
@@ -15,5 +15,17 @@
|
||||
],
|
||||
"Lua.diagnostics.disable": [
|
||||
"undefined-global"
|
||||
]
|
||||
],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
27
.vscode/tasks.json
vendored
Normal file
27
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
// 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"
|
||||
},
|
||||
{
|
||||
"label": "Build & Run TIC80",
|
||||
"type": "shell",
|
||||
"command": "make build && tic80 --fs=. impostor.lua"
|
||||
},
|
||||
{
|
||||
"label": "Export assets",
|
||||
"type": "shell",
|
||||
"command": "make export_assets"
|
||||
},
|
||||
{
|
||||
"label": "Make build",
|
||||
"type": "shell",
|
||||
"command": "make build"
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -1,21 +1,20 @@
|
||||
environment: &environment
|
||||
GAME_NAME: mranderson
|
||||
GAME_LANG: lua
|
||||
|
||||
steps:
|
||||
- name: version
|
||||
image: alpine
|
||||
commands:
|
||||
- 'apk add --no-cache make'
|
||||
- 'make ci-version'
|
||||
|
||||
- name: build
|
||||
image: gitea.vps.teletype.hu/games/tic80pro:latest
|
||||
image: git.teletype.hu/internal/tic80pro:latest
|
||||
environment:
|
||||
<<: *environment
|
||||
XDG_RUNTIME_DIR: /tmp
|
||||
commands:
|
||||
- make build PROJECT=$GAME_NAME
|
||||
- make export PROJECT=$GAME_NAME
|
||||
- 'make ci-export'
|
||||
|
||||
- name: artifact
|
||||
image: alpine
|
||||
environment:
|
||||
<<: *environment
|
||||
DROPAREA_HOST: vps.teletype.hu
|
||||
DROPAREA_PORT: 2223
|
||||
DROPAREA_TARGET_PATH: /home/drop
|
||||
@@ -23,17 +22,15 @@ steps:
|
||||
DROPAREA_SSH_PASSWORD:
|
||||
from_secret: droparea_ssh_password
|
||||
commands:
|
||||
- apk add --no-cache openssh-client sshpass
|
||||
- mkdir -p /root/.ssh
|
||||
- sshpass -p $DROPAREA_SSH_PASSWORD scp -o StrictHostKeyChecking=no -P $DROPAREA_PORT $GAME_NAME.$GAME_LANG $GAME_NAME.tic $GAME_NAME.html.zip $DROPAREA_USER@$DROPAREA_HOST:$DROPAREA_TARGET_PATH
|
||||
- 'apk add --no-cache make openssh-client sshpass'
|
||||
- 'make ci-upload'
|
||||
|
||||
- name: update
|
||||
image: alpine
|
||||
environment:
|
||||
<<: *environment
|
||||
UPDATE_SERVER: https://games.vps.teletype.hu
|
||||
UPDATE_SECRET:
|
||||
from_secret: update_secret_key
|
||||
commands:
|
||||
- apk add --no-cache curl
|
||||
- curl "$UPDATE_SERVER/update?secret=$UPDATE_SECRET&name=$GAME_NAME&platform=tic80"
|
||||
- 'apk add --no-cache make curl'
|
||||
- 'make ci-update'
|
||||
107
GEMINI.md
107
GEMINI.md
@@ -1,6 +1,6 @@
|
||||
# TIC-80 Lua Code Regularities
|
||||
|
||||
Based on the analysis of `mranderson.lua`, the following regularities and conventions should be followed for future modifications and development within this project:
|
||||
Based on the analysis of `impostor.lua`, the following regularities and conventions should be followed for future modifications and development within this project:
|
||||
|
||||
## General Structure & Lifecycle
|
||||
|
||||
@@ -39,7 +39,7 @@ Based on the analysis of `mranderson.lua`, the following regularities and conven
|
||||
|
||||
11. **`spr()` for Sprites:** Individual sprites should be rendered using the `spr()` function.
|
||||
12. **`map()` for Maps:** Tilemaps should be drawn using the `map()` function.
|
||||
13. **`print()` for Text:** Text display should utilize the `print()` function.
|
||||
13. **`Print.text()` for Text:** Text display should utilize the `Print.text()` function.
|
||||
|
||||
## Code Style
|
||||
|
||||
@@ -52,3 +52,106 @@ Based on the analysis of `mranderson.lua`, the following regularities and conven
|
||||
## 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
|
||||

|
||||
|
||||
```
|
||||
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
|
||||
|
||||
230
Makefile
230
Makefile
@@ -1,15 +1,8 @@
|
||||
# -----------------------------------------
|
||||
# Makefile – TIC-80 project builder
|
||||
# Usage:
|
||||
# make PROJECT=mranderson
|
||||
# make build PROJECT=mranderson
|
||||
# make watch PROJECT=mranderson
|
||||
# make export PROJECT=mranderson
|
||||
# -----------------------------------------
|
||||
|
||||
ifndef PROJECT
|
||||
$(error Specify the project name: make PROJECT=name)
|
||||
endif
|
||||
PROJECT = impostor
|
||||
|
||||
ORDER = $(PROJECT).inc
|
||||
OUTPUT = $(PROJECT).lua
|
||||
@@ -19,28 +12,219 @@ OUTPUT_TIC = $(PROJECT).tic
|
||||
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
|
||||
|
||||
# CI/CD variables
|
||||
VERSION_FILE = .version
|
||||
GAME_LANG ?= lua
|
||||
DROPAREA_HOST ?= vps.teletype.hu
|
||||
DROPAREA_PORT ?= 2223
|
||||
DROPAREA_TARGET_PATH ?= /home/drop
|
||||
DROPAREA_USER ?= drop
|
||||
UPDATE_SERVER ?= https://games.vps.teletype.hu
|
||||
|
||||
all: build
|
||||
|
||||
build: $(OUTPUT)
|
||||
@echo "==> Build complete: $(OUTPUT)"
|
||||
|
||||
$(OUTPUT): $(SRC) $(ORDER)
|
||||
@echo "==> Building $(OUTPUT)..."
|
||||
@rm -f $(OUTPUT)
|
||||
@while read f; do \
|
||||
@sed 's/\r$$//' $(ORDER) | while read f; do \
|
||||
cat "$(SRC_DIR)/$$f" >> $(OUTPUT); \
|
||||
echo "\n" >> $(OUTPUT); \
|
||||
done < $(ORDER)
|
||||
@echo "==> Done."
|
||||
echo "" >> $(OUTPUT); \
|
||||
done
|
||||
|
||||
export: $(OUTPUT)
|
||||
@echo "==> TIC-80 export..."
|
||||
tic80 --cli --skip --fs=. \
|
||||
--cmd="load $(OUTPUT) & save $(PROJECT) & export html $(PROJECT).html & exit"
|
||||
@echo "==> HTML ZIP: $(OUTPUT_ZIP)"
|
||||
@echo "==> TIC: $(OUTPUT_TIC)"
|
||||
export: build
|
||||
@if [ -z "$(VERSION)" ]; then \
|
||||
echo "ERROR: VERSION not set!"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "==> Exporting HTML for version $(VERSION)"
|
||||
@tic80 --cli --skip --fs=. \
|
||||
--cmd="load $(OUTPUT) & save $(PROJECT)-$(VERSION) & export html $(PROJECT)-$(VERSION).html & exit"
|
||||
@echo "==> Creating versioned files"
|
||||
@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
|
||||
|
||||
watch:
|
||||
@echo "==> Watching project: $(PROJECT)"
|
||||
make build PROJECT=$(PROJECT)
|
||||
fswatch -o $(SRC_DIR) $(ORDER) | while read; do make build PROJECT=$(PROJECT); done
|
||||
make build
|
||||
fswatch -o $(SRC_DIR) $(ORDER) assets | while read; do make build; done
|
||||
|
||||
import_assets: $(OUTPUT)
|
||||
@TIC_CMD="load $(OUTPUT) &"; \
|
||||
for t in $(ASSET_TYPES); do \
|
||||
for f in $(ASSETS_DIR)/$$t/*.png; do \
|
||||
[ -e "$$f" ] || continue; \
|
||||
echo "==> Importing $$f as $$t..."; \
|
||||
TIC_CMD="$${TIC_CMD} & import $$t $$f"; \
|
||||
done; \
|
||||
done; \
|
||||
TIC_CMD="$$TIC_CMD save & exit"; \
|
||||
echo $$TIC_CMD; \
|
||||
tic80 --cli --skip --fs=. --cmd="$$TIC_CMD"
|
||||
|
||||
# export helper function
|
||||
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)
|
||||
@echo "==> Exporting TIC-80 asset sections"
|
||||
@mkdir -p inc/meta
|
||||
@echo -n '' > $(ASSETS_LUA)
|
||||
@$(call f_export_asset_awk,PALETTE,$(OUTPUT),$(ASSETS_LUA))
|
||||
@$(call f_export_asset_awk,TILES,$(OUTPUT),$(ASSETS_LUA))
|
||||
@$(call f_export_asset_awk,SPRITES,$(OUTPUT),$(ASSETS_LUA))
|
||||
@$(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))
|
||||
|
||||
clean:
|
||||
@rm -f $(PROJECT)-*.tic $(PROJECT)-*.html.zip $(OUTPUT)
|
||||
@echo "==> Cleaned build artifacts"
|
||||
|
||||
# CI/CD Targets
|
||||
ci-version:
|
||||
@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 \
|
||||
VERSION=dev-$$VERSION-$$BRANCH; \
|
||||
fi; \
|
||||
echo "VERSION is: $$VERSION"; \
|
||||
echo $$VERSION > $(VERSION_FILE)
|
||||
|
||||
ci-export:
|
||||
@VERSION=$$(cat $(VERSION_FILE)); \
|
||||
echo "==> Building and exporting version $$VERSION"; \
|
||||
$(MAKE) export VERSION=$$VERSION
|
||||
|
||||
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) $$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"
|
||||
|
||||
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."
|
||||
|
||||
.PHONY: all build export watch import_assets export_assets clean lint ci-version ci-export ci-upload ci-update install_precommit_hook docs
|
||||
|
||||
#-- <WAVES>
|
||||
#-- 000:224578acdeeeeddcba95434567653100
|
||||
#-- </WAVES>
|
||||
#
|
||||
#-- <SFX>
|
||||
#-- 000:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000
|
||||
#-- </SFX>
|
||||
|
||||
12
README.md
12
README.md
@@ -1,4 +1,4 @@
|
||||
# Mr. Anderson's Matrix Escape
|
||||
# Definitely not an Impostor
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -8,14 +8,6 @@ This game is designed for the TIC-80 fantasy computer. To play, follow these ste
|
||||
2. **Clone this repository** Clone this repository to tic80's cartridge folder (MacOS: ~Library/Application Support/com.nesbox.tic/TIC-80)
|
||||
2. **Launch TIC-80:** Start the TIC-80 application.
|
||||
3. **Load the Game:**
|
||||
* Navigate to the directory where `game.lua` is located using the TIC-80 command line (`cd mranderson`).
|
||||
* Navigate to the directory where `game.lua` is located using the TIC-80 command line (`cd impostor`).
|
||||
* Type `load game.lua` and press Enter.
|
||||
* Once loaded, type `run` and press Enter to start the game.
|
||||
|
||||
## Story: The Coder's Lament
|
||||
|
||||
Before he was "The One," before he dodged bullets and shattered the illusion, Thomas Anderson was just a software developer named Neo. Trapped in a cubicle farm of endless bugs and looming deadlines, Neo's days were a monotonous cycle of debugging legacy code, attending pointless meetings, and battling unresponsive APIs. Each line of code felt like a chain, each project a heavier burden, pulling him deeper into a digital malaise.
|
||||
|
||||
He yearned for something more, a glitch in the system, a whisper of a different reality. His fingers, calloused from countless hours on the keyboard, danced across cryptic forums late at night, searching for answers, for meaning beyond the mundane syntax of his corporate prison. The coffee flowed freely, the pizza boxes piled high, and the lines of code blurred into an indistinguishable stream of ones and zeros.
|
||||
|
||||
This game chronicles Mr. Anderson's final, desperate struggles within the software development matrix. Navigate the labyrinthine codebase, escape the relentless pursuit of project managers (Agents), and uncover the hidden truths that will lead him to question everything he knows. Will he find the "red pill" in a sea of green code, or will he forever be just another drone in the system?
|
||||
|
||||
0
assets/music/.keep
Normal file
0
assets/music/.keep
Normal file
0
assets/sfx/.keep
Normal file
0
assets/sfx/.keep
Normal file
0
assets/sprites/.keep
Normal file
0
assets/sprites/.keep
Normal file
0
assets/tiles/.keep
Normal file
0
assets/tiles/.keep
Normal file
BIN
assets_src/26.01.14 Norman szobája v1.6.ase
Normal file
BIN
assets_src/26.01.14 Norman szobája v1.6.ase
Normal file
Binary file not shown.
BIN
assets_src/26.01.20 Iroda v1.0.ase
Normal file
BIN
assets_src/26.01.20 Iroda v1.0.ase
Normal file
Binary file not shown.
BIN
assets_src/26.01.20 Iroda v1.1.ase
Normal file
BIN
assets_src/26.01.20 Iroda v1.1.ase
Normal file
Binary file not shown.
BIN
assets_src/26.01.20 Iroda v1.2.ase
Normal file
BIN
assets_src/26.01.20 Iroda v1.2.ase
Normal file
Binary file not shown.
BIN
assets_src/26.01.20 Norman szobája v2.0.ase
Normal file
BIN
assets_src/26.01.20 Norman szobája v2.0.ase
Normal file
Binary file not shown.
49
impostor.inc
Normal file
49
impostor.inc
Normal file
@@ -0,0 +1,49 @@
|
||||
meta/meta.header.lua
|
||||
init/init.module.lua
|
||||
init/init.config.lua
|
||||
init/init.minigame.lua
|
||||
init/init.meter.lua
|
||||
init/init.window.lua
|
||||
init/init.context.lua
|
||||
system/system.util.lua
|
||||
system/system.print.lua
|
||||
system/system.input.lua
|
||||
system/system.ui.lua
|
||||
audio/audio.manager.lua
|
||||
audio/audio.songs.lua
|
||||
sprite/sprite.manager.lua
|
||||
sprite/sprite.norman.lua
|
||||
situation/situation.manager.lua
|
||||
situation/situation.drink_coffee.lua
|
||||
decision/decision.manager.lua
|
||||
decision/decision.have_a_coffee.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_walking_to_home.lua
|
||||
decision/decision.play_button_mash.lua
|
||||
decision/decision.play_rhythm.lua
|
||||
decision/decision.play_ddr.lua
|
||||
map/map.manager.lua
|
||||
map/map.bedroom.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
|
||||
window/window.manager.lua
|
||||
window/window.register.lua
|
||||
window/window.splash.lua
|
||||
window/window.intro.lua
|
||||
window/window.menu.lua
|
||||
window/window.configuration.lua
|
||||
window/window.audiotest.lua
|
||||
window/window.popup.lua
|
||||
window/window.minigame.mash.lua
|
||||
window/window.minigame.rhythm.lua
|
||||
window/window.minigame.ddr.lua
|
||||
window/window.game.lua
|
||||
system/system.main.lua
|
||||
meta/meta.assets.lua
|
||||
27
inc/audio/audio.manager.lua
Normal file
27
inc/audio/audio.manager.lua
Normal file
@@ -0,0 +1,27 @@
|
||||
--- Stops current music.
|
||||
function Audio.music_stop() music() end
|
||||
--- Plays main menu music.
|
||||
function Audio.music_play_mainmenu() end
|
||||
--- Plays waking up music.
|
||||
function Audio.music_play_wakingup() end
|
||||
--- Plays room morning music.
|
||||
function Audio.music_play_room_morning() end
|
||||
--- Plays room street 1 music.
|
||||
function Audio.music_play_room_street_1() end
|
||||
--- Plays room street 2 music.
|
||||
function Audio.music_play_room_street_2() end
|
||||
--- Plays room music.
|
||||
function Audio.music_play_room_() end
|
||||
--- Plays room work music.
|
||||
function Audio.music_play_room_work() end
|
||||
|
||||
--- Plays select sound effect.
|
||||
function Audio.sfx_select() sfx(17, 'C-7', 30) end
|
||||
--- Plays deselect sound effect.
|
||||
function Audio.sfx_deselect() sfx(18, 'C-7', 30) end
|
||||
--- Plays beep sound effect.
|
||||
function Audio.sfx_beep() sfx(19, 'C-6', 30) end
|
||||
--- Plays success sound effect.
|
||||
function Audio.sfx_success() sfx(16, 'C-7', 60) end
|
||||
--- Plays bloop sound effect.
|
||||
function Audio.sfx_bloop() sfx(21, 'C-3', 60) end
|
||||
156
inc/audio/audio.songs.lua
Normal file
156
inc/audio/audio.songs.lua
Normal file
@@ -0,0 +1,156 @@
|
||||
-- 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
|
||||
}
|
||||
}
|
||||
|
||||
--- Converts beats to frames.
|
||||
-- @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.
|
||||
-- @param beats table A table of beat data, e.g., {{1, "left"}, {2, "down"}}.
|
||||
-- @param bpm number Beats per minute.
|
||||
-- @param[opt] fps number Frames per second (default: 60).
|
||||
-- @return table The generated pattern.
|
||||
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)
|
||||
}
|
||||
]]
|
||||
7
inc/decision/decision.go_to_home.lua
Normal file
7
inc/decision/decision.go_to_home.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
Decision.register({
|
||||
id = "go_to_home",
|
||||
label = "Go to Home",
|
||||
handle = function()
|
||||
Util.go_to_screen_by_id("home")
|
||||
end,
|
||||
})
|
||||
7
inc/decision/decision.go_to_office.lua
Normal file
7
inc/decision/decision.go_to_office.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
Decision.register({
|
||||
id = "go_to_office",
|
||||
label = "Go to Office",
|
||||
handle = function()
|
||||
Util.go_to_screen_by_id("office")
|
||||
end,
|
||||
})
|
||||
7
inc/decision/decision.go_to_toilet.lua
Normal file
7
inc/decision/decision.go_to_toilet.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
Decision.register({
|
||||
id = "go_to_toilet",
|
||||
label = "Go to Toilet",
|
||||
handle = function()
|
||||
Util.go_to_screen_by_id("toilet")
|
||||
end,
|
||||
})
|
||||
7
inc/decision/decision.go_to_walking_to_home.lua
Normal file
7
inc/decision/decision.go_to_walking_to_home.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
Decision.register({
|
||||
id = "go_to_walking_to_home",
|
||||
label = "Walking to home",
|
||||
handle = function()
|
||||
Util.go_to_screen_by_id("walking_to_home")
|
||||
end,
|
||||
})
|
||||
7
inc/decision/decision.go_to_walking_to_office.lua
Normal file
7
inc/decision/decision.go_to_walking_to_office.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
Decision.register({
|
||||
id = "go_to_walking_to_office",
|
||||
label = "Walking to office",
|
||||
handle = function()
|
||||
Util.go_to_screen_by_id("walking_to_office")
|
||||
end,
|
||||
})
|
||||
8
inc/decision/decision.have_a_coffee.lua
Normal file
8
inc/decision/decision.have_a_coffee.lua
Normal file
@@ -0,0 +1,8 @@
|
||||
Decision.register({
|
||||
id = "have_a_coffee",
|
||||
label = "Have a Coffee",
|
||||
handle = function()
|
||||
local new_situation_id = Situation.apply("drink_coffee", Context.game.current_screen)
|
||||
Context.game.current_situation = new_situation_id
|
||||
end,
|
||||
})
|
||||
69
inc/decision/decision.manager.lua
Normal file
69
inc/decision/decision.manager.lua
Normal file
@@ -0,0 +1,69 @@
|
||||
local _decisions = {}
|
||||
|
||||
--- Registers a decision definition.
|
||||
-- @param decision table The decision data table.
|
||||
function Decision.register(decision)
|
||||
if not decision or not decision.id then
|
||||
PopupWindow.show({"Error: Invalid decision object registered (missing id)!"})
|
||||
return
|
||||
end
|
||||
if not decision.label then
|
||||
PopupWindow.show({"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 a decision by ID.
|
||||
-- @param id string The ID of the decision.
|
||||
-- @return table The decision table or nil.
|
||||
function Decision.get(id)
|
||||
return _decisions[id]
|
||||
end
|
||||
|
||||
--- Gets all registered decisions.
|
||||
-- @return table A table of all registered decisions.
|
||||
function Decision.get_all_registered()
|
||||
return _decisions
|
||||
end
|
||||
|
||||
--- Gets decision objects based on a screen's data.
|
||||
-- @param screen_data table The data for the screen, containing a 'decisions' field (list of decision IDs).
|
||||
-- @return table A table containing decision objects relevant to the screen.
|
||||
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(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.
|
||||
-- @param decisions_list table A table of decision objects.
|
||||
-- @return table A new table containing only the decisions for which condition() is true.
|
||||
function Decision.filter_available(decisions_list)
|
||||
local available = {}
|
||||
for _, decision in ipairs(decisions_list) do
|
||||
if decision and decision.condition() then
|
||||
table.insert(available, decision)
|
||||
end
|
||||
end
|
||||
return available
|
||||
end
|
||||
5
inc/decision/decision.play_button_mash.lua
Normal file
5
inc/decision/decision.play_button_mash.lua
Normal file
@@ -0,0 +1,5 @@
|
||||
Decision.register({
|
||||
id = "play_button_mash",
|
||||
label = "Play Button Mash",
|
||||
handle = function() Meter.hide() MinigameButtonMashWindow.start("game") end,
|
||||
})
|
||||
5
inc/decision/decision.play_ddr.lua
Normal file
5
inc/decision/decision.play_ddr.lua
Normal file
@@ -0,0 +1,5 @@
|
||||
Decision.register({
|
||||
id = "play_ddr",
|
||||
label = "Play DDR (Random)",
|
||||
handle = function() Meter.hide() MinigameDDRWindow.start("game", nil) end,
|
||||
})
|
||||
5
inc/decision/decision.play_rhythm.lua
Normal file
5
inc/decision/decision.play_rhythm.lua
Normal file
@@ -0,0 +1,5 @@
|
||||
Decision.register({
|
||||
id = "play_rhythm",
|
||||
label = "Play Rhythm Game",
|
||||
handle = function() Meter.hide() MinigameRhythmWindow.start("game") end,
|
||||
})
|
||||
@@ -1,49 +0,0 @@
|
||||
function Item.use()
|
||||
print("Used item: " .. Context.dialog.active_entity.name)
|
||||
GameWindow.set_state(WINDOW_INVENTORY)
|
||||
end
|
||||
function Item.look_at()
|
||||
PopupWindow.show_description_dialog(Context.dialog.active_entity, Context.dialog.active_entity.desc)
|
||||
end
|
||||
function Item.put_away()
|
||||
-- Add item to inventory
|
||||
table.insert(Context.inventory, Context.dialog.active_entity)
|
||||
|
||||
-- Remove item from screen
|
||||
local currentScreenData = Context.screens[Context.current_screen]
|
||||
for i, item in ipairs(currentScreenData.items) do
|
||||
if item == Context.dialog.active_entity then
|
||||
table.remove(currentScreenData.items, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Go back to game
|
||||
GameWindow.set_state(WINDOW_GAME)
|
||||
end
|
||||
function Item.go_back_from_item_dialog()
|
||||
GameWindow.set_state(WINDOW_GAME)
|
||||
end
|
||||
|
||||
function Item.go_back_from_inventory_action()
|
||||
GameWindow.set_state(WINDOW_GAME)
|
||||
end
|
||||
|
||||
function Item.drop()
|
||||
-- Remove item from inventory
|
||||
for i, item in ipairs(Context.inventory) do
|
||||
if item == Context.dialog.active_entity then
|
||||
table.remove(Context.inventory, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Add item to screen
|
||||
local currentScreenData = Context.screens[Context.current_screen]
|
||||
Context.dialog.active_entity.x = Context.player.x
|
||||
Context.dialog.active_entity.y = Context.player.y
|
||||
table.insert(currentScreenData.items, Context.dialog.active_entity)
|
||||
|
||||
-- Go back to inventory
|
||||
GameWindow.set_state(WINDOW_INVENTORY)
|
||||
end
|
||||
@@ -1,13 +0,0 @@
|
||||
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
|
||||
@@ -1,98 +0,0 @@
|
||||
function Player.draw()
|
||||
spr(Context.player.sprite_id, Context.player.x, Context.player.y, 0)
|
||||
end
|
||||
|
||||
function Player.update()
|
||||
-- Handle input
|
||||
if Input.left() then
|
||||
Context.player.vx = -Config.physics.move_speed
|
||||
elseif Input.right() then
|
||||
Context.player.vx = Config.physics.move_speed
|
||||
else
|
||||
Context.player.vx = 0
|
||||
end
|
||||
|
||||
if Input.player_jump() and Context.player.jumps < Config.physics.max_jumps then
|
||||
Context.player.vy = Config.physics.jump_power
|
||||
Context.player.jumps = Context.player.jumps + 1
|
||||
end
|
||||
|
||||
-- Update player position
|
||||
Context.player.x = Context.player.x + Context.player.vx
|
||||
Context.player.y = Context.player.y + Context.player.vy
|
||||
|
||||
-- Screen transition
|
||||
if Context.player.x > Config.screen.width - Context.player.w then
|
||||
if Context.current_screen < #Context.screens then
|
||||
Context.current_screen = Context.current_screen + 1
|
||||
Context.player.x = 0
|
||||
else
|
||||
Context.player.x = Config.screen.width - Context.player.w
|
||||
end
|
||||
elseif Context.player.x < 0 then
|
||||
if Context.current_screen > 1 then
|
||||
Context.current_screen = Context.current_screen - 1
|
||||
Context.player.x = Config.screen.width - Context.player.w
|
||||
else
|
||||
Context.player.x = 0
|
||||
end
|
||||
end
|
||||
|
||||
-- Apply gravity
|
||||
Context.player.vy = Context.player.vy + Config.physics.gravity
|
||||
|
||||
local currentScreenData = Context.screens[Context.current_screen]
|
||||
-- Collision detection with platforms
|
||||
for _, p in ipairs(currentScreenData.platforms) do
|
||||
if Context.player.vy > 0 and Context.player.y + Context.player.h >= p.y and Context.player.y + Context.player.h <= p.y + p.h and Context.player.x + Context.player.w > p.x and Context.player.x < p.x + p.w then
|
||||
Context.player.y = p.y - Context.player.h
|
||||
Context.player.vy = 0
|
||||
Context.player.jumps = 0
|
||||
end
|
||||
end
|
||||
|
||||
-- Collision detection with ground
|
||||
if Context.player.y + Context.player.h > Context.ground.y then
|
||||
Context.player.y = Context.ground.y - Context.player.h
|
||||
Context.player.vy = 0
|
||||
Context.player.jumps = 0
|
||||
end
|
||||
|
||||
-- Entity interaction
|
||||
if Input.player_interact() then
|
||||
local interaction_found = false
|
||||
-- NPC interaction
|
||||
for _, npc in ipairs(currentScreenData.npcs) do
|
||||
if math.abs(Context.player.x - npc.x) < Config.physics.interaction_radius_npc and math.abs(Context.player.y - npc.y) < Config.physics.interaction_radius_npc then
|
||||
PopupWindow.show_menu_dialog(npc, {
|
||||
{label = "Talk to", action = NPC.talk_to},
|
||||
{label = "Fight", action = NPC.fight},
|
||||
{label = "Go back", action = NPC.go_back}
|
||||
}, WINDOW_POPUP)
|
||||
interaction_found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not interaction_found then
|
||||
-- Item interaction
|
||||
for _, item in ipairs(currentScreenData.items) do
|
||||
if math.abs(Context.player.x - item.x) < Config.physics.interaction_radius_item and math.abs(Context.player.y - item.y) < Config.physics.interaction_radius_item then
|
||||
PopupWindow.show_menu_dialog(item, {
|
||||
{label = "Use", action = Item.use},
|
||||
{label = "Look at", action = Item.look_at},
|
||||
{label = "Put away", action = Item.put_away},
|
||||
{label = "Go back", action = Item.go_back_from_item_dialog}
|
||||
}, WINDOW_POPUP)
|
||||
interaction_found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- If no interaction happened, open inventory
|
||||
if not interaction_found then
|
||||
GameWindow.set_state(WINDOW_INVENTORY)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,32 +1,54 @@
|
||||
local Config = {
|
||||
Config = {}
|
||||
|
||||
function Config.initial_data()
|
||||
return {
|
||||
screen = {
|
||||
width = 240,
|
||||
height = 136
|
||||
},
|
||||
colors = {
|
||||
black = 0,
|
||||
black = 2,
|
||||
light_grey = 13,
|
||||
dark_grey = 14,
|
||||
green = 6,
|
||||
npc = 8,
|
||||
item = 12 -- yellow
|
||||
},
|
||||
player = {
|
||||
w = 8,
|
||||
h = 8,
|
||||
start_x = 120,
|
||||
start_y = 128,
|
||||
sprite_id = 1
|
||||
},
|
||||
physics = {
|
||||
gravity = 0.5,
|
||||
jump_power = -5,
|
||||
move_speed = 1.5,
|
||||
max_jumps = 2,
|
||||
interaction_radius_npc = 12,
|
||||
interaction_radius_item = 8
|
||||
red = 0,
|
||||
green = 7,
|
||||
blue = 9,
|
||||
white = 12,
|
||||
item = 12,
|
||||
meter_bg = 12
|
||||
},
|
||||
timing = {
|
||||
splash_duration = 120
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
--- Restores default configuration settings.
|
||||
function Config.reset()
|
||||
local initial = Config.initial_data()
|
||||
Config.screen = initial.screen
|
||||
Config.colors = initial.colors
|
||||
Config.timing = initial.timing
|
||||
end
|
||||
|
||||
local CONFIG_SAVE_BANK = 7
|
||||
local CONFIG_MAGIC_VALUE_ADDRESS = 2
|
||||
local CONFIG_SPLASH_DURATION_ADDRESS = 3
|
||||
local CONFIG_MAGIC_VALUE = 0xDE
|
||||
|
||||
--- Saves the current configuration.
|
||||
function Config.save()
|
||||
mset(CONFIG_MAGIC_VALUE, CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK)
|
||||
mset(Config.timing.splash_duration, CONFIG_SPLASH_DURATION_ADDRESS, CONFIG_SAVE_BANK)
|
||||
end
|
||||
|
||||
--- Loads saved configuration.
|
||||
function Config.load()
|
||||
if mget(CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK) == CONFIG_MAGIC_VALUE then
|
||||
Config.timing.splash_duration = mget(CONFIG_SPLASH_DURATION_ADDRESS, CONFIG_SAVE_BANK)
|
||||
else
|
||||
Config.reset()
|
||||
end
|
||||
end
|
||||
|
||||
Config.load()
|
||||
|
||||
@@ -1,432 +1,67 @@
|
||||
local Context = {
|
||||
active_window = WINDOW_SPLASH,
|
||||
inventory = {},
|
||||
intro = {
|
||||
y = Config.screen.height,
|
||||
speed = 0.5,
|
||||
text = "Mr. Anderson is an average\nprogrammer. His daily life\nrevolves around debugging,\npull requests, and end-of-sprint\nmeetings, all while secretly\ndreaming of being destined\nfor something more."
|
||||
},
|
||||
current_screen = 1,
|
||||
local SAVE_GAME_BANK = 6
|
||||
local SAVE_GAME_MAGIC_VALUE_ADDRESS = 0
|
||||
local SAVE_GAME_MAGIC_VALUE = 0xCA
|
||||
|
||||
--- Global game context.
|
||||
Context = {}
|
||||
|
||||
--- Gets initial data for Context.
|
||||
-- @return table Initial context data.
|
||||
function Context.initial_data()
|
||||
return {
|
||||
current_menu_item = 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
|
||||
popup = {
|
||||
show = false,
|
||||
content = {}
|
||||
},
|
||||
player = {
|
||||
x = Config.player.start_x,
|
||||
y = Config.player.start_y,
|
||||
w = Config.player.w,
|
||||
h = Config.player.h,
|
||||
vx = 0,
|
||||
vy = 0,
|
||||
jumps = 0,
|
||||
sprite_id = Config.player.sprite_id
|
||||
},
|
||||
ground = {
|
||||
x = 0,
|
||||
y = Config.screen.height,
|
||||
w = Config.screen.width,
|
||||
h = 8
|
||||
},
|
||||
menu_items = {},
|
||||
selected_menu_item = 1,
|
||||
selected_inventory_item = 1,
|
||||
-- Screen data
|
||||
screens = {
|
||||
{
|
||||
-- Screen 1
|
||||
name = "Screen 1",
|
||||
platforms = {
|
||||
{
|
||||
x = 80,
|
||||
y = 110,
|
||||
w = 40,
|
||||
h = 8
|
||||
},
|
||||
{
|
||||
x = 160,
|
||||
y = 90,
|
||||
w = 40,
|
||||
h = 8
|
||||
}
|
||||
},
|
||||
npcs = {
|
||||
{
|
||||
x = 180,
|
||||
y = 82,
|
||||
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
|
||||
game_in_progress = false,
|
||||
minigame_ddr = Minigame.get_default_ddr(),
|
||||
minigame_button_mash = Minigame.get_default_button_mash(),
|
||||
minigame_rhythm = Minigame.get_default_rhythm(),
|
||||
meters = Meter.get_initial(),
|
||||
game = {
|
||||
current_screen = "home",
|
||||
current_situation = nil,
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
x = 90,
|
||||
y = 102,
|
||||
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_you = {
|
||||
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 = {
|
||||
{
|
||||
x = 100,
|
||||
y = 128,
|
||||
w = 8,
|
||||
h = 8,
|
||||
name = "Key",
|
||||
sprite_id = 4,
|
||||
desc = "A rusty old key. It might open something."
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
-- Screen 2
|
||||
name = "Screen 2",
|
||||
platforms = {
|
||||
{
|
||||
x = 30,
|
||||
y = 100,
|
||||
w = 50,
|
||||
h = 8
|
||||
},
|
||||
{
|
||||
x = 100,
|
||||
y = 80,
|
||||
w = 50,
|
||||
h = 8
|
||||
},
|
||||
{
|
||||
x = 170,
|
||||
y = 60,
|
||||
w = 50,
|
||||
h = 8
|
||||
}
|
||||
},
|
||||
npcs = {
|
||||
{
|
||||
x = 120,
|
||||
y = 72,
|
||||
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
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
x = 40,
|
||||
y = 92,
|
||||
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 = {
|
||||
{
|
||||
x = 180,
|
||||
y = 52,
|
||||
w = 8,
|
||||
h = 8,
|
||||
name = "Potion",
|
||||
sprite_id = 7,
|
||||
desc = "A glowing red potion. It looks potent."
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
-- Screen 3
|
||||
name = "Screen 3",
|
||||
platforms = {
|
||||
{
|
||||
x = 50,
|
||||
y = 110,
|
||||
w = 30,
|
||||
h = 8
|
||||
},
|
||||
{
|
||||
x = 100,
|
||||
y = 90,
|
||||
w = 30,
|
||||
h = 8
|
||||
},
|
||||
{
|
||||
x = 150,
|
||||
y = 70,
|
||||
w = 30,
|
||||
h = 8
|
||||
},
|
||||
{
|
||||
x = 200,
|
||||
y = 50,
|
||||
w = 30,
|
||||
h = 8
|
||||
}
|
||||
},
|
||||
npcs = {
|
||||
{
|
||||
x = 210,
|
||||
y = 42,
|
||||
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 = {}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
x = 160,
|
||||
y = 62,
|
||||
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.
|
||||
function Context.reset()
|
||||
local initial_data = Context.initial_data()
|
||||
for k in pairs(Context) do
|
||||
if type(Context[k]) ~= "function" then Context[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
for k, v in pairs(initial_data) do
|
||||
Context[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
--- Starts a new game.
|
||||
function Context.new_game()
|
||||
Context.reset()
|
||||
Context.game_in_progress = true
|
||||
MenuWindow.refresh_menu_items()
|
||||
Screen.get_by_id(Context.game.current_screen).init()
|
||||
end
|
||||
|
||||
--- Saves the current game state.
|
||||
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)
|
||||
end
|
||||
|
||||
--- Loads a saved game state.
|
||||
function Context.load_game()
|
||||
if mget(SAVE_GAME_MAGIC_VALUE_ADDRESS, SAVE_GAME_BANK) ~= SAVE_GAME_MAGIC_VALUE then
|
||||
Context.new_game()
|
||||
return
|
||||
end
|
||||
Context.reset()
|
||||
Context.game_in_progress = true
|
||||
MenuWindow.refresh_menu_items()
|
||||
Screen.get_by_id(Context.game.current_screen).init()
|
||||
end
|
||||
|
||||
89
inc/init/init.meter.lua
Normal file
89
inc/init/init.meter.lua
Normal file
@@ -0,0 +1,89 @@
|
||||
local METER_MAX = 1000
|
||||
local METER_DEFAULT = 500
|
||||
local METER_DECAY_PER_FRAME = 0.02
|
||||
local METER_GAIN_PER_CHORE = 100
|
||||
local COMBO_BASE_BONUS = 0.02
|
||||
local COMBO_MAX_BONUS = 0.16
|
||||
local COMBO_TIMEOUT_FRAMES = 600
|
||||
|
||||
-- Internal meters for tracking game progress and player stats.
|
||||
Meter.COLOR_ISM = Config.colors.red
|
||||
Meter.COLOR_WPM = Config.colors.blue
|
||||
Meter.COLOR_BM = Config.colors.black
|
||||
Meter.COLOR_BG = Config.colors.meter_bg
|
||||
|
||||
--- Gets initial meter values.
|
||||
-- @return table A table of initial meter values.
|
||||
function Meter.get_initial()
|
||||
return {
|
||||
ism = METER_DEFAULT,
|
||||
wpm = METER_DEFAULT,
|
||||
bm = METER_DEFAULT,
|
||||
combo = 0,
|
||||
combo_timer = 0,
|
||||
hidden = false,
|
||||
}
|
||||
end
|
||||
|
||||
--- Hides meters.
|
||||
function Meter.hide()
|
||||
if Context and Context.meters then Context.meters.hidden = true end
|
||||
end
|
||||
|
||||
--- Shows meters.
|
||||
function Meter.show()
|
||||
if Context and Context.meters then Context.meters.hidden = false end
|
||||
end
|
||||
|
||||
--- Gets max meter value.
|
||||
-- @return number The maximum meter value.
|
||||
function Meter.get_max()
|
||||
return METER_MAX
|
||||
end
|
||||
|
||||
--- Gets combo multiplier.
|
||||
-- @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.
|
||||
function Meter.update()
|
||||
if not Context or not Context.game_in_progress or not Context.meters then return end
|
||||
local m = Context.meters
|
||||
m.ism = math.max(0, m.ism - METER_DECAY_PER_FRAME)
|
||||
m.wpm = math.max(0, m.wpm - METER_DECAY_PER_FRAME)
|
||||
m.bm = math.max(0, m.bm - METER_DECAY_PER_FRAME)
|
||||
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
|
||||
|
||||
--- Adds amount to a 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
|
||||
m[key] = math.min(METER_MAX, m[key] + amount)
|
||||
end
|
||||
end
|
||||
|
||||
--- Called on minigame completion.
|
||||
function Meter.on_minigame_complete()
|
||||
local m = Context.meters
|
||||
local gain = math.floor(METER_GAIN_PER_CHORE * Meter.get_combo_multiplier())
|
||||
Meter.add("wpm", gain)
|
||||
Meter.add("ism", gain)
|
||||
Meter.add("bm", gain)
|
||||
m.combo = m.combo + 1
|
||||
m.combo_timer = 0
|
||||
end
|
||||
127
inc/init/init.minigame.lua
Normal file
127
inc/init/init.minigame.lua
Normal file
@@ -0,0 +1,127 @@
|
||||
-- Manages minigame configurations and initial states.
|
||||
Minigame = {}
|
||||
|
||||
--- Applies parameters to defaults.
|
||||
-- @param defaults table The default configuration table.
|
||||
-- @param params table The parameters to apply.
|
||||
-- @return table The updated configuration table.
|
||||
local function apply_params(defaults, params)
|
||||
if not params then return defaults end
|
||||
for k, v in pairs(params) do
|
||||
defaults[k] = v
|
||||
end
|
||||
return defaults
|
||||
end
|
||||
|
||||
--- Gets default DDR minigame configuration.
|
||||
-- @return table The default DDR minigame configuration.
|
||||
function Minigame.get_default_ddr()
|
||||
local arrow_size = 12
|
||||
local arrow_spacing = 30
|
||||
local total_width = (4 * arrow_size) + (3 * arrow_spacing)
|
||||
local start_x = (Config.screen.width - total_width) / 2
|
||||
return {
|
||||
bar_fill = 0,
|
||||
max_fill = 100,
|
||||
fill_per_hit = 10,
|
||||
miss_penalty = 5,
|
||||
bar_x = 20,
|
||||
bar_y = 10,
|
||||
bar_width = 200,
|
||||
bar_height = 12,
|
||||
arrow_size = arrow_size,
|
||||
arrow_spawn_timer = 0,
|
||||
arrow_spawn_interval = 45,
|
||||
arrow_fall_speed = 1.5,
|
||||
arrows = {},
|
||||
target_y = 115,
|
||||
target_arrows = {
|
||||
{ dir = "left", x = start_x },
|
||||
{ dir = "down", x = start_x + arrow_size + arrow_spacing },
|
||||
{ dir = "up", x = start_x + (arrow_size + arrow_spacing) * 2 },
|
||||
{ dir = "right", x = start_x + (arrow_size + arrow_spacing) * 3 }
|
||||
},
|
||||
hit_threshold = 8,
|
||||
button_pressed_timers = {},
|
||||
button_press_duration = 8,
|
||||
input_cooldowns = { left = 0, down = 0, up = 0, right = 0 },
|
||||
input_cooldown_duration = 10,
|
||||
frame_counter = 0,
|
||||
current_song = nil,
|
||||
pattern_index = 1,
|
||||
use_pattern = false,
|
||||
return_window = nil
|
||||
}
|
||||
end
|
||||
|
||||
--- Gets default button mash minigame configuration.
|
||||
-- @return table The default button mash minigame configuration.
|
||||
function Minigame.get_default_button_mash()
|
||||
return {
|
||||
bar_fill = 0,
|
||||
max_fill = 100,
|
||||
fill_per_press = 8,
|
||||
base_degradation = 0.15,
|
||||
degradation_multiplier = 0.006,
|
||||
button_pressed_timer = 0,
|
||||
button_press_duration = 8,
|
||||
return_window = nil,
|
||||
bar_x = 20,
|
||||
bar_y = 10,
|
||||
bar_width = 200,
|
||||
bar_height = 12,
|
||||
button_x = 20,
|
||||
button_y = 110,
|
||||
button_size = 12
|
||||
}
|
||||
end
|
||||
|
||||
--- Gets default rhythm minigame configuration.
|
||||
-- @return table The default rhythm minigame configuration.
|
||||
function Minigame.get_default_rhythm()
|
||||
return {
|
||||
line_position = 0,
|
||||
line_speed = 0.015,
|
||||
line_direction = 1,
|
||||
target_center = 0.5,
|
||||
target_width = 0.3,
|
||||
initial_target_width = 0.3,
|
||||
min_target_width = 0.08,
|
||||
target_shrink_rate = 0.9,
|
||||
score = 0,
|
||||
max_score = 10,
|
||||
button_pressed_timer = 0,
|
||||
button_press_duration = 10,
|
||||
return_window = nil,
|
||||
bar_x = 20,
|
||||
bar_y = 10,
|
||||
bar_width = 200,
|
||||
bar_height = 12,
|
||||
button_x = 210,
|
||||
button_y = 110,
|
||||
button_size = 10,
|
||||
press_cooldown = 0,
|
||||
press_cooldown_duration = 15
|
||||
}
|
||||
end
|
||||
|
||||
--- Configures DDR minigame.
|
||||
-- @param params table Optional parameters to override defaults.
|
||||
-- @return table The configured DDR minigame state.
|
||||
function Minigame.configure_ddr(params)
|
||||
return apply_params(Minigame.get_default_ddr(), params)
|
||||
end
|
||||
|
||||
--- Configures button mash minigame.
|
||||
-- @param params table Optional parameters to override defaults.
|
||||
-- @return table The configured button mash minigame state.
|
||||
function Minigame.configure_button_mash(params)
|
||||
return apply_params(Minigame.get_default_button_mash(), params)
|
||||
end
|
||||
|
||||
--- Configures rhythm minigame.
|
||||
-- @param params table Optional parameters to override defaults.
|
||||
-- @return table The configured rhythm minigame state.
|
||||
function Minigame.configure_rhythm(params)
|
||||
return apply_params(Minigame.get_default_rhythm(), params)
|
||||
end
|
||||
13
inc/init/init.module.lua
Normal file
13
inc/init/init.module.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
Window = {}
|
||||
Util = {}
|
||||
Meter = {}
|
||||
Minigame = {}
|
||||
Decision = {}
|
||||
Situation = {}
|
||||
Screen = {}
|
||||
Map = {}
|
||||
UI = {}
|
||||
Print = {}
|
||||
Input = {}
|
||||
Sprite = {}
|
||||
Audio = {}
|
||||
@@ -1,13 +0,0 @@
|
||||
local SplashWindow = {}
|
||||
local IntroWindow = {}
|
||||
local MenuWindow = {}
|
||||
local GameWindow = {}
|
||||
local PopupWindow = {}
|
||||
local InventoryWindow = {}
|
||||
local ConfigurationWindow = {}
|
||||
|
||||
local UI = {}
|
||||
local Input = {}
|
||||
local NPC = {}
|
||||
local Item = {}
|
||||
local Player = {}
|
||||
0
inc/init/init.window.lua
Normal file
0
inc/init/init.window.lua
Normal file
@@ -1,8 +0,0 @@
|
||||
local WINDOW_SPLASH = 0
|
||||
local WINDOW_INTRO = 1
|
||||
local WINDOW_MENU = 2
|
||||
local WINDOW_GAME = 3
|
||||
local WINDOW_POPUP = 4
|
||||
local WINDOW_INVENTORY = 5
|
||||
local WINDOW_INVENTORY_ACTION = 6
|
||||
local WINDOW_CONFIGURATION = 7
|
||||
9
inc/map/map.bedroom.lua
Normal file
9
inc/map/map.bedroom.lua
Normal file
@@ -0,0 +1,9 @@
|
||||
Map.register({
|
||||
id = "bedroom",
|
||||
from_x = 0,
|
||||
from_y = 0,
|
||||
width = 30,
|
||||
height = 17,
|
||||
to_x = 0,
|
||||
to_y = 0,
|
||||
})
|
||||
46
inc/map/map.manager.lua
Normal file
46
inc/map/map.manager.lua
Normal file
@@ -0,0 +1,46 @@
|
||||
-- Manages game maps.
|
||||
|
||||
local _maps = {}
|
||||
|
||||
--- Gets all registered maps as an array.
|
||||
-- @return table An array of registered map data.
|
||||
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.
|
||||
-- @param map_data table The map data table.
|
||||
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.
|
||||
-- @param map_id string The ID of the map.
|
||||
-- @return table The map data table or nil.
|
||||
function Map.get_by_id(map_id)
|
||||
return _maps[map_id]
|
||||
end
|
||||
|
||||
--- Draws a 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
|
||||
@@ -1,29 +1,313 @@
|
||||
-- <TILES>
|
||||
-- 000:4444444444444444444444444444444444444444444444444444444444444444
|
||||
-- 001:1111111111111111111111111111111111111111111111111111111111111111
|
||||
-- 002:5555555555555555555555555555555555555555555555555555555555555555
|
||||
-- 003:6666666666666666666666666666666666666666666666666666666666666666
|
||||
-- 004:7777777777777777777777777777777777777777777777777777777777777777
|
||||
-- 005:8888888888888888888888888888888888888888888888888888888888888888
|
||||
-- 006:9999999999999999999999999999999999999999999999999999999999999999
|
||||
-- 007:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
-- 008:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
-- </TILES>
|
||||
|
||||
-- <WAVES>
|
||||
-- 000:00000000ffffffff00000000ffffffff
|
||||
-- 001:0123456789abcdeffedcba9876543210
|
||||
-- 02:0123456789abcdef0123456789abcdef
|
||||
-- </WAVES>
|
||||
|
||||
-- <SFX>
|
||||
-- 000:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000304000000000
|
||||
-- </SFX>
|
||||
|
||||
-- <TRACKS>
|
||||
-- 000:100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||
-- </TRACKS>
|
||||
|
||||
--luacheck: ignore max_line_length
|
||||
-- <PALETTE>
|
||||
-- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
|
||||
-- 000:ab53375f574f0101017f2553c3c3c71d2b53fff1e929adff83779d3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
|
||||
-- </PALETTE>
|
||||
-- <SFX>
|
||||
-- 000:060006400600064006000640060006400600060006000600060006000600060006000600060006000600060006000600060006000600060006000600300000000900
|
||||
-- 016:05000500050005400540054005700570057005400540054005700570057005c005c005c005c005c005c005c005c005c005c005c005c005c005c005c0470000000000
|
||||
-- 017:040004000400040004000400046004600460046004600460146024c034c054c064c084c0a4c0b4c0c4c0c4c0d4c0d4c0e4c0f4c0f4c0f4c0f4c0f4c0400000000000
|
||||
-- 018:04c004c004c004c004c004c0046004600460046004600460240034005400640084009400a400b400c400d400d400e400e400e400f400f400f400f400300000000000
|
||||
-- 019:0400040004000400040004d014d014d024d034d054d074d094d0b4d0c4d0e4d0f4d0f4d0f4d0f4d0f4d0f4d0f4d0f4d0f4d0f4d0f4d0f4d0f4d0f4d0400000000000
|
||||
-- 020:090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900500000000000
|
||||
-- 021:01000100010001000100f10001100110011001100110f11001200120012001200120f1201130113011302130213021302130313041308130a130d130380000000000
|
||||
-- 032:010001100100011001000110010001100100010001000100010001000100010001000100010001000100010001000100010001000100010001000100301000000800
|
||||
-- 033:000000010002000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d40000000004
|
||||
-- 044:0600f6000620f6000600f6000610f600f600f6000600f600f600f600f6000600060006000600060006000600060006000600060006000600060006004600000f0f00
|
||||
-- 045:0000f0000020f0000000f0000010f000f000f0000000f000f000f000f0000000000000000000000000000000000000000000000000000000000000004600000f0f00
|
||||
-- 057:000000010002000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d40000000004
|
||||
-- 058:41004110410041104100411041004110c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100100000080800
|
||||
-- 059:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000
|
||||
-- 060:220022002200820082008200820082008200820082008200820082008200820082008200820082008200820082008200820082008200820082008200100000000000
|
||||
-- 061:9f009f00bf00df00df00ef00ef00ef00ef00ef00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00400000000000
|
||||
-- 062:00000100010001000100510081008100910091009100a100a100a100a100a100b100b100b100b100c100c100c100d100d100d100e100e100e100f100484000000000
|
||||
-- 063:00b000100000000000000000100060009000b000c000d000d000e000e000e000f000f000f000f000f000f000f000f000f000f000f000f000f000f000405000000000
|
||||
-- </SFX>
|
||||
-- <WAVES>
|
||||
-- 000:bcceefceedddddc84333121268abaa99
|
||||
-- 001:6789bdd96adc83248dd6334adda7578b
|
||||
-- 002:0123456789abcdef0123456789abcdef
|
||||
-- 003:224578acdeeeeddcba95434567653100
|
||||
-- 004:00000000ffffffff00000000ffffffff
|
||||
-- 005:0123456789abcdeffedcba9876543210
|
||||
-- 006:0123456789abcdef0123456789abcdef
|
||||
-- 007:76543210123456789abcdefedcba9878
|
||||
-- 008:0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f
|
||||
-- 009:fff000fff000fff000fff000fff000ff
|
||||
-- </WAVES>
|
||||
-- <TILES>
|
||||
-- 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>
|
||||
-- <MAP>
|
||||
-- 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>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
-- title: Mr Anderson's Adventure
|
||||
-- author: Zsolt Tasnadi
|
||||
-- title: Definitely not an Impostor
|
||||
-- name: impostor
|
||||
-- author: Teletype Games
|
||||
-- desc: Life of a programmer in the Vector
|
||||
-- site: https://github.com/rastasi/mranderson
|
||||
-- site: https://git.teletype.hu/games/impostor
|
||||
-- license: MIT License
|
||||
-- version: 0.10
|
||||
-- version: 0.1
|
||||
-- script: lua
|
||||
|
||||
9
inc/screen/screen.home.lua
Normal file
9
inc/screen/screen.home.lua
Normal file
@@ -0,0 +1,9 @@
|
||||
Screen.register({
|
||||
id = "home",
|
||||
name = "Home",
|
||||
decisions = {
|
||||
"go_to_toilet",
|
||||
"go_to_walking_to_office",
|
||||
},
|
||||
background = "bedroom",
|
||||
})
|
||||
32
inc/screen/screen.manager.lua
Normal file
32
inc/screen/screen.manager.lua
Normal file
@@ -0,0 +1,32 @@
|
||||
local _screens = {}
|
||||
|
||||
--- Registers a screen definition.
|
||||
-- @param screen_data table The screen data table.
|
||||
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.situations then
|
||||
screen_data.situations = {}
|
||||
end
|
||||
if not screen_data.init then
|
||||
screen_data.init = function() end
|
||||
end
|
||||
if not screen_data.update then
|
||||
screen_data.update = function() end
|
||||
end
|
||||
_screens[screen_data.id] = screen_data
|
||||
end
|
||||
|
||||
--- Gets a screen by ID.
|
||||
-- @param screen_id string The ID of the screen.
|
||||
-- @return table The screen table or nil.
|
||||
function Screen.get_by_id(screen_id)
|
||||
return _screens[screen_id]
|
||||
end
|
||||
|
||||
--- Gets all registered screens.
|
||||
-- @return table A table containing all registered screen data, indexed by their IDs.
|
||||
function Screen.get_all()
|
||||
return _screens
|
||||
end
|
||||
14
inc/screen/screen.office.lua
Normal file
14
inc/screen/screen.office.lua
Normal file
@@ -0,0 +1,14 @@
|
||||
Screen.register({
|
||||
id = "office",
|
||||
name = "Office",
|
||||
decisions = {
|
||||
"play_button_mash",
|
||||
"play_rhythm",
|
||||
"play_ddr",
|
||||
"go_to_walking_to_home",
|
||||
"have_a_coffee",
|
||||
},
|
||||
situations = {
|
||||
"drink_coffee",
|
||||
},
|
||||
})
|
||||
7
inc/screen/screen.toilet.lua
Normal file
7
inc/screen/screen.toilet.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
Screen.register({
|
||||
id = "toilet",
|
||||
name = "Toilet",
|
||||
decisions = {
|
||||
"go_to_home",
|
||||
}
|
||||
})
|
||||
8
inc/screen/screen.walking_to_home.lua
Normal file
8
inc/screen/screen.walking_to_home.lua
Normal file
@@ -0,0 +1,8 @@
|
||||
Screen.register({
|
||||
id = "walking_to_home",
|
||||
name = "Walking to home",
|
||||
decisions = {
|
||||
"go_to_home",
|
||||
"go_to_office",
|
||||
}
|
||||
})
|
||||
8
inc/screen/screen.walking_to_office.lua
Normal file
8
inc/screen/screen.walking_to_office.lua
Normal file
@@ -0,0 +1,8 @@
|
||||
Screen.register({
|
||||
id = "walking_to_office",
|
||||
name = "Walking to office",
|
||||
decisions = {
|
||||
"go_to_home",
|
||||
"go_to_office",
|
||||
}
|
||||
})
|
||||
7
inc/situation/situation.drink_coffee.lua
Normal file
7
inc/situation/situation.drink_coffee.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
Situation.register({
|
||||
id = "drink_coffee",
|
||||
handle = function()
|
||||
Audio.sfx_select()
|
||||
Sprite.show("norman", 100, 100)
|
||||
end,
|
||||
})
|
||||
65
inc/situation/situation.manager.lua
Normal file
65
inc/situation/situation.manager.lua
Normal file
@@ -0,0 +1,65 @@
|
||||
local _situations = {}
|
||||
|
||||
--- Registers a situation definition.
|
||||
-- @param situation table The situation data table.
|
||||
function Situation.register(situation)
|
||||
if not situation or not situation.id then
|
||||
PopupWindow.show({"Error: Invalid situation object registered (missing id)!"})
|
||||
return
|
||||
end
|
||||
if not situation.handle then
|
||||
situation.handle = function() end
|
||||
end
|
||||
if not situation.update then
|
||||
situation.update = function() end
|
||||
end
|
||||
if _situations[situation.id] then
|
||||
trace("Warning: Overwriting situation with id: " .. situation.id)
|
||||
end
|
||||
_situations[situation.id] = situation
|
||||
end
|
||||
|
||||
--- Gets a situation by ID.
|
||||
-- @param id string The situation ID.
|
||||
-- @return table The situation table or nil.
|
||||
function Situation.get_by_id(id)
|
||||
return _situations[id]
|
||||
end
|
||||
|
||||
--- Gets all registered situations, optionally filtered by screen ID.
|
||||
-- @param screen_id string Optional. If provided, returns situations associated with this screen ID.
|
||||
-- @return table A table containing all registered situation data, indexed by their IDs, or filtered by screen_id.
|
||||
function Situation.get_all(screen_id)
|
||||
if screen_id then
|
||||
local filtered_situations = {}
|
||||
for _, situation in pairs(_situations) do
|
||||
if situation.screen_id == screen_id then
|
||||
table.insert(filtered_situations, situation)
|
||||
end
|
||||
end
|
||||
return filtered_situations
|
||||
end
|
||||
return _situations
|
||||
end
|
||||
|
||||
--- Applies a situation, checking screen compatibility and returning the new situation ID if successful.
|
||||
-- @param id string The situation ID to apply.
|
||||
-- @param current_screen_id string The ID of the currently active screen.
|
||||
-- @return string|nil The ID of the applied situation if successful, otherwise nil.
|
||||
function Situation.apply(id, current_screen_id)
|
||||
local situation = Situation.get_by_id(id)
|
||||
local screen = Screen.get_by_id(current_screen_id)
|
||||
|
||||
if not situation then
|
||||
trace("Error: No situation found with id: " .. id)
|
||||
return nil
|
||||
end
|
||||
|
||||
if Util.contains(screen.situations, id) then
|
||||
situation.handle()
|
||||
return id
|
||||
else
|
||||
trace("Info: Situation " .. id .. " cannot be applied to current screen (id: " .. current_screen_id .. ").")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
83
inc/sprite/sprite.manager.lua
Normal file
83
inc/sprite/sprite.manager.lua
Normal file
@@ -0,0 +1,83 @@
|
||||
local _sprites = {}
|
||||
local _active_sprites = {}
|
||||
|
||||
--- Registers a sprite definition.
|
||||
-- @param sprite_data table A table containing the sprite definition.
|
||||
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
|
||||
|
||||
--- Schedules a sprite for drawing.
|
||||
-- @param id string The unique identifier of the sprite.
|
||||
-- @param x number The x-coordinate.
|
||||
-- @param y number The y-coordinate.
|
||||
-- @param[opt] colorkey number The color index for transparency.
|
||||
-- @param[opt] scale number The scaling factor.
|
||||
-- @param[opt] flip_x number Set to 1 to flip horizontally.
|
||||
-- @param[opt] flip_y number Set to 1 to flip vertically.
|
||||
-- @param[opt] rot number The rotation in degrees.
|
||||
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.
|
||||
-- @param id string The unique identifier of the sprite.
|
||||
function Sprite.hide(id)
|
||||
_active_sprites[id] = nil
|
||||
end
|
||||
|
||||
--- Draws all scheduled sprites.
|
||||
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
|
||||
|
||||
local colorkey = params.colorkey or sprite_data.colorkey or 0
|
||||
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
|
||||
end
|
||||
17
inc/sprite/sprite.norman.lua
Normal file
17
inc/sprite/sprite.norman.lua
Normal file
@@ -0,0 +1,17 @@
|
||||
Sprite.register({
|
||||
id = "norman",
|
||||
sprites = {
|
||||
-- Body (sprite index 0)
|
||||
{ s = 0, x_offset = 0, y_offset = 0 },
|
||||
-- Head (sprite index 1)
|
||||
{ s = 1, x_offset = 0, y_offset = -8 },
|
||||
-- Left Arm (sprite index 2)
|
||||
{ s = 2, x_offset = -4, y_offset = 4 },
|
||||
-- Right Arm (sprite index 3, flipped)
|
||||
{ s = 3, x_offset = 4, y_offset = 4, flip_x = 1 }, -- Flipped arm
|
||||
-- Left Leg (sprite index 4)
|
||||
{ s = 4, x_offset = -2, y_offset = 8 },
|
||||
-- Right Leg (sprite index 5, flipped)
|
||||
{ s = 5, x_offset = 2, y_offset = 8, flip_x = 1 } -- Flipped leg
|
||||
}
|
||||
})
|
||||
@@ -1,8 +1,27 @@
|
||||
function Input.up() return btnp(0) end
|
||||
function Input.down() return btnp(1) end
|
||||
function Input.left() return btnp(2) end
|
||||
function Input.right() return btnp(3) end
|
||||
function Input.player_jump() return btnp(4) end
|
||||
function Input.menu_confirm() return btnp(4) end
|
||||
function Input.player_interact() return btnp(5) end -- B button
|
||||
function Input.menu_back() return btnp(5) end
|
||||
local INPUT_KEY_UP = 0
|
||||
local INPUT_KEY_DOWN = 1
|
||||
local INPUT_KEY_LEFT = 2
|
||||
local INPUT_KEY_RIGHT = 3
|
||||
local INPUT_KEY_A = 4 local INPUT_KEY_B = 5 local INPUT_KEY_Y = 7
|
||||
local INPUT_KEY_SPACE = 48
|
||||
local INPUT_KEY_BACKSPACE = 51
|
||||
local INPUT_KEY_ENTER = 50
|
||||
|
||||
--- Checks if Up is pressed.
|
||||
function Input.up() return btnp(INPUT_KEY_UP) end
|
||||
--- Checks if Down is pressed.
|
||||
function Input.down() return btnp(INPUT_KEY_DOWN) end
|
||||
--- Checks if Left is pressed.
|
||||
function Input.left() return btnp(INPUT_KEY_LEFT) end
|
||||
--- Checks if Right is pressed.
|
||||
function Input.right() return btnp(INPUT_KEY_RIGHT) end
|
||||
--- Checks if Select is pressed.
|
||||
function Input.select() return btnp(INPUT_KEY_A) or keyp(INPUT_KEY_SPACE) end
|
||||
--- Checks if Menu Confirm is pressed.
|
||||
function Input.menu_confirm() return btnp(INPUT_KEY_A) or keyp(INPUT_KEY_ENTER) end
|
||||
--- Checks if Player Interact is pressed.
|
||||
function Input.player_interact() return btnp(INPUT_KEY_B) or keyp(INPUT_KEY_ENTER) end
|
||||
--- Checks if Menu Back is pressed.
|
||||
function Input.menu_back() return btnp(INPUT_KEY_Y) or keyp(INPUT_KEY_BACKSPACE) end
|
||||
--- Checks if Toggle Popup is pressed.
|
||||
function Input.toggle_popup() return keyp(INPUT_KEY_ENTER) end
|
||||
|
||||
@@ -1,44 +1,24 @@
|
||||
local STATE_HANDLERS = {
|
||||
[WINDOW_SPLASH] = function()
|
||||
SplashWindow.update()
|
||||
SplashWindow.draw()
|
||||
end,
|
||||
[WINDOW_INTRO] = function()
|
||||
IntroWindow.update()
|
||||
IntroWindow.draw()
|
||||
end,
|
||||
[WINDOW_MENU] = function()
|
||||
MenuWindow.update()
|
||||
MenuWindow.draw()
|
||||
end,
|
||||
[WINDOW_GAME] = function()
|
||||
GameWindow.update()
|
||||
GameWindow.draw()
|
||||
end,
|
||||
[WINDOW_POPUP] = function()
|
||||
GameWindow.draw()
|
||||
PopupWindow.update()
|
||||
PopupWindow.draw()
|
||||
end,
|
||||
[WINDOW_INVENTORY] = function()
|
||||
InventoryWindow.update()
|
||||
InventoryWindow.draw()
|
||||
end,
|
||||
[WINDOW_INVENTORY_ACTION] = function()
|
||||
InventoryWindow.draw()
|
||||
PopupWindow.draw()
|
||||
PopupWindow.update()
|
||||
end,
|
||||
[WINDOW_CONFIGURATION] = function()
|
||||
ConfigurationWindow.update()
|
||||
ConfigurationWindow.draw()
|
||||
end,
|
||||
}
|
||||
local initialized_game = false
|
||||
|
||||
--- Initializes game state.
|
||||
local function init_game()
|
||||
if initialized_game then return end
|
||||
Context.reset()
|
||||
Window.set_current("splash") -- Set initial window using new manager
|
||||
MenuWindow.refresh_menu_items()
|
||||
initialized_game = true
|
||||
end
|
||||
|
||||
--- Main game loop (TIC-80 callback).
|
||||
function TIC()
|
||||
init_game()
|
||||
cls(Config.colors.black)
|
||||
local handler = STATE_HANDLERS[Context.active_window]
|
||||
local handler = Window.get_current_handler() -- Get handler from Window manager
|
||||
if handler then
|
||||
handler()
|
||||
end
|
||||
Meter.update()
|
||||
if Context.game_in_progress then
|
||||
UI.draw_meters()
|
||||
end
|
||||
end
|
||||
|
||||
28
inc/system/system.print.lua
Normal file
28
inc/system/system.print.lua
Normal file
@@ -0,0 +1,28 @@
|
||||
--- Prints text with shadow.
|
||||
-- @param text string The text to print.
|
||||
-- @param x number The x-coordinate.
|
||||
-- @param y number The y-coordinate.
|
||||
-- @param color number The color of the text.
|
||||
-- @param[opt] fixed boolean If true, uses fixed-width font.
|
||||
-- @param[opt] scale number The scaling factor.
|
||||
function Print.text(text, x, y, color, fixed, scale)
|
||||
local shadow_color = Config.colors.black
|
||||
if color == shadow_color then shadow_color = Config.colors.light_grey end
|
||||
scale = scale or 1
|
||||
print(text, x + 1, y + 1, shadow_color, fixed, scale)
|
||||
print(text, x, y, color, fixed, scale)
|
||||
end
|
||||
|
||||
--- Prints centered text with shadow.
|
||||
-- @param text string The text to print.
|
||||
-- @param x number The x-coordinate for centering.
|
||||
-- @param y number The y-coordinate.
|
||||
-- @param color number The color of the text.
|
||||
-- @param[opt] fixed boolean If true, uses fixed-width font.
|
||||
-- @param[opt] scale number The scaling factor.
|
||||
function Print.text_center(text, x, y, color, fixed, scale)
|
||||
scale = scale or 1
|
||||
local text_width = print(text, 0, -6, 0, fixed, scale)
|
||||
local centered_x = x - (text_width / 2)
|
||||
Print.text(text, centered_x, y, color, fixed, scale)
|
||||
end
|
||||
@@ -1,29 +1,43 @@
|
||||
--- Draws the top bar.
|
||||
-- @param title string The title text to display.
|
||||
function UI.draw_top_bar(title)
|
||||
rect(0, 0, Config.screen.width, 10, Config.colors.dark_grey)
|
||||
print(title, 3, 2, Config.colors.green)
|
||||
Print.text(title, 3, 2, Config.colors.green)
|
||||
end
|
||||
|
||||
--- Draws dialog window.
|
||||
function UI.draw_dialog()
|
||||
PopupWindow.draw()
|
||||
end
|
||||
|
||||
--- Draws a menu.
|
||||
-- @param items table A table of menu items.
|
||||
-- @param selected_item number The index of the currently selected item.
|
||||
-- @param x number The x-coordinate for the menu.
|
||||
-- @param y number The y-coordinate for the menu.
|
||||
function UI.draw_menu(items, selected_item, x, y)
|
||||
for i, item in ipairs(items) do
|
||||
local current_y = y + (i-1)*10
|
||||
if i == selected_item then
|
||||
print(">", x - 8, current_y, Config.colors.green)
|
||||
Print.text(">", x - 8, current_y, Config.colors.green)
|
||||
end
|
||||
print(item.label, x, current_y, Config.colors.green)
|
||||
Print.text(item.label, x, current_y, Config.colors.green)
|
||||
end
|
||||
end
|
||||
|
||||
--- Updates menu selection.
|
||||
-- @param items table A table of menu items.
|
||||
-- @param selected_item number The current index of the selected item.
|
||||
-- @return number The updated index of the selected item.
|
||||
function UI.update_menu(items, selected_item)
|
||||
if Input.up() then
|
||||
Audio.sfx_beep()
|
||||
selected_item = selected_item - 1
|
||||
if selected_item < 1 then
|
||||
selected_item = #items
|
||||
end
|
||||
elseif Input.down() then
|
||||
Audio.sfx_beep()
|
||||
selected_item = selected_item + 1
|
||||
if selected_item > #items then
|
||||
selected_item = 1
|
||||
@@ -32,10 +46,13 @@ function UI.update_menu(items, selected_item)
|
||||
return selected_item
|
||||
end
|
||||
|
||||
--- Wraps text.
|
||||
-- @param text string The text to wrap.
|
||||
-- @param max_chars_per_line number The maximum characters per line.
|
||||
-- @return table A table of wrapped lines.
|
||||
function UI.word_wrap(text, max_chars_per_line)
|
||||
if text == nil then return {""} end
|
||||
local lines = {}
|
||||
|
||||
for input_line in (text .. "\n"):gmatch("(.-)\n") do
|
||||
local current_line = ""
|
||||
local words_in_line = 0
|
||||
@@ -50,21 +67,27 @@ function UI.word_wrap(text, max_chars_per_line)
|
||||
current_line = word
|
||||
end
|
||||
end
|
||||
|
||||
if words_in_line > 0 then
|
||||
table.insert(lines, current_line)
|
||||
else
|
||||
table.insert(lines, "")
|
||||
end
|
||||
end
|
||||
|
||||
if #lines == 0 then
|
||||
return {""}
|
||||
end
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
--- Creates a numeric stepper.
|
||||
-- @param label string The label for the stepper.
|
||||
-- @param value_getter function Function to get the current value.
|
||||
-- @param value_setter function Function to set the current value.
|
||||
-- @param min number The minimum value.
|
||||
-- @param max number The maximum value.
|
||||
-- @param step number The step increment.
|
||||
-- @param[opt] format string The format string for displaying the value.
|
||||
-- @return table A numeric stepper control definition.
|
||||
function UI.create_numeric_stepper(label, value_getter, value_setter, min, max, step, format)
|
||||
return {
|
||||
label = label,
|
||||
@@ -77,3 +100,79 @@ function UI.create_numeric_stepper(label, value_getter, value_setter, min, max,
|
||||
type = "numeric_stepper"
|
||||
}
|
||||
end
|
||||
|
||||
--- Creates an action item.
|
||||
-- @param label string The label for the action item.
|
||||
-- @param action function The function to execute when the item is selected.
|
||||
-- @return table An action item control definition.
|
||||
function UI.create_action_item(label, action)
|
||||
return {
|
||||
label = label,
|
||||
action = action,
|
||||
type = "action_item"
|
||||
}
|
||||
end
|
||||
|
||||
--- Draws decision selector.
|
||||
-- @param decisions table A table of decision items.
|
||||
-- @param selected_decision_index number The index of the selected decision.
|
||||
function UI.draw_decision_selector(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 = selected_decision.label
|
||||
local text_width = #decision_label * 4 local text_y = bar_y + 4
|
||||
local text_x = (Config.screen.width - text_width) / 2
|
||||
Print.text("<", 2, text_y, Config.colors.green)
|
||||
Print.text(decision_label, text_x, text_y, Config.colors.item) Print.text(">", Config.screen.width - 6, text_y, Config.colors.green) end
|
||||
end
|
||||
|
||||
--- Draws meters.
|
||||
function UI.draw_meters()
|
||||
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 bar_w = 44
|
||||
local bar_h = 2
|
||||
local bar_x = 182
|
||||
local label_x = 228
|
||||
local line_h = 5
|
||||
local start_y = 11
|
||||
local bar_offset = math.floor((line_h - bar_h) / 2)
|
||||
|
||||
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 + bar_offset
|
||||
local fill_w = math.max(0, math.floor((m[meter.key] / max) * 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
|
||||
print(meter.label, label_x, label_y, meter.color, false, 1, true)
|
||||
end
|
||||
end
|
||||
|
||||
--- Updates decision selector.
|
||||
-- @param decisions table A table of decision items.
|
||||
-- @param selected_decision_index number The current index of the selected decision.
|
||||
-- @return number The updated index of the selected decision.
|
||||
function UI.update_decision_selector(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
|
||||
return selected_decision_index
|
||||
end
|
||||
|
||||
37
inc/system/system.util.lua
Normal file
37
inc/system/system.util.lua
Normal file
@@ -0,0 +1,37 @@
|
||||
--- Utility functions.
|
||||
Util = {}
|
||||
|
||||
--- Safely wraps an index for an array.
|
||||
-- @param array table The array to index.
|
||||
-- @param index number The desired index (can be out of bounds).
|
||||
-- @return number The wrapped index within the array's bounds.
|
||||
function Util.safeindex(array, index)
|
||||
return ((index - 1 + #array) % #array) + 1
|
||||
end
|
||||
|
||||
--- Navigates to a screen by its ID.
|
||||
-- @param screen_id string The ID of the screen to go to.
|
||||
function Util.go_to_screen_by_id(screen_id)
|
||||
local screen = Screen.get_by_id(screen_id)
|
||||
if screen then
|
||||
Context.game.current_screen = screen_id
|
||||
local all_decisions_for_screen = Decision.get_for_screen(screen)
|
||||
Context.game.decisions = Decision.filter_available(all_decisions_for_screen)
|
||||
Context.game.selected_decision_index = 1
|
||||
screen.init() -- Initialize the new screen
|
||||
else
|
||||
PopupWindow.show({"Error: Screen '" .. screen_id .. "' not found!"})
|
||||
end
|
||||
end
|
||||
|
||||
-- Checks if a table contains a specific value.
|
||||
-- @param t table The table to check.
|
||||
-- @param value any The value to look for.
|
||||
function Util.contains(t, value)
|
||||
for i = 1, #t do
|
||||
if t[i] == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
107
inc/window/window.audiotest.lua
Normal file
107
inc/window/window.audiotest.lua
Normal file
@@ -0,0 +1,107 @@
|
||||
AudioTestWindow = {
|
||||
index_menu = 1,
|
||||
index_func = 1,
|
||||
list_func = {},
|
||||
menuitems = {},
|
||||
last_pressed = false
|
||||
}
|
||||
|
||||
--- Generates menu items for audio test.
|
||||
-- @param list_func table List of audio functions.
|
||||
-- @param index_func number Current index of selected function.
|
||||
-- @return table Generated menu items.
|
||||
function AudioTestWindow.generate_menuitems(list_func, index_func)
|
||||
return {
|
||||
{
|
||||
label = "Play music/sound: " .. (list_func[index_func] or "?"),
|
||||
decision = function()
|
||||
local current_func = Audio[list_func[index_func]]
|
||||
if current_func then
|
||||
current_func()
|
||||
else
|
||||
trace("Invalid Audio function: " .. list_func[index_menu])
|
||||
end
|
||||
end
|
||||
},
|
||||
{
|
||||
label = "Stop playing music",
|
||||
decision = function()
|
||||
Audio.music_stop()
|
||||
end
|
||||
},
|
||||
{
|
||||
label = "Back",
|
||||
decision = function()
|
||||
AudioTestWindow.back()
|
||||
end
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
--- Generates list of audio functions.
|
||||
-- @return table A sorted list of audio function names.
|
||||
function AudioTestWindow.generate_listfunc()
|
||||
local result = {}
|
||||
|
||||
for k, v in pairs(Audio) do
|
||||
if type(v) == "function" then
|
||||
result[#result + 1] = k
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(result)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
--- Navigates back from audio test window.
|
||||
function AudioTestWindow.back()
|
||||
Audio.sfx_deselect()
|
||||
GameWindow.set_state("menu")
|
||||
end
|
||||
|
||||
--- Initializes audio test window.
|
||||
function AudioTestWindow.init()
|
||||
AudioTestWindow.last_pressed = false
|
||||
AudioTestWindow.index_menu = 1
|
||||
AudioTestWindow.index_func = 1
|
||||
AudioTestWindow.list_func = AudioTestWindow.generate_listfunc()
|
||||
AudioTestWindow.menuitems = AudioTestWindow.generate_menuitems(
|
||||
AudioTestWindow.list_func, AudioTestWindow.index_func
|
||||
)
|
||||
end
|
||||
|
||||
--- Draws audio test window.
|
||||
function AudioTestWindow.draw()
|
||||
UI.draw_top_bar("Audio test")
|
||||
UI.draw_menu(AudioTestWindow.menuitems, AudioTestWindow.index_menu, 20, 50)
|
||||
end
|
||||
|
||||
--- Updates audio test window logic.
|
||||
function AudioTestWindow.update()
|
||||
if Input.up() then
|
||||
AudioTestWindow.index_menu = Util.safeindex(AudioTestWindow.menuitems, AudioTestWindow.index_menu - 1)
|
||||
elseif Input.down() then
|
||||
AudioTestWindow.index_menu = Util.safeindex(AudioTestWindow.menuitems, AudioTestWindow.index_menu + 1)
|
||||
elseif Input.left() then
|
||||
AudioTestWindow.index_func = Util.safeindex(
|
||||
AudioTestWindow.list_func,
|
||||
AudioTestWindow.index_func - 1
|
||||
)
|
||||
AudioTestWindow.menuitems = AudioTestWindow.generate_menuitems(
|
||||
AudioTestWindow.list_func, AudioTestWindow.index_func
|
||||
)
|
||||
elseif Input.right() then
|
||||
AudioTestWindow.index_func = Util.safeindex(
|
||||
AudioTestWindow.list_func,
|
||||
AudioTestWindow.index_func + 1
|
||||
)
|
||||
AudioTestWindow.menuitems = AudioTestWindow.generate_menuitems(
|
||||
AudioTestWindow.list_func, AudioTestWindow.index_func
|
||||
)
|
||||
elseif Input.menu_confirm() then
|
||||
AudioTestWindow.menuitems[AudioTestWindow.index_menu].decision()
|
||||
elseif Input.menu_back() then
|
||||
AudioTestWindow.back()
|
||||
end
|
||||
end
|
||||
@@ -3,66 +3,67 @@ ConfigurationWindow = {
|
||||
selected_control = 1,
|
||||
}
|
||||
|
||||
--- Initializes configuration window.
|
||||
function ConfigurationWindow.init()
|
||||
ConfigurationWindow.controls = {
|
||||
UI.create_numeric_stepper(
|
||||
"Move Speed",
|
||||
function() return Config.physics.move_speed end,
|
||||
function(v) Config.physics.move_speed = v end,
|
||||
0.5, 3, 0.1, "%.1f"
|
||||
UI.create_decision_item(
|
||||
"Save",
|
||||
function() Config.save() end
|
||||
),
|
||||
UI.create_numeric_stepper(
|
||||
"Max Jumps",
|
||||
function() return Config.physics.max_jumps end,
|
||||
function(v) Config.physics.max_jumps = v end,
|
||||
1, 5, 1, "%d"
|
||||
UI.create_decision_item(
|
||||
"Restore Defaults",
|
||||
function() Config.restore_defaults() end
|
||||
),
|
||||
}
|
||||
end
|
||||
|
||||
--- Draws configuration window.
|
||||
function ConfigurationWindow.draw()
|
||||
UI.draw_top_bar("Configuration")
|
||||
|
||||
local x_start = 10 -- Left margin for labels
|
||||
local y_start = 40
|
||||
local x_value_right_align = Config.screen.width - 10 -- Right margin for values
|
||||
local char_width = 4 -- Approximate character width for default font
|
||||
|
||||
local x_start = 10 local y_start = 40
|
||||
local x_value_right_align = Config.screen.width - 10
|
||||
local char_width = 4
|
||||
for i, control in ipairs(ConfigurationWindow.controls) do
|
||||
local current_y = y_start + (i - 1) * 12
|
||||
local color = Config.colors.green
|
||||
|
||||
if control.type == "numeric_stepper" then
|
||||
local value = control.get()
|
||||
local label_text = control.label
|
||||
local value_text = string.format(control.format, value)
|
||||
|
||||
-- Calculate x position for right-aligned value
|
||||
local value_x = x_value_right_align - (#value_text * char_width)
|
||||
|
||||
if i == ConfigurationWindow.selected_control then
|
||||
color = Config.colors.item
|
||||
print("<", x_start -8, current_y, color)
|
||||
print(label_text, x_start, current_y, color) -- Shift label due to '<'
|
||||
print(value_text, value_x, current_y, color)
|
||||
print(">", x_value_right_align + 4, current_y, color) -- Print '>' after value
|
||||
Print.text("<", x_start -8, current_y, color)
|
||||
Print.text(label_text, x_start, current_y, color) Print.text(value_text, value_x, current_y, color)
|
||||
Print.text(">", x_value_right_align + 4, current_y, color) else
|
||||
Print.text(label_text, x_start, current_y, color)
|
||||
Print.text(value_text, value_x, current_y, color)
|
||||
end
|
||||
elseif control.type == "decision_item" then
|
||||
local label_text = control.label
|
||||
if i == ConfigurationWindow.selected_control then
|
||||
color = Config.colors.item
|
||||
Print.text("<", x_start -8, current_y, color)
|
||||
Print.text(label_text, x_start, current_y, color)
|
||||
Print.text(">", x_start + 8 + (#label_text * char_width) + 4, current_y, color)
|
||||
else
|
||||
print(label_text, x_start, current_y, color)
|
||||
print(value_text, value_x, current_y, color)
|
||||
Print.text(label_text, x_start, current_y, color)
|
||||
end
|
||||
end
|
||||
|
||||
print("Press B to go back", x_start, 120, Config.colors.light_grey)
|
||||
end
|
||||
Print.text("Press B to go back", x_start, 120, Config.colors.light_grey)
|
||||
end
|
||||
|
||||
--- Updates configuration window logic.
|
||||
function ConfigurationWindow.update()
|
||||
if Input.menu_back() then
|
||||
-- I need to find out how to switch back to the menu
|
||||
-- For now, I'll assume a function GameWindow.set_state exists
|
||||
GameWindow.set_state(WINDOW_MENU)
|
||||
GameWindow.set_state("menu")
|
||||
return
|
||||
end
|
||||
|
||||
-- Navigate between controls
|
||||
if Input.up() then
|
||||
ConfigurationWindow.selected_control = ConfigurationWindow.selected_control - 1
|
||||
if ConfigurationWindow.selected_control < 1 then
|
||||
@@ -75,16 +76,19 @@ function ConfigurationWindow.update()
|
||||
end
|
||||
end
|
||||
|
||||
-- Modify control value
|
||||
local control = ConfigurationWindow.controls[ConfigurationWindow.selected_control]
|
||||
if control then
|
||||
if control.type == "numeric_stepper" then
|
||||
local current_value = control.get()
|
||||
if Input.left() then
|
||||
local new_value = math.max(control.min, current_value - control.step)
|
||||
if btnp(2) then local new_value = math.max(control.min, current_value - control.step)
|
||||
control.set(new_value)
|
||||
elseif Input.right() then
|
||||
local new_value = math.min(control.max, current_value + control.step)
|
||||
elseif btnp(3) then local new_value = math.min(control.max, current_value + control.step)
|
||||
control.set(new_value)
|
||||
end
|
||||
elseif control.type == "decision_item" then
|
||||
if Input.menu_confirm() then
|
||||
control.decision()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,35 +1,62 @@
|
||||
local _available_decisions = {}
|
||||
local _selected_decision_index = 1
|
||||
|
||||
--- Draws the game window.
|
||||
function GameWindow.draw()
|
||||
local currentScreenData = Context.screens[Context.current_screen]
|
||||
|
||||
UI.draw_top_bar(currentScreenData.name)
|
||||
|
||||
-- Draw platforms
|
||||
for _, p in ipairs(currentScreenData.platforms) do
|
||||
rect(p.x, p.y, p.w, p.h, Config.colors.green)
|
||||
local screen = Screen.get_by_id(Context.game.current_screen)
|
||||
Map.draw(screen.background)
|
||||
UI.draw_top_bar(screen.name)
|
||||
if #_available_decisions > 0 then
|
||||
UI.draw_decision_selector(_available_decisions, _selected_decision_index)
|
||||
end
|
||||
|
||||
-- Draw items
|
||||
for _, item in ipairs(currentScreenData.items) do
|
||||
spr(item.sprite_id, item.x, item.y, 0)
|
||||
end
|
||||
|
||||
-- Draw NPCs
|
||||
for _, npc in ipairs(currentScreenData.npcs) do
|
||||
spr(npc.sprite_id, npc.x, npc.y, 0)
|
||||
end
|
||||
|
||||
-- Draw ground
|
||||
rect(Context.ground.x, Context.ground.y, Context.ground.w, Context.ground.h, Config.colors.dark_grey)
|
||||
|
||||
-- Draw player
|
||||
Player.draw()
|
||||
Sprite.draw()
|
||||
end
|
||||
|
||||
--- Updates the game window logic.
|
||||
function GameWindow.update()
|
||||
Player.update() -- Call the encapsulated player update logic
|
||||
if Input.menu_back() then
|
||||
Context.current_window = "menu"
|
||||
MenuWindow.refresh_menu_items()
|
||||
return
|
||||
end
|
||||
|
||||
local screen = Screen.get_by_id(Context.game.current_screen)
|
||||
screen.update()
|
||||
|
||||
-- Handle situations (Context.game.current_situation is still present)
|
||||
if Context.game.current_situation then
|
||||
local current_situation_obj = Situation.get_by_id(Context.game.current_situation)
|
||||
if current_situation_obj and current_situation_obj.update then
|
||||
current_situation_obj.update()
|
||||
end
|
||||
end
|
||||
|
||||
-- Fetch and filter decisions locally
|
||||
local all_decisions_for_screen = Decision.get_for_screen(screen)
|
||||
_available_decisions = Decision.filter_available(all_decisions_for_screen)
|
||||
|
||||
if #_available_decisions == 0 then return end
|
||||
|
||||
local new_selected_decision_index = UI.update_decision_selector(
|
||||
_available_decisions,
|
||||
_selected_decision_index
|
||||
)
|
||||
|
||||
if new_selected_decision_index ~= _selected_decision_index then
|
||||
_selected_decision_index = new_selected_decision_index
|
||||
end
|
||||
|
||||
if Input.select() then
|
||||
local selected_decision = _available_decisions[_selected_decision_index]
|
||||
if selected_decision and selected_decision.handle then
|
||||
Audio.sfx_select()
|
||||
selected_decision.handle()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets the active window.
|
||||
-- @param new_state number The ID of the new active window.
|
||||
function GameWindow.set_state(new_state)
|
||||
Context.active_window = new_state
|
||||
-- Add any state-specific initialization/cleanup here later if needed
|
||||
Context.current_window = new_state
|
||||
end
|
||||
@@ -1,25 +1,38 @@
|
||||
IntroWindow.y = Config.screen.height
|
||||
IntroWindow.speed = 0.5
|
||||
IntroWindow.text = [[
|
||||
Norman Reds’ everyday life
|
||||
seems ordinary: work,
|
||||
meetings, coffee, and
|
||||
endless notifications.
|
||||
But beneath him, or around
|
||||
him — something is
|
||||
constantly building, and
|
||||
it soon becomes clear
|
||||
that there is more going
|
||||
on than meets the eye.
|
||||
]]
|
||||
|
||||
--- Draws the intro window.
|
||||
function IntroWindow.draw()
|
||||
local x = (Config.screen.width - 132) / 2 -- Centered text
|
||||
print(Context.intro.text, x, Context.intro.y, Config.colors.green)
|
||||
local x = (Config.screen.width - 132) / 2
|
||||
Print.text(IntroWindow.text, x, IntroWindow.y, Config.colors.green)
|
||||
end
|
||||
|
||||
--- Updates the intro window logic.
|
||||
function IntroWindow.update()
|
||||
Context.intro.y = Context.intro.y - Context.intro.speed
|
||||
IntroWindow.y = IntroWindow.y - IntroWindow.speed
|
||||
|
||||
-- Count lines in intro text to determine when scrolling is done
|
||||
local lines = 1
|
||||
for _ in string.gmatch(Context.intro.text, "\n") do
|
||||
for _ in string.gmatch(IntroWindow.text, "\n") do
|
||||
lines = lines + 1
|
||||
end
|
||||
|
||||
-- When text is off-screen, go to menu
|
||||
if Context.intro.y < -lines * 8 then
|
||||
GameWindow.set_state(WINDOW_MENU)
|
||||
if IntroWindow.y < -lines * 8 then
|
||||
GameWindow.set_state("menu")
|
||||
end
|
||||
|
||||
-- Skip intro by pressing A
|
||||
if Input.menu_confirm() then
|
||||
GameWindow.set_state(WINDOW_MENU)
|
||||
GameWindow.set_state("menu")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
function InventoryWindow.draw()
|
||||
UI.draw_top_bar("Inventory")
|
||||
|
||||
if #Context.inventory == 0 then
|
||||
print("Inventory is empty.", 70, 70, Config.colors.light_grey)
|
||||
else
|
||||
for i, item in ipairs(Context.inventory) do
|
||||
local color = Config.colors.light_grey
|
||||
if i == Context.selected_inventory_item then
|
||||
color = Config.colors.green
|
||||
print(">", 60, 20 + i * 10, color)
|
||||
end
|
||||
print(item.name, 70, 20 + i * 10, color)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function InventoryWindow.update()
|
||||
Context.selected_inventory_item = UI.update_menu(Context.inventory, Context.selected_inventory_item)
|
||||
|
||||
if Input.menu_confirm() and #Context.inventory > 0 then
|
||||
local selected_item = Context.inventory[Context.selected_inventory_item]
|
||||
PopupWindow.show_menu_dialog(selected_item, {
|
||||
{label = "Use", action = Item.use},
|
||||
{label = "Drop", action = Item.drop},
|
||||
{label = "Look at", action = Item.look_at},
|
||||
{label = "Go back", action = Item.go_back_from_inventory_action}
|
||||
}, WINDOW_INVENTORY_ACTION)
|
||||
end
|
||||
|
||||
if Input.menu_back() then
|
||||
GameWindow.set_state(WINDOW_GAME)
|
||||
end
|
||||
end
|
||||
43
inc/window/window.manager.lua
Normal file
43
inc/window/window.manager.lua
Normal file
@@ -0,0 +1,43 @@
|
||||
local _windows = {}
|
||||
|
||||
--- Registers a window table.
|
||||
-- @param id string The ID of the window (e.g., "splash", "menu").
|
||||
-- @param window_table table The actual window module table (e.g., SplashWindow).
|
||||
function Window.register(id, window_table)
|
||||
_windows[id] = window_table
|
||||
end
|
||||
|
||||
--- Retrieves a registered window table by its ID.
|
||||
-- @param id string The ID of the window.
|
||||
-- @return table The window module table.
|
||||
function Window.get(id)
|
||||
return _windows[id]
|
||||
end
|
||||
|
||||
--- Sets the currently active window.
|
||||
-- @param id string The ID of the window to activate.
|
||||
function Window.set_current(id)
|
||||
Context.current_window = id
|
||||
end
|
||||
|
||||
--- Gets the ID of the currently active window.
|
||||
-- @return string The ID of the active window.
|
||||
function Window.get_current_id()
|
||||
return Context.current_window
|
||||
end
|
||||
|
||||
--- Gets the handler function for the currently active window.
|
||||
-- This function is used by the main game loop to update and draw the active window.
|
||||
-- @return function A function that updates and draws the current window.
|
||||
function Window.get_current_handler()
|
||||
local window_table = Window.get(Context.current_window)
|
||||
if window_table and window_table.update and window_table.draw then
|
||||
return function()
|
||||
window_table.update()
|
||||
window_table.draw()
|
||||
end
|
||||
else
|
||||
-- Fallback handler for unregistered or incomplete windows
|
||||
return function() trace("Error: No handler for window: " .. tostring(Context.current_window)) end
|
||||
end
|
||||
end
|
||||
@@ -1,42 +1,73 @@
|
||||
local _menu_items = {}
|
||||
|
||||
--- Draws the menu window.
|
||||
function MenuWindow.draw()
|
||||
UI.draw_top_bar("Main Menu")
|
||||
UI.draw_menu(Context.menu_items, Context.selected_menu_item, 108, 70)
|
||||
UI.draw_menu(_menu_items, Context.current_menu_item, 108, 70)
|
||||
end
|
||||
|
||||
--- Updates the menu window logic.
|
||||
function MenuWindow.update()
|
||||
Context.selected_menu_item = UI.update_menu(Context.menu_items, Context.selected_menu_item)
|
||||
Context.current_menu_item = UI.update_menu(_menu_items, Context.current_menu_item)
|
||||
|
||||
if Input.menu_confirm() then
|
||||
local selected_item = Context.menu_items[Context.selected_menu_item]
|
||||
if selected_item and selected_item.action then
|
||||
selected_item.action()
|
||||
local selected_item = _menu_items[Context.current_menu_item]
|
||||
if selected_item and selected_item.decision then
|
||||
Audio.sfx_select()
|
||||
selected_item.decision()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MenuWindow.play()
|
||||
-- Reset player state and screen for a new game
|
||||
Context.player.x = Config.player.start_x
|
||||
Context.player.y = Config.player.start_y
|
||||
Context.player.vx = 0
|
||||
Context.player.vy = 0
|
||||
Context.player.jumps = 0
|
||||
Context.current_screen = 1
|
||||
GameWindow.set_state(WINDOW_GAME)
|
||||
--- Starts a new game from the menu.
|
||||
function MenuWindow.new_game()
|
||||
Context.new_game() GameWindow.set_state("game")
|
||||
end
|
||||
|
||||
--- Loads a game from the menu.
|
||||
function MenuWindow.load_game()
|
||||
Context.load_game() GameWindow.set_state("game")
|
||||
end
|
||||
|
||||
--- Saves the current game from the menu.
|
||||
function MenuWindow.save_game()
|
||||
Context.save_game() end
|
||||
|
||||
--- Resumes the game from the menu.
|
||||
function MenuWindow.resume_game()
|
||||
GameWindow.set_state("game")
|
||||
end
|
||||
|
||||
--- Exits the game.
|
||||
function MenuWindow.exit()
|
||||
exit()
|
||||
end
|
||||
|
||||
--- Opens the configuration menu.
|
||||
function MenuWindow.configuration()
|
||||
ConfigurationWindow.init()
|
||||
GameWindow.set_state(WINDOW_CONFIGURATION)
|
||||
GameWindow.set_state("configuration")
|
||||
end
|
||||
|
||||
-- Initialize menu items after actions are defined
|
||||
Context.menu_items = {
|
||||
{label = "Play", action = MenuWindow.play},
|
||||
{label = "Configuration", action = MenuWindow.configuration},
|
||||
{label = "Exit", action = MenuWindow.exit}
|
||||
}
|
||||
--- Opens the audio test menu.
|
||||
function MenuWindow.audio_test()
|
||||
AudioTestWindow.init()
|
||||
GameWindow.set_state("audiotest")
|
||||
end
|
||||
|
||||
--- Refreshes menu items.
|
||||
function MenuWindow.refresh_menu_items()
|
||||
_menu_items = {}
|
||||
if Context.game_in_progress then
|
||||
table.insert(_menu_items, {label = "Resume Game", decision = MenuWindow.resume_game})
|
||||
table.insert(_menu_items, {label = "Save Game", decision = MenuWindow.save_game})
|
||||
end
|
||||
|
||||
table.insert(_menu_items, {label = "New Game", decision = MenuWindow.new_game})
|
||||
table.insert(_menu_items, {label = "Load Game", decision = MenuWindow.load_game})
|
||||
table.insert(_menu_items, {label = "Configuration", decision = MenuWindow.configuration})
|
||||
table.insert(_menu_items, {label = "Audio Test", decision = MenuWindow.audio_test})
|
||||
table.insert(_menu_items, {label = "Exit", decision = MenuWindow.exit})
|
||||
|
||||
Context.current_menu_item = 1
|
||||
end
|
||||
|
||||
257
inc/window/window.minigame.ddr.lua
Normal file
257
inc/window/window.minigame.ddr.lua
Normal file
@@ -0,0 +1,257 @@
|
||||
--- Initializes DDR minigame state.
|
||||
-- @param params table Optional parameters for configuration.
|
||||
function MinigameDDRWindow.init(params)
|
||||
Context.minigame_ddr = Minigame.configure_ddr(params)
|
||||
end
|
||||
|
||||
--- Starts the DDR minigame.
|
||||
-- @param return_window number The window ID to return to after the minigame.
|
||||
-- @param[opt] song_key string The key of the song to play.
|
||||
-- @param[opt] params table Optional parameters for minigame configuration.
|
||||
function MinigameDDRWindow.start(return_window, song_key, params)
|
||||
MinigameDDRWindow.init(params)
|
||||
Context.minigame_ddr.return_window = return_window or "game"
|
||||
Context.minigame_ddr.debug_song_key = song_key
|
||||
if song_key and Songs and Songs[song_key] then
|
||||
Context.minigame_ddr.current_song = Songs[song_key]
|
||||
Context.minigame_ddr.use_pattern = true
|
||||
Context.minigame_ddr.pattern_index = 1
|
||||
Context.minigame_ddr.debug_status = "Pattern loaded: " .. song_key
|
||||
else
|
||||
Context.minigame_ddr.use_pattern = false
|
||||
if song_key then
|
||||
Context.minigame_ddr.debug_status = "Song not found: " .. tostring(song_key)
|
||||
else
|
||||
Context.minigame_ddr.debug_status = "Random mode"
|
||||
end
|
||||
end
|
||||
Context.current_window = "minigame_ddr"
|
||||
end
|
||||
|
||||
--- Spawns a random arrow.
|
||||
local function spawn_arrow()
|
||||
local mg = Context.minigame_ddr
|
||||
local target = mg.target_arrows[math.random(1, 4)]
|
||||
table.insert(mg.arrows, {
|
||||
dir = target.dir,
|
||||
x = target.x,
|
||||
y = mg.bar_y + mg.bar_height + 10
|
||||
})
|
||||
end
|
||||
|
||||
--- Spawns an arrow in a specific direction.
|
||||
-- @param direction string The direction of the arrow ("left", "down", "up", "right").
|
||||
local function spawn_arrow_dir(direction)
|
||||
local mg = Context.minigame_ddr
|
||||
for _, target in ipairs(mg.target_arrows) do
|
||||
if target.dir == direction then
|
||||
table.insert(mg.arrows, {
|
||||
dir = direction,
|
||||
x = target.x,
|
||||
y = mg.bar_y + mg.bar_height + 10
|
||||
})
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Checks if an arrow is hit.
|
||||
-- @param arrow table The arrow data.
|
||||
-- @return boolean True if the arrow is hit, false otherwise.
|
||||
local function check_hit(arrow)
|
||||
local mg = Context.minigame_ddr
|
||||
local distance = math.abs(arrow.y - mg.target_y)
|
||||
return distance <= mg.hit_threshold
|
||||
end
|
||||
|
||||
--- Checks if an arrow is missed.
|
||||
-- @param arrow table The arrow data.
|
||||
-- @return boolean True if the arrow is missed, false otherwise.
|
||||
local function check_miss(arrow)
|
||||
local mg = Context.minigame_ddr
|
||||
return arrow.y > mg.target_y + mg.hit_threshold
|
||||
end
|
||||
|
||||
--- Draws an arrow.
|
||||
-- @param x number The x-coordinate.
|
||||
-- @param y number The y-coordinate.
|
||||
-- @param direction string The direction of the arrow.
|
||||
-- @param color number The color of the arrow.
|
||||
local function draw_arrow(x, y, direction, color)
|
||||
local size = 12
|
||||
local half = size / 2
|
||||
if direction == "left" then
|
||||
tri(x + half, y, x, y + half, x + half, y + size, color)
|
||||
rect(x + half, y + half - 2, half, 4, color)
|
||||
elseif direction == "right" then
|
||||
tri(x + half, y, x + size, y + half, x + half, y + size, color)
|
||||
rect(x, y + half - 2, half, 4, color)
|
||||
elseif direction == "up" then
|
||||
tri(x, y + half, x + half, y, x + size, y + half, color)
|
||||
rect(x + half - 2, y + half, 4, half, color)
|
||||
elseif direction == "down" then
|
||||
tri(x, y + half, x + half, y + size, x + size, y + half, color)
|
||||
rect(x + half - 2, y, 4, half, color)
|
||||
end
|
||||
end
|
||||
|
||||
--- Updates DDR minigame logic.
|
||||
function MinigameDDRWindow.update()
|
||||
local mg = Context.minigame_ddr
|
||||
if mg.bar_fill >= mg.max_fill then
|
||||
Meter.on_minigame_complete()
|
||||
Meter.show()
|
||||
Context.current_window = mg.return_window
|
||||
return
|
||||
end
|
||||
mg.frame_counter = mg.frame_counter + 1
|
||||
if mg.use_pattern and mg.current_song and mg.current_song.end_frame then
|
||||
if mg.frame_counter > mg.current_song.end_frame and #mg.arrows == 0 then
|
||||
Meter.on_minigame_complete()
|
||||
Meter.show()
|
||||
Context.current_window = mg.return_window
|
||||
return
|
||||
end
|
||||
end
|
||||
if mg.use_pattern and mg.current_song and mg.current_song.pattern then
|
||||
local pattern = mg.current_song.pattern
|
||||
while mg.pattern_index <= #pattern do
|
||||
local spawn_entry = pattern[mg.pattern_index]
|
||||
if mg.frame_counter >= spawn_entry.frame then
|
||||
spawn_arrow_dir(spawn_entry.dir)
|
||||
mg.pattern_index = mg.pattern_index + 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
mg.arrow_spawn_timer = mg.arrow_spawn_timer + 1
|
||||
if mg.arrow_spawn_timer >= mg.arrow_spawn_interval then
|
||||
spawn_arrow()
|
||||
mg.arrow_spawn_timer = 0
|
||||
end
|
||||
end
|
||||
local arrows_to_remove = {}
|
||||
for i, arrow in ipairs(mg.arrows) do
|
||||
arrow.y = arrow.y + mg.arrow_fall_speed
|
||||
if check_miss(arrow) then
|
||||
table.insert(arrows_to_remove, i)
|
||||
mg.bar_fill = mg.bar_fill - mg.miss_penalty
|
||||
if mg.bar_fill < 0 then
|
||||
mg.bar_fill = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
-- iterate backwards to avoid index shift issues
|
||||
for i = #arrows_to_remove, 1, -1 do
|
||||
table.remove(mg.arrows, arrows_to_remove[i])
|
||||
end
|
||||
for dir, _ in pairs(mg.input_cooldowns) do
|
||||
if mg.input_cooldowns[dir] > 0 then
|
||||
mg.input_cooldowns[dir] = mg.input_cooldowns[dir] - 1
|
||||
end
|
||||
end
|
||||
for dir, _ in pairs(mg.button_pressed_timers) do
|
||||
if mg.button_pressed_timers[dir] > 0 then
|
||||
mg.button_pressed_timers[dir] = mg.button_pressed_timers[dir] - 1
|
||||
end
|
||||
end
|
||||
local input_map = {
|
||||
left = Input.left(),
|
||||
down = Input.down(),
|
||||
up = Input.up(),
|
||||
right = Input.right()
|
||||
}
|
||||
for dir, pressed in pairs(input_map) do
|
||||
if pressed and mg.input_cooldowns[dir] == 0 then
|
||||
mg.input_cooldowns[dir] = mg.input_cooldown_duration
|
||||
mg.button_pressed_timers[dir] = mg.button_press_duration
|
||||
local hit = false
|
||||
for i, arrow in ipairs(mg.arrows) do
|
||||
if arrow.dir == dir and check_hit(arrow) then
|
||||
mg.bar_fill = mg.bar_fill + mg.fill_per_hit
|
||||
if mg.bar_fill > mg.max_fill then
|
||||
mg.bar_fill = mg.max_fill
|
||||
end
|
||||
table.remove(mg.arrows, i)
|
||||
hit = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not hit then
|
||||
mg.bar_fill = mg.bar_fill - 2
|
||||
if mg.bar_fill < 0 then
|
||||
mg.bar_fill = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Draws DDR minigame.
|
||||
function MinigameDDRWindow.draw()
|
||||
local mg = Context.minigame_ddr
|
||||
if not mg then
|
||||
cls(0)
|
||||
print("DDR ERROR: Context not initialized", 10, 10, 12)
|
||||
print("Press Z to return", 10, 20, 12)
|
||||
if Input.select() then
|
||||
Context.current_window = "game"
|
||||
end
|
||||
return
|
||||
end
|
||||
if mg.return_window == "game" then
|
||||
GameWindow.draw()
|
||||
end
|
||||
rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black)
|
||||
rect(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.light_grey)
|
||||
rectb(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.dark_grey)
|
||||
local fill_width = (mg.bar_fill / mg.max_fill) * mg.bar_width
|
||||
if fill_width > 0 then
|
||||
local bar_color = Config.colors.green
|
||||
if mg.bar_fill > 66 then
|
||||
bar_color = Config.colors.item
|
||||
elseif mg.bar_fill > 33 then
|
||||
bar_color = Config.colors.blue
|
||||
end
|
||||
rect(mg.bar_x, mg.bar_y, fill_width, mg.bar_height, bar_color)
|
||||
end
|
||||
local percentage = math.floor((mg.bar_fill / mg.max_fill) * 100)
|
||||
Print.text_center(percentage .. "%", mg.bar_x + mg.bar_width / 2, mg.bar_y + 2, Config.colors.black)
|
||||
if mg.target_arrows then
|
||||
for _, target in ipairs(mg.target_arrows) do
|
||||
local is_pressed = mg.button_pressed_timers[target.dir] and mg.button_pressed_timers[target.dir] > 0
|
||||
local color = is_pressed and Config.colors.green or Config.colors.light_grey
|
||||
draw_arrow(target.x, mg.target_y, target.dir, color)
|
||||
end
|
||||
end
|
||||
if mg.arrows then
|
||||
for _, arrow in ipairs(mg.arrows) do
|
||||
draw_arrow(arrow.x, arrow.y, arrow.dir, Config.colors.blue)
|
||||
end
|
||||
end
|
||||
Print.text_center("Hit the arrows!", Config.screen.width / 2, mg.bar_y + mg.bar_height + 10, Config.colors.light_grey)
|
||||
local debug_y = 60
|
||||
if mg.debug_status then
|
||||
Print.text_center(mg.debug_status, Config.screen.width / 2, debug_y, Config.colors.item)
|
||||
debug_y = debug_y + 10
|
||||
end
|
||||
if mg.use_pattern then
|
||||
Print.text_center(
|
||||
"PATTERN MODE - Frame:" .. mg.frame_counter,
|
||||
Config.screen.width / 2,
|
||||
debug_y,
|
||||
Config.colors.green
|
||||
)
|
||||
if mg.current_song and mg.current_song.pattern then
|
||||
Print.text_center(
|
||||
"Pattern Len:" .. #mg.current_song.pattern .. " Index:" .. mg.pattern_index,
|
||||
Config.screen.width / 2,
|
||||
debug_y + 10,
|
||||
Config.colors.green
|
||||
)
|
||||
end
|
||||
else
|
||||
Print.text_center("RANDOM MODE", Config.screen.width / 2, debug_y, Config.colors.blue)
|
||||
end
|
||||
end
|
||||
73
inc/window/window.minigame.mash.lua
Normal file
73
inc/window/window.minigame.mash.lua
Normal file
@@ -0,0 +1,73 @@
|
||||
--- Initializes button mash minigame state.
|
||||
-- @param params table Optional parameters for configuration.
|
||||
function MinigameButtonMashWindow.init(params)
|
||||
Context.minigame_button_mash = Minigame.configure_button_mash(params)
|
||||
end
|
||||
|
||||
--- Starts the button mash minigame.
|
||||
-- @param return_window number The window ID to return to after the minigame.
|
||||
-- @param[opt] params table Optional parameters for minigame configuration.
|
||||
function MinigameButtonMashWindow.start(return_window, params)
|
||||
MinigameButtonMashWindow.init(params)
|
||||
Context.minigame_button_mash.return_window = return_window or "game"
|
||||
Context.current_window = "minigame_button_mash"
|
||||
end
|
||||
|
||||
--- Updates button mash minigame logic.
|
||||
function MinigameButtonMashWindow.update()
|
||||
local mg = Context.minigame_button_mash
|
||||
if Input.select() then
|
||||
mg.bar_fill = mg.bar_fill + mg.fill_per_press
|
||||
mg.button_pressed_timer = mg.button_press_duration
|
||||
if mg.bar_fill > mg.max_fill then
|
||||
mg.bar_fill = mg.max_fill
|
||||
end
|
||||
end
|
||||
if mg.bar_fill >= mg.max_fill then
|
||||
Meter.on_minigame_complete()
|
||||
Meter.show()
|
||||
Context.current_window = mg.return_window
|
||||
return
|
||||
end
|
||||
local degradation = mg.base_degradation + (mg.bar_fill * mg.degradation_multiplier)
|
||||
mg.bar_fill = mg.bar_fill - degradation
|
||||
if mg.bar_fill < 0 then
|
||||
mg.bar_fill = 0
|
||||
end
|
||||
if mg.button_pressed_timer > 0 then
|
||||
mg.button_pressed_timer = mg.button_pressed_timer - 1
|
||||
end
|
||||
end
|
||||
|
||||
--- Draws button mash minigame.
|
||||
function MinigameButtonMashWindow.draw()
|
||||
local mg = Context.minigame_button_mash
|
||||
if mg.return_window == "game" then
|
||||
GameWindow.draw()
|
||||
end
|
||||
rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black)
|
||||
rect(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.light_grey)
|
||||
rectb(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.dark_grey)
|
||||
local fill_width = (mg.bar_fill / mg.max_fill) * mg.bar_width
|
||||
if fill_width > 0 then
|
||||
local bar_color = Config.colors.green
|
||||
if mg.bar_fill > 66 then
|
||||
bar_color = Config.colors.item
|
||||
elseif mg.bar_fill > 33 then
|
||||
bar_color = Config.colors.blue
|
||||
end
|
||||
rect(mg.bar_x, mg.bar_y, fill_width, mg.bar_height, bar_color)
|
||||
end
|
||||
local button_color = Config.colors.light_grey
|
||||
if mg.button_pressed_timer > 0 then
|
||||
button_color = Config.colors.green
|
||||
end
|
||||
circb(mg.button_x, mg.button_y, mg.button_size, button_color)
|
||||
if mg.button_pressed_timer > 0 then
|
||||
circ(mg.button_x, mg.button_y, mg.button_size - 2, button_color)
|
||||
end
|
||||
Print.text_center(" Z", mg.button_x - 2, mg.button_y - 3, Config.colors.light_grey)
|
||||
Print.text_center("MASH Z!", Config.screen.width / 2, mg.bar_y + mg.bar_height + 10, Config.colors.light_grey)
|
||||
local percentage = math.floor((mg.bar_fill / mg.max_fill) * 100)
|
||||
Print.text_center(percentage .. "%", mg.bar_x + mg.bar_width / 2, mg.bar_y + 2, Config.colors.black)
|
||||
end
|
||||
92
inc/window/window.minigame.rhythm.lua
Normal file
92
inc/window/window.minigame.rhythm.lua
Normal file
@@ -0,0 +1,92 @@
|
||||
--- Initializes rhythm minigame state.
|
||||
-- @param params table Optional parameters for configuration.
|
||||
function MinigameRhythmWindow.init(params)
|
||||
Context.minigame_rhythm = Minigame.configure_rhythm(params)
|
||||
end
|
||||
|
||||
--- Starts the rhythm minigame.
|
||||
-- @param return_window number The window ID to return to after the minigame.
|
||||
-- @param[opt] params table Optional parameters for minigame configuration.
|
||||
function MinigameRhythmWindow.start(return_window, params)
|
||||
MinigameRhythmWindow.init(params)
|
||||
Context.minigame_rhythm.return_window = return_window or "game"
|
||||
Context.current_window = "minigame_rhythm"
|
||||
end
|
||||
|
||||
--- Updates rhythm minigame logic.
|
||||
function MinigameRhythmWindow.update()
|
||||
local mg = Context.minigame_rhythm
|
||||
mg.line_position = mg.line_position + (mg.line_speed * mg.line_direction)
|
||||
if mg.line_position > 1 then
|
||||
mg.line_position = 1
|
||||
mg.line_direction = -1
|
||||
elseif mg.line_position < 0 then
|
||||
mg.line_position = 0
|
||||
mg.line_direction = 1
|
||||
end
|
||||
if mg.press_cooldown > 0 then
|
||||
mg.press_cooldown = mg.press_cooldown - 1
|
||||
end
|
||||
if Input.select() and mg.press_cooldown == 0 then
|
||||
mg.button_pressed_timer = mg.button_press_duration
|
||||
mg.press_cooldown = mg.press_cooldown_duration
|
||||
local target_left = mg.target_center - (mg.target_width / 2)
|
||||
local target_right = mg.target_center + (mg.target_width / 2)
|
||||
if mg.line_position >= target_left and mg.line_position <= target_right then
|
||||
mg.score = mg.score + 1
|
||||
else
|
||||
mg.score = mg.score - 1
|
||||
if mg.score < 0 then
|
||||
mg.score = 0
|
||||
end
|
||||
end
|
||||
mg.target_width = mg.initial_target_width * (mg.target_shrink_rate ^ mg.score)
|
||||
if mg.target_width < mg.min_target_width then
|
||||
mg.target_width = mg.min_target_width
|
||||
end
|
||||
end
|
||||
if mg.score >= mg.max_score then
|
||||
Meter.on_minigame_complete()
|
||||
Meter.show()
|
||||
Context.current_window = mg.return_window
|
||||
return
|
||||
end
|
||||
if mg.button_pressed_timer > 0 then
|
||||
mg.button_pressed_timer = mg.button_pressed_timer - 1
|
||||
end
|
||||
end
|
||||
|
||||
--- Draws rhythm minigame.
|
||||
function MinigameRhythmWindow.draw()
|
||||
local mg = Context.minigame_rhythm
|
||||
if mg.return_window == "game" then
|
||||
GameWindow.draw()
|
||||
end
|
||||
rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black)
|
||||
rect(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.light_grey)
|
||||
rectb(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.dark_grey)
|
||||
rect(mg.bar_x, mg.bar_y, mg.bar_width, mg.bar_height, Config.colors.dark_grey)
|
||||
local target_left = mg.target_center - (mg.target_width / 2)
|
||||
local target_x = mg.bar_x + (target_left * mg.bar_width)
|
||||
local target_width_pixels = mg.target_width * mg.bar_width
|
||||
rect(target_x, mg.bar_y, target_width_pixels, mg.bar_height, Config.colors.green)
|
||||
local line_x = mg.bar_x + (mg.line_position * mg.bar_width)
|
||||
rect(line_x - 1, mg.bar_y, 2, mg.bar_height, Config.colors.item)
|
||||
local score_text = "SCORE: " .. mg.score .. " / " .. mg.max_score
|
||||
Print.text_center(score_text, Config.screen.width / 2, mg.bar_y + mg.bar_height + 8, Config.colors.light_grey)
|
||||
Print.text_center(
|
||||
"Press Z when line is in green!",
|
||||
Config.screen.width / 2,
|
||||
mg.bar_y + mg.bar_height + 20,
|
||||
Config.colors.light_grey
|
||||
)
|
||||
local button_color = Config.colors.light_grey
|
||||
if mg.button_pressed_timer > 0 then
|
||||
button_color = Config.colors.green
|
||||
end
|
||||
circb(mg.button_x, mg.button_y, mg.button_size, button_color)
|
||||
if mg.button_pressed_timer > 0 then
|
||||
circ(mg.button_x, mg.button_y, mg.button_size - 2, button_color)
|
||||
end
|
||||
Print.text_center("Z", mg.button_x - 2, mg.button_y - 3, button_color)
|
||||
end
|
||||
@@ -1,102 +1,41 @@
|
||||
function PopupWindow.set_dialog_node(node_key)
|
||||
local npc = Context.dialog.active_entity
|
||||
local node = npc.dialog[node_key]
|
||||
local POPUP_X = 40
|
||||
local POPUP_Y = 40
|
||||
local POPUP_WIDTH = 160
|
||||
local POPUP_HEIGHT = 80
|
||||
local TEXT_MARGIN_X = POPUP_X + 10
|
||||
local TEXT_MARGIN_Y = POPUP_Y + 10
|
||||
local LINE_HEIGHT = 8
|
||||
--- Displays a popup window.
|
||||
-- @param content_strings table A table of strings to display in the popup.
|
||||
function PopupWindow.show(content_strings)
|
||||
Context.popup.show = true
|
||||
Context.popup.content = content_strings or {} GameWindow.set_state("popup") end
|
||||
|
||||
if not node then
|
||||
GameWindow.set_state(WINDOW_GAME)
|
||||
return
|
||||
end
|
||||
|
||||
Context.dialog.current_node_key = node_key
|
||||
Context.dialog.text = node.text
|
||||
|
||||
local menu_items = {}
|
||||
if node.options then
|
||||
for _, option in ipairs(node.options) do
|
||||
table.insert(menu_items, {
|
||||
label = option.label,
|
||||
action = function()
|
||||
PopupWindow.set_dialog_node(option.next_node)
|
||||
end
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- if no options, it's the end of this branch.
|
||||
if #menu_items == 0 then
|
||||
table.insert(menu_items, {
|
||||
label = "Go back",
|
||||
action = function() GameWindow.set_state(WINDOW_GAME) end
|
||||
})
|
||||
end
|
||||
|
||||
Context.dialog.menu_items = menu_items
|
||||
Context.dialog.selected_menu_item = 1
|
||||
Context.dialog.showing_description = false
|
||||
GameWindow.set_state(WINDOW_POPUP)
|
||||
end
|
||||
--- Hides the popup window.
|
||||
function PopupWindow.hide()
|
||||
Context.popup.show = false
|
||||
Context.popup.content = {} GameWindow.set_state("game") end
|
||||
|
||||
--- Updates popup window logic.
|
||||
function PopupWindow.update()
|
||||
if Context.dialog.showing_description then
|
||||
if Input.menu_confirm() or Input.menu_back() then
|
||||
Context.dialog.showing_description = false
|
||||
Context.dialog.text = "" -- Clear the description text
|
||||
-- No need to change active_window, as it remains in WINDOW_POPUP or WINDOW_INVENTORY_ACTION
|
||||
end
|
||||
else
|
||||
Context.dialog.selected_menu_item = UI.update_menu(Context.dialog.menu_items, Context.dialog.selected_menu_item)
|
||||
|
||||
if Input.menu_confirm() then
|
||||
local selected_item = Context.dialog.menu_items[Context.dialog.selected_menu_item]
|
||||
if selected_item and selected_item.action then
|
||||
selected_item.action()
|
||||
end
|
||||
end
|
||||
|
||||
if Input.menu_back() then
|
||||
GameWindow.set_state(WINDOW_GAME)
|
||||
if Context.popup.show then
|
||||
if Input.menu_confirm() or Input.menu_back() then PopupWindow.hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PopupWindow.show_menu_dialog(entity, menu_items, dialog_active_window)
|
||||
Context.dialog.active_entity = entity
|
||||
Context.dialog.text = "" -- Initial dialog text is empty, name is title
|
||||
GameWindow.set_state(dialog_active_window or WINDOW_POPUP)
|
||||
Context.dialog.showing_description = false
|
||||
Context.dialog.menu_items = menu_items
|
||||
Context.dialog.selected_menu_item = 1
|
||||
end
|
||||
|
||||
function PopupWindow.show_description_dialog(entity, description_text)
|
||||
Context.dialog.active_entity = entity
|
||||
Context.dialog.text = description_text
|
||||
GameWindow.set_state(WINDOW_POPUP)
|
||||
Context.dialog.showing_description = true
|
||||
-- No menu items needed for description dialog
|
||||
end
|
||||
|
||||
--- Draws the popup window.
|
||||
function PopupWindow.draw()
|
||||
rect(40, 40, 160, 80, Config.colors.black)
|
||||
rectb(40, 40, 160, 80, Config.colors.green)
|
||||
if Context.popup.show then
|
||||
rect(POPUP_X, POPUP_Y, POPUP_WIDTH, POPUP_HEIGHT, Config.colors.black)
|
||||
rectb(POPUP_X, POPUP_Y, POPUP_WIDTH, POPUP_HEIGHT, Config.colors.green)
|
||||
|
||||
-- Display the entity's name as the dialog title
|
||||
if Context.dialog.active_entity and Context.dialog.active_entity.name then
|
||||
print(Context.dialog.active_entity.name, 120 - #Context.dialog.active_entity.name * 2, 45, Config.colors.green)
|
||||
local current_y = TEXT_MARGIN_Y
|
||||
for _, line in ipairs(Context.popup.content) do
|
||||
Print.text(line, TEXT_MARGIN_X, current_y, Config.colors.light_grey)
|
||||
current_y = current_y + LINE_HEIGHT
|
||||
end
|
||||
|
||||
-- Display the dialog content (description for "look at", or initial name/dialog for others)
|
||||
local wrapped_lines = UI.word_wrap(Context.dialog.text, 25) -- Max 25 chars per line
|
||||
local current_y = 55 -- Starting Y position for the first line of content
|
||||
for _, line in ipairs(wrapped_lines) do
|
||||
print(line, 50, current_y, Config.colors.light_grey)
|
||||
current_y = current_y + 8 -- Move to the next line (8 pixels for default font height + padding)
|
||||
end
|
||||
|
||||
-- Adjust menu position based on the number of wrapped lines
|
||||
if not Context.dialog.showing_description then
|
||||
UI.draw_menu(Context.dialog.menu_items, Context.dialog.selected_menu_item, 50, current_y + 2)
|
||||
else
|
||||
print("[A] Go Back", 50, current_y + 10, Config.colors.green)
|
||||
Print.text("[A] Close", TEXT_MARGIN_X, POPUP_Y + POPUP_HEIGHT - LINE_HEIGHT - 2, Config.colors.green)
|
||||
end
|
||||
end
|
||||
|
||||
29
inc/window/window.register.lua
Normal file
29
inc/window/window.register.lua
Normal file
@@ -0,0 +1,29 @@
|
||||
SplashWindow = {}
|
||||
Window.register("splash", SplashWindow)
|
||||
|
||||
IntroWindow = {}
|
||||
Window.register("intro", IntroWindow)
|
||||
|
||||
MenuWindow = {}
|
||||
Window.register("menu", MenuWindow)
|
||||
|
||||
GameWindow = {}
|
||||
Window.register("game", GameWindow)
|
||||
|
||||
PopupWindow = {}
|
||||
Window.register("popup", PopupWindow)
|
||||
|
||||
ConfigurationWindow = {}
|
||||
Window.register("configuration", ConfigurationWindow)
|
||||
|
||||
AudioTestWindow = {}
|
||||
Window.register("audiotest", AudioTestWindow)
|
||||
|
||||
MinigameButtonMashWindow = {}
|
||||
Window.register("minigame_button_mash", MinigameButtonMashWindow)
|
||||
|
||||
MinigameRhythmWindow = {}
|
||||
Window.register("minigame_rhythm", MinigameRhythmWindow)
|
||||
|
||||
MinigameDDRWindow = {}
|
||||
Window.register("minigame_ddr", MinigameDDRWindow)
|
||||
@@ -1,11 +1,16 @@
|
||||
--- Draws the splash window.
|
||||
function SplashWindow.draw()
|
||||
print("Mr. Anderson's", 78, 60, Config.colors.green)
|
||||
print("Addventure", 90, 70, Config.colors.green)
|
||||
local txt = "Definitely not an Impostor"
|
||||
local w = #txt * 6
|
||||
local x = (240 - w) / 2
|
||||
local y = (136 - 6) / 2
|
||||
print(txt, x, y, 12)
|
||||
end
|
||||
|
||||
--- Updates the splash window logic.
|
||||
function SplashWindow.update()
|
||||
Context.splash_timer = Context.splash_timer - 1
|
||||
if Context.splash_timer <= 0 or Input.menu_confirm() then
|
||||
GameWindow.set_state(WINDOW_INTRO)
|
||||
GameWindow.set_state("intro")
|
||||
end
|
||||
end
|
||||
|
||||
289
infra.md
Normal file
289
infra.md
Normal file
@@ -0,0 +1,289 @@
|
||||
# Server
|
||||
```mermaid
|
||||
graph TD
|
||||
Internet --> Nginx
|
||||
|
||||
Nginx --> Traefik
|
||||
Traefik --> Gitea
|
||||
Gitea --> GiteaDB[(Gitea data / SQLite)]
|
||||
|
||||
Traefik --> WoodpeckerServer
|
||||
WoodpeckerServer --> WoodpeckerDB[(Woodpecker data / SQLite)]
|
||||
WoodpeckerServer --> WoodpeckerAgent
|
||||
WoodpeckerAgent --> DockerSocket[(Docker)]
|
||||
|
||||
Traefik --> WebApp
|
||||
WebApp --> MySQL[(MySQL)]
|
||||
WebApp --> Softwares[(Volume)]
|
||||
|
||||
Droparea --> Softwares
|
||||
|
||||
Nginx --> Discourse
|
||||
Discourse --> ForumDB[(Postgres)]
|
||||
Discourse --> Redis[(Redis)]
|
||||
|
||||
Nginx --> Wiki
|
||||
Wiki --> WikiDB[(Postgres)]
|
||||
```
|
||||
|
||||
# TIC-80 Pipeline
|
||||
|
||||
This document describes the Woodpecker CI pipeline used to build, export, upload, and publish a TIC-80 game project.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The pipeline performs the following steps:
|
||||
|
||||
1. **Build** the TIC-80 project using a custom Docker image
|
||||
2. **Export** the game to `.tic` and HTML formats
|
||||
3. **Upload artifacts** to a remote server via SCP
|
||||
4. **Notify an update server** to publish the new version
|
||||
|
||||
The pipeline is driven by environment variables so it can be reused across projects.
|
||||
|
||||
---
|
||||
|
||||
## Global Environment
|
||||
|
||||
```yaml
|
||||
environment: &environment
|
||||
GAME_NAME: mranderson
|
||||
GAME_LANG: lua
|
||||
```
|
||||
|
||||
- **GAME_NAME**: Project name (used for all outputs)
|
||||
- **GAME_LANG**: Source language used by TIC-80 (Lua)
|
||||
|
||||
The anchor (`&environment`) allows reuse across steps.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Build & Export
|
||||
|
||||
```yaml
|
||||
- name: build
|
||||
image: git.teletype.hu/internal/tic80pro:latest
|
||||
environment:
|
||||
<<: *environment
|
||||
XDG_RUNTIME_DIR: /tmp
|
||||
commands:
|
||||
- make build
|
||||
- make export
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
||||
- Uses a custom TIC-80 Pro Docker image hosted in Gitea
|
||||
- Runs the Makefile `build` target to assemble source files
|
||||
- Runs the `export` target to generate:
|
||||
- `.tic` cartridge
|
||||
- `.html.zip` web build
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Artifact Upload
|
||||
|
||||
```yaml
|
||||
- name: artifact
|
||||
image: alpine
|
||||
environment:
|
||||
<<: *environment
|
||||
DROPAREA_HOST: vps.teletype.hu
|
||||
DROPAREA_PORT: 2223
|
||||
DROPAREA_TARGET_PATH: /home/drop
|
||||
DROPAREA_USER: drop
|
||||
DROPAREA_SSH_PASSWORD:
|
||||
from_secret: droparea_ssh_password
|
||||
commands:
|
||||
- apk add --no-cache openssh-client sshpass
|
||||
- mkdir -p /root/.ssh
|
||||
- sshpass -p $DROPAREA_SSH_PASSWORD scp -o StrictHostKeyChecking=no -P $DROPAREA_PORT \
|
||||
$GAME_NAME.$GAME_LANG \
|
||||
$GAME_NAME.tic \
|
||||
$GAME_NAME.html.zip \
|
||||
$DROPAREA_USER@$DROPAREA_HOST:$DROPAREA_TARGET_PATH
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
||||
- Installs SCP tooling in a minimal Alpine container
|
||||
- Uploads:
|
||||
- Source file
|
||||
- TIC-80 cartridge
|
||||
- HTML export ZIP
|
||||
- Uses secrets for SSH authentication
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Update Notification
|
||||
|
||||
```yaml
|
||||
- name: update
|
||||
image: alpine
|
||||
environment:
|
||||
<<: *environment
|
||||
UPDATE_SERVER: https://games.vps.teletype.hu
|
||||
UPDATE_SECRET:
|
||||
from_secret: update_secret_key
|
||||
commands:
|
||||
- apk add --no-cache curl
|
||||
- curl "$UPDATE_SERVER/update?secret=$UPDATE_SECRET&name=$GAME_NAME&platform=tic80"
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
||||
- Sends an HTTP request to the update server
|
||||
- Notifies that a new TIC-80 build is available
|
||||
- Uses a secret key to authorize the update
|
||||
|
||||
---
|
||||
|
||||
## Result
|
||||
|
||||
After a successful run:
|
||||
|
||||
- The game is built and exported
|
||||
- Artifacts are uploaded to the server
|
||||
- The public game index is updated automatically
|
||||
|
||||
This pipeline enables **fully automated TIC-80 releases** using open tools and infrastructure.
|
||||
|
||||
|
||||
# TIC-80 Makefile Project Builder
|
||||
|
||||
This Makefile provides a simple, reproducible workflow for building **TIC-80 Lua projects** from multiple source files. It is designed for small indie or experimental projects where the source code is split into logical parts and then merged into a single `.lua` cartridge.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The workflow is based on four core ideas:
|
||||
|
||||
- Source code is split into multiple Lua files inside an `inc/` directory
|
||||
- A project-specific `.inc` file defines the **build order**
|
||||
- All source files are concatenated into one final `.lua` file
|
||||
- TIC-80 is used in CLI mode to export runnable artifacts
|
||||
|
||||
This approach keeps the codebase modular while remaining compatible with TIC-80’s single-file cartridge model.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```text
|
||||
project-root/
|
||||
├── inc/
|
||||
│ ├── core.lua
|
||||
│ ├── player.lua
|
||||
│ └── world.lua
|
||||
├── mranderson.inc
|
||||
├── Makefile
|
||||
└── README.md
|
||||
```
|
||||
|
||||
- `inc/` contains all Lua source fragments
|
||||
- `<project>.inc` defines the order in which files are merged
|
||||
- `<project>.lua` is generated automatically
|
||||
|
||||
---
|
||||
|
||||
## The `.inc` File
|
||||
|
||||
The `.inc` file is a **plain text file** listing Lua source files in build order:
|
||||
|
||||
```text
|
||||
core.lua
|
||||
player.lua
|
||||
world.lua
|
||||
```
|
||||
|
||||
The order matters. Files listed earlier are concatenated first and must define any globals used later.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Build (default)
|
||||
|
||||
```sh
|
||||
make build
|
||||
```
|
||||
|
||||
- Reads `mranderson.inc`
|
||||
- Concatenates files from `inc/`
|
||||
- Produces `mranderson.lua`
|
||||
|
||||
### Export (TIC-80)
|
||||
|
||||
```sh
|
||||
make export
|
||||
```
|
||||
|
||||
- Loads the generated Lua file into TIC-80 (CLI mode)
|
||||
- Saves a `.tic` cartridge
|
||||
- Exports an HTML build
|
||||
|
||||
### Export Assets
|
||||
|
||||
```sh
|
||||
make export_assets
|
||||
```
|
||||
|
||||
- **Purpose**: Extracts asset sections (PALETTE, TILES, SPRITES, MAP, SFX, MUSIC) from the compiled `<project>.lua` file.
|
||||
- **Mechanism**: Uses `sed` to directly parse the generated `<project>.lua` and saves the extracted data into `inc/meta/meta.assets.lua`. This file can then be used to embed the asset data directly into other parts of the project or for version control of visual assets.
|
||||
|
||||
### Import Assets
|
||||
|
||||
The `import_assets` target was considered during development but is currently not part of the build workflow. Asset handling for TIC-80 projects within this Makefile relies solely on direct extraction (`export_assets`) from the built Lua cartridge, rather than importing external asset definitions. This target may be implemented in the future if a need for pre-build asset injection arises.
|
||||
|
||||
### Watch Mode
|
||||
|
||||
```sh
|
||||
make watch
|
||||
```
|
||||
|
||||
- Performs an initial build
|
||||
- Watches the `inc/` directory and `.inc` file
|
||||
- Rebuilds automatically on any change
|
||||
|
||||
Requires `fswatch` to be installed.
|
||||
|
||||
---
|
||||
|
||||
## Generated Artifacts
|
||||
|
||||
| File | Description |
|
||||
|-----|-------------|
|
||||
| `<project>.lua` | Merged Lua source (input for TIC-80) |
|
||||
| `<project>.tic` | TIC-80 cartridge |
|
||||
| `<project>.html` | Web export |
|
||||
| `<project>.html.zip` | Packaged HTML build |
|
||||
|
||||
---
|
||||
|
||||
## Design Goals
|
||||
|
||||
- Keep TIC-80 projects modular
|
||||
- Avoid manual copy-paste between files
|
||||
- Enable fast iteration and experimentation
|
||||
- Remain fully compatible with open-source tooling
|
||||
|
||||
This Makefile is intentionally minimal and transparent, favoring simplicity over abstraction.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- `make`
|
||||
- `tic80` available in PATH
|
||||
- `fswatch` (only for watch mode)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT License — free to use, modify, and redistribute.
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
meta/meta.header.lua
|
||||
init/init.config.lua
|
||||
init/init.windows.lua
|
||||
init/init.modules.lua
|
||||
init/init.context.lua
|
||||
entity/entity.npc.lua
|
||||
entity/entity.item.lua
|
||||
entity/entity.player.lua
|
||||
system/system.input.lua
|
||||
system/system.ui.lua
|
||||
window/window.splash.lua
|
||||
window/window.intro.lua
|
||||
window/window.menu.lua
|
||||
window/window.configuration.lua
|
||||
window/window.popup.lua
|
||||
window/window.inventory.lua
|
||||
window/window.game.lua
|
||||
system/system.main.lua
|
||||
meta/meta.assets.lua
|
||||
Reference in New Issue
Block a user