Compare commits
63 Commits
1913d7d7d4
...
feature/sp
| Author | SHA1 | Date | |
|---|---|---|---|
| 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
|
||||||
51
.luacheckrc
Normal file
51
.luacheckrc
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
-- .luacheckrc
|
||||||
|
-- Configuration for luacheck
|
||||||
|
|
||||||
|
globals = {
|
||||||
|
"Util",
|
||||||
|
"Decision",
|
||||||
|
"Situation",
|
||||||
|
"Screen",
|
||||||
|
"Sprite",
|
||||||
|
"UI",
|
||||||
|
"Print",
|
||||||
|
"Input",
|
||||||
|
"Audio",
|
||||||
|
"Context",
|
||||||
|
"Meters",
|
||||||
|
"Minigames",
|
||||||
|
"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": [
|
"Lua.diagnostics.disable": [
|
||||||
"undefined-global"
|
"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:
|
steps:
|
||||||
|
- name: version
|
||||||
|
image: alpine
|
||||||
|
commands:
|
||||||
|
- 'apk add --no-cache make'
|
||||||
|
- 'make ci-version'
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
image: gitea.vps.teletype.hu/games/tic80pro:latest
|
image: git.teletype.hu/internal/tic80pro:latest
|
||||||
environment:
|
environment:
|
||||||
<<: *environment
|
|
||||||
XDG_RUNTIME_DIR: /tmp
|
XDG_RUNTIME_DIR: /tmp
|
||||||
commands:
|
commands:
|
||||||
- make build PROJECT=$GAME_NAME
|
- 'make ci-export'
|
||||||
- make export PROJECT=$GAME_NAME
|
|
||||||
|
|
||||||
- name: artifact
|
- name: artifact
|
||||||
image: alpine
|
image: alpine
|
||||||
environment:
|
environment:
|
||||||
<<: *environment
|
|
||||||
DROPAREA_HOST: vps.teletype.hu
|
DROPAREA_HOST: vps.teletype.hu
|
||||||
DROPAREA_PORT: 2223
|
DROPAREA_PORT: 2223
|
||||||
DROPAREA_TARGET_PATH: /home/drop
|
DROPAREA_TARGET_PATH: /home/drop
|
||||||
@@ -23,17 +22,15 @@ steps:
|
|||||||
DROPAREA_SSH_PASSWORD:
|
DROPAREA_SSH_PASSWORD:
|
||||||
from_secret: droparea_ssh_password
|
from_secret: droparea_ssh_password
|
||||||
commands:
|
commands:
|
||||||
- apk add --no-cache openssh-client sshpass
|
- 'apk add --no-cache make openssh-client sshpass'
|
||||||
- mkdir -p /root/.ssh
|
- 'make ci-upload'
|
||||||
- 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
|
|
||||||
|
|
||||||
- name: update
|
- name: update
|
||||||
image: alpine
|
image: alpine
|
||||||
environment:
|
environment:
|
||||||
<<: *environment
|
|
||||||
UPDATE_SERVER: https://games.vps.teletype.hu
|
UPDATE_SERVER: https://games.vps.teletype.hu
|
||||||
UPDATE_SECRET:
|
UPDATE_SECRET:
|
||||||
from_secret: update_secret_key
|
from_secret: update_secret_key
|
||||||
commands:
|
commands:
|
||||||
- apk add --no-cache curl
|
- 'apk add --no-cache make curl'
|
||||||
- curl "$UPDATE_SERVER/update?secret=$UPDATE_SECRET&name=$GAME_NAME&platform=tic80"
|
- 'make ci-update'
|
||||||
107
GEMINI.md
107
GEMINI.md
@@ -1,6 +1,6 @@
|
|||||||
# TIC-80 Lua Code Regularities
|
# 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
|
## 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.
|
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.
|
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
|
## Code Style
|
||||||
|
|
||||||
@@ -52,3 +52,106 @@ Based on the analysis of `mranderson.lua`, the following regularities and conven
|
|||||||
## Agent Directives
|
## Agent Directives
|
||||||
|
|
||||||
- **Git Operations:** In the future, do not perform `git add` or `git commit` operations. This responsibility will be handled by the user.
|
- **Git Operations:** In the future, do not perform `git add` or `git commit` operations. This responsibility will be handled by the user.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Impostor Minigames 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** - Minigames 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
|
||||||
|
|||||||
217
Makefile
217
Makefile
@@ -1,15 +1,8 @@
|
|||||||
# -----------------------------------------
|
# -----------------------------------------
|
||||||
# Makefile – TIC-80 project builder
|
# Makefile – TIC-80 project builder
|
||||||
# Usage:
|
|
||||||
# make PROJECT=mranderson
|
|
||||||
# make build PROJECT=mranderson
|
|
||||||
# make watch PROJECT=mranderson
|
|
||||||
# make export PROJECT=mranderson
|
|
||||||
# -----------------------------------------
|
# -----------------------------------------
|
||||||
|
|
||||||
ifndef PROJECT
|
PROJECT = impostor
|
||||||
$(error Specify the project name: make PROJECT=name)
|
|
||||||
endif
|
|
||||||
|
|
||||||
ORDER = $(PROJECT).inc
|
ORDER = $(PROJECT).inc
|
||||||
OUTPUT = $(PROJECT).lua
|
OUTPUT = $(PROJECT).lua
|
||||||
@@ -17,30 +10,206 @@ OUTPUT_ZIP = $(PROJECT).html.zip
|
|||||||
OUTPUT_TIC = $(PROJECT).tic
|
OUTPUT_TIC = $(PROJECT).tic
|
||||||
|
|
||||||
SRC_DIR = inc
|
SRC_DIR = inc
|
||||||
SRC = $(shell sed 's|^|$(SRC_DIR)/|' $(ORDER))
|
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
|
all: build
|
||||||
|
|
||||||
build: $(OUTPUT)
|
build: $(OUTPUT)
|
||||||
@echo "==> Build complete: $(OUTPUT)"
|
|
||||||
|
|
||||||
$(OUTPUT): $(SRC) $(ORDER)
|
$(OUTPUT): $(SRC) $(ORDER)
|
||||||
@echo "==> Building $(OUTPUT)..."
|
|
||||||
@rm -f $(OUTPUT)
|
@rm -f $(OUTPUT)
|
||||||
@while read f; do \
|
@sed 's/\r$$//' $(ORDER) | while read f; do \
|
||||||
cat "$(SRC_DIR)/$$f" >> $(OUTPUT); \
|
cat "$(SRC_DIR)/$$f" >> $(OUTPUT); \
|
||||||
echo "\n" >> $(OUTPUT); \
|
echo "" >> $(OUTPUT); \
|
||||||
done < $(ORDER)
|
done
|
||||||
@echo "==> Done."
|
|
||||||
|
|
||||||
export: $(OUTPUT)
|
export: build
|
||||||
@echo "==> TIC-80 export..."
|
@if [ -z "$(VERSION)" ]; then \
|
||||||
tic80 --cli --skip --fs=. \
|
echo "ERROR: VERSION not set!"; \
|
||||||
--cmd="load $(OUTPUT) & save $(PROJECT) & export html $(PROJECT).html & exit"
|
exit 1; \
|
||||||
@echo "==> HTML ZIP: $(OUTPUT_ZIP)"
|
fi
|
||||||
@echo "==> TIC: $(OUTPUT_TIC)"
|
@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:
|
watch:
|
||||||
@echo "==> Watching project: $(PROJECT)"
|
make build
|
||||||
make build PROJECT=$(PROJECT)
|
fswatch -o $(SRC_DIR) $(ORDER) assets | while read; do make build; done
|
||||||
fswatch -o $(SRC_DIR) $(ORDER) | while read; do make build PROJECT=$(PROJECT); 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."
|
||||||
|
|
||||||
|
.PHONY: all build export watch import_assets export_assets clean lint ci-version ci-export ci-upload ci-update install_precommit_hook
|
||||||
|
|
||||||
|
#-- <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
|
## 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. **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.
|
2. **Launch TIC-80:** Start the TIC-80 application.
|
||||||
3. **Load the Game:**
|
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.
|
* Type `load game.lua` and press Enter.
|
||||||
* Once loaded, type `run` and press Enter to start the game.
|
* 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.
47
impostor.inc
Normal file
47
impostor.inc
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
meta/meta.header.lua
|
||||||
|
init/init.modules.lua
|
||||||
|
init/init.config.lua
|
||||||
|
init/init.minigames.lua
|
||||||
|
init/init.meters.lua
|
||||||
|
system/system.util.lua
|
||||||
|
init/init.windows.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
|
||||||
|
init/init.context.lua
|
||||||
|
data/data.songs.lua
|
||||||
|
system/system.print.lua
|
||||||
|
system/system.input.lua
|
||||||
|
system/system.audio.lua
|
||||||
|
system/system.ui.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
|
||||||
150
inc/data/data.songs.lua
Normal file
150
inc/data/data.songs.lua
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
-- 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Helper function to calculate frame from beat
|
||||||
|
-- Usage: frame_from_beat(beat_number, bpm, fps)
|
||||||
|
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
|
||||||
|
|
||||||
|
-- Helper function to convert simple beat notation to frame pattern
|
||||||
|
-- Usage: beats_to_pattern({{1, "left"}, {2, "down"}}, 120)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
]]
|
||||||
8
inc/decision/decision.go_to_home.lua
Normal file
8
inc/decision/decision.go_to_home.lua
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Decision.register({
|
||||||
|
id = "go_to_home",
|
||||||
|
label = "Go to Home",
|
||||||
|
handle = function()
|
||||||
|
Util.go_to_screen_by_id("home")
|
||||||
|
end,
|
||||||
|
condition = function() return true end
|
||||||
|
})
|
||||||
8
inc/decision/decision.go_to_office.lua
Normal file
8
inc/decision/decision.go_to_office.lua
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Decision.register({
|
||||||
|
id = "go_to_office",
|
||||||
|
label = "Go to Office",
|
||||||
|
handle = function()
|
||||||
|
Util.go_to_screen_by_id("office")
|
||||||
|
end,
|
||||||
|
condition = function() return true end
|
||||||
|
})
|
||||||
8
inc/decision/decision.go_to_toilet.lua
Normal file
8
inc/decision/decision.go_to_toilet.lua
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Decision.register({
|
||||||
|
id = "go_to_toilet",
|
||||||
|
label = "Go to Toilet",
|
||||||
|
handle = function()
|
||||||
|
Util.go_to_screen_by_id("toilet")
|
||||||
|
end,
|
||||||
|
condition = function() return true end
|
||||||
|
})
|
||||||
8
inc/decision/decision.go_to_walking_to_home.lua
Normal file
8
inc/decision/decision.go_to_walking_to_home.lua
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Decision.register({
|
||||||
|
id = "go_to_walking_to_home",
|
||||||
|
label = "Walking to home",
|
||||||
|
handle = function()
|
||||||
|
Util.go_to_screen_by_id("walking_to_home")
|
||||||
|
end,
|
||||||
|
condition = function() return true end
|
||||||
|
})
|
||||||
8
inc/decision/decision.go_to_walking_to_office.lua
Normal file
8
inc/decision/decision.go_to_walking_to_office.lua
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Decision.register({
|
||||||
|
id = "go_to_walking_to_office",
|
||||||
|
label = "Walking to office",
|
||||||
|
handle = function()
|
||||||
|
Util.go_to_screen_by_id("walking_to_office")
|
||||||
|
end,
|
||||||
|
condition = function() return true 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()
|
||||||
|
Situation.apply("drink_coffee")
|
||||||
|
end,
|
||||||
|
condition = function() return true end
|
||||||
|
})
|
||||||
31
inc/decision/decision.manager.lua
Normal file
31
inc/decision/decision.manager.lua
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
local _decisions = {}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
function Decision.get(id)
|
||||||
|
return _decisions[id]
|
||||||
|
end
|
||||||
|
|
||||||
|
function Decision.get_all()
|
||||||
|
return _decisions
|
||||||
|
end
|
||||||
6
inc/decision/decision.play_button_mash.lua
Normal file
6
inc/decision/decision.play_button_mash.lua
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Decision.register({
|
||||||
|
id = "play_button_mash",
|
||||||
|
label = "Play Button Mash",
|
||||||
|
handle = function() Meters.hide() MinigameButtonMashWindow.start(WINDOW_GAME) end,
|
||||||
|
condition = function() return true end
|
||||||
|
})
|
||||||
6
inc/decision/decision.play_ddr.lua
Normal file
6
inc/decision/decision.play_ddr.lua
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Decision.register({
|
||||||
|
id = "play_ddr",
|
||||||
|
label = "Play DDR (Random)",
|
||||||
|
handle = function() Meters.hide() MinigameDDRWindow.start(WINDOW_GAME, nil) end,
|
||||||
|
condition = function() return true end
|
||||||
|
})
|
||||||
6
inc/decision/decision.play_rhythm.lua
Normal file
6
inc/decision/decision.play_rhythm.lua
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Decision.register({
|
||||||
|
id = "play_rhythm",
|
||||||
|
label = "Play Rhythm Game",
|
||||||
|
handle = function() Meters.hide() MinigameRhythmWindow.start(WINDOW_GAME) end,
|
||||||
|
condition = function() return true 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,4 +1,4 @@
|
|||||||
local Config = {
|
local DEFAULT_CONFIG = {
|
||||||
screen = {
|
screen = {
|
||||||
width = 240,
|
width = 240,
|
||||||
height = 136
|
height = 136
|
||||||
@@ -7,26 +7,47 @@ local Config = {
|
|||||||
black = 0,
|
black = 0,
|
||||||
light_grey = 13,
|
light_grey = 13,
|
||||||
dark_grey = 14,
|
dark_grey = 14,
|
||||||
|
red = 2,
|
||||||
green = 6,
|
green = 6,
|
||||||
npc = 8,
|
blue = 9,
|
||||||
item = 12 -- yellow
|
white = 12,
|
||||||
|
item = 12,
|
||||||
|
meter_bg = 12
|
||||||
},
|
},
|
||||||
player = {
|
player = {
|
||||||
w = 8,
|
|
||||||
h = 8,
|
|
||||||
start_x = 120,
|
|
||||||
start_y = 128,
|
|
||||||
sprite_id = 1
|
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
|
|
||||||
},
|
|
||||||
timing = {
|
timing = {
|
||||||
splash_duration = 120
|
splash_duration = 120
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local Config = {
|
||||||
|
screen = DEFAULT_CONFIG.screen,
|
||||||
|
colors = DEFAULT_CONFIG.colors,
|
||||||
|
player = DEFAULT_CONFIG.player,
|
||||||
|
timing = DEFAULT_CONFIG.timing,
|
||||||
|
}
|
||||||
|
|
||||||
|
local CONFIG_SAVE_BANK = 7
|
||||||
|
local CONFIG_MAGIC_VALUE_ADDRESS = 2
|
||||||
|
local CONFIG_SPLASH_DURATION_ADDRESS = 3
|
||||||
|
local CONFIG_MAGIC_VALUE = 0xDE
|
||||||
|
|
||||||
|
function Config.save()
|
||||||
|
mset(CONFIG_MAGIC_VALUE, CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK)
|
||||||
|
end
|
||||||
|
|
||||||
|
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.restore_defaults()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Config.restore_defaults()
|
||||||
|
Config.timing.splash_duration = DEFAULT_CONFIG.timing.splash_duration
|
||||||
|
end
|
||||||
|
|
||||||
|
Config.load()
|
||||||
|
|||||||
@@ -1,432 +1,108 @@
|
|||||||
local Context = {
|
local SAVE_GAME_BANK = 6
|
||||||
active_window = WINDOW_SPLASH,
|
local SAVE_GAME_MAGIC_VALUE_ADDRESS = 0
|
||||||
inventory = {},
|
local SAVE_GAME_MAGIC_VALUE = 0xCA
|
||||||
intro = {
|
|
||||||
y = Config.screen.height,
|
local SAVE_GAME_CURRENT_SCREEN_ADDRESS = 6
|
||||||
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."
|
local function get_initial_data()
|
||||||
},
|
return {
|
||||||
current_screen = 1,
|
active_window = WINDOW_SPLASH,
|
||||||
splash_timer = Config.timing.splash_duration,
|
intro = {
|
||||||
dialog = {
|
y = Config.screen.height,
|
||||||
text = "",
|
speed = 0.5,
|
||||||
|
text = [[Norman Reds’ everyday life
|
||||||
|
seems ordinary: work,
|
||||||
|
meetings, coffee, and
|
||||||
|
endless notifications.
|
||||||
|
But beneath the surface
|
||||||
|
— within him, or around
|
||||||
|
him — something is
|
||||||
|
constantly building, and
|
||||||
|
it soon becomes clear
|
||||||
|
that there is more going
|
||||||
|
on than meets the eye.]]
|
||||||
|
},
|
||||||
|
current_screen = 1,
|
||||||
|
splash_timer = Config.timing.splash_duration,
|
||||||
|
popup = {
|
||||||
|
show = false,
|
||||||
|
content = {}
|
||||||
|
},
|
||||||
|
player = {
|
||||||
|
sprite_id = Config.player.sprite_id
|
||||||
|
},
|
||||||
|
ground = {
|
||||||
|
x = 0,
|
||||||
|
y = Config.screen.height,
|
||||||
|
w = Config.screen.width,
|
||||||
|
h = 8
|
||||||
|
},
|
||||||
menu_items = {},
|
menu_items = {},
|
||||||
selected_menu_item = 1,
|
selected_menu_item = 1,
|
||||||
active_entity = nil,
|
selected_decision_index = 1,
|
||||||
showing_description = false,
|
game_in_progress = false,
|
||||||
current_node_key = nil
|
screens = {},
|
||||||
},
|
minigame_ddr = Minigames.get_default_ddr(),
|
||||||
player = {
|
minigame_button_mash = Minigames.get_default_button_mash(),
|
||||||
x = Config.player.start_x,
|
minigame_rhythm = Minigames.get_default_rhythm(),
|
||||||
y = Config.player.start_y,
|
meters = Meters.get_initial(),
|
||||||
w = Config.player.w,
|
sprites = {},
|
||||||
h = Config.player.h,
|
current_situation = nil,
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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
|
||||||
|
|
||||||
|
Context = {}
|
||||||
|
|
||||||
|
local function reset_context_to_initial_state()
|
||||||
|
local initial_data = get_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
|
||||||
|
|
||||||
|
Context.screens = {}
|
||||||
|
Context.screen_indices_by_id = {}
|
||||||
|
local screen_order = {"home", "toilet", "walking_to_office", "office", "walking_to_home"}
|
||||||
|
for i, screen_id in ipairs(screen_order) do
|
||||||
|
local screen_data = Screen.get_by_id(screen_id)
|
||||||
|
if screen_data then
|
||||||
|
table.insert(Context.screens, screen_data)
|
||||||
|
Context.screen_indices_by_id[screen_id] = i
|
||||||
|
else
|
||||||
|
PopupWindow.show({"Error: Screen '" .. screen_id .. "' not registered!"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
reset_context_to_initial_state()
|
||||||
|
|
||||||
|
function Context.new_game()
|
||||||
|
reset_context_to_initial_state()
|
||||||
|
Context.game_in_progress = true
|
||||||
|
MenuWindow.refresh_menu_items()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Context.save_game()
|
||||||
|
if not Context.game_in_progress then return end
|
||||||
|
|
||||||
|
mset(SAVE_GAME_MAGIC_VALUE, SAVE_GAME_MAGIC_VALUE_ADDRESS, SAVE_GAME_BANK)
|
||||||
|
mset(Context.current_screen, SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Context.load_game()
|
||||||
|
if mget(SAVE_GAME_MAGIC_VALUE_ADDRESS, SAVE_GAME_BANK) ~= SAVE_GAME_MAGIC_VALUE then
|
||||||
|
Context.new_game()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
reset_context_to_initial_state()
|
||||||
|
Context.current_screen = mget(SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK)
|
||||||
|
|
||||||
|
Context.game_in_progress = true
|
||||||
|
MenuWindow.refresh_menu_items()
|
||||||
|
end
|
||||||
|
|||||||
75
inc/init/init.meters.lua
Normal file
75
inc/init/init.meters.lua
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
Meters.COLOR_ISM = Config.colors.red
|
||||||
|
Meters.COLOR_WPM = Config.colors.blue
|
||||||
|
Meters.COLOR_BM = Config.colors.black
|
||||||
|
Meters.COLOR_BG = Config.colors.meter_bg
|
||||||
|
|
||||||
|
function Meters.get_initial()
|
||||||
|
return {
|
||||||
|
ism = METER_DEFAULT,
|
||||||
|
wpm = METER_DEFAULT,
|
||||||
|
bm = METER_DEFAULT,
|
||||||
|
combo = 0,
|
||||||
|
combo_timer = 0,
|
||||||
|
hidden = false,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function Meters.hide()
|
||||||
|
if Context and Context.meters then Context.meters.hidden = true end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Meters.show()
|
||||||
|
if Context and Context.meters then Context.meters.hidden = false end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Meters.get_max()
|
||||||
|
return METER_MAX
|
||||||
|
end
|
||||||
|
|
||||||
|
function Meters.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
|
||||||
|
|
||||||
|
function Meters.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
|
||||||
|
|
||||||
|
function Meters.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
|
||||||
|
|
||||||
|
function Meters.on_minigame_complete()
|
||||||
|
local m = Context.meters
|
||||||
|
local gain = math.floor(METER_GAIN_PER_CHORE * Meters.get_combo_multiplier())
|
||||||
|
Meters.add("wpm", gain)
|
||||||
|
Meters.add("ism", gain)
|
||||||
|
Meters.add("bm", gain)
|
||||||
|
m.combo = m.combo + 1
|
||||||
|
m.combo_timer = 0
|
||||||
|
end
|
||||||
107
inc/init/init.minigames.lua
Normal file
107
inc/init/init.minigames.lua
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
Minigames = {}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
function Minigames.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
|
||||||
|
|
||||||
|
function Minigames.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
|
||||||
|
|
||||||
|
function Minigames.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
|
||||||
|
|
||||||
|
function Minigames.configure_ddr(params)
|
||||||
|
return apply_params(Minigames.get_default_ddr(), params)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Minigames.configure_button_mash(params)
|
||||||
|
return apply_params(Minigames.get_default_button_mash(), params)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Minigames.configure_rhythm(params)
|
||||||
|
return apply_params(Minigames.get_default_rhythm(), params)
|
||||||
|
end
|
||||||
@@ -3,11 +3,20 @@ local IntroWindow = {}
|
|||||||
local MenuWindow = {}
|
local MenuWindow = {}
|
||||||
local GameWindow = {}
|
local GameWindow = {}
|
||||||
local PopupWindow = {}
|
local PopupWindow = {}
|
||||||
local InventoryWindow = {}
|
|
||||||
local ConfigurationWindow = {}
|
local ConfigurationWindow = {}
|
||||||
|
local AudioTestWindow = {}
|
||||||
local UI = {}
|
local MinigameButtonMashWindow = {}
|
||||||
local Input = {}
|
local MinigameRhythmWindow = {}
|
||||||
local NPC = {}
|
local MinigameDDRWindow = {}
|
||||||
local Item = {}
|
Util = {}
|
||||||
local Player = {}
|
Meters = {}
|
||||||
|
Minigames = {}
|
||||||
|
Decision = {}
|
||||||
|
Situation = {}
|
||||||
|
Screen = {}
|
||||||
|
Map = {}
|
||||||
|
UI = {}
|
||||||
|
Print = {}
|
||||||
|
Input = {}
|
||||||
|
Sprite = {}
|
||||||
|
Audio = {}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ local WINDOW_INTRO = 1
|
|||||||
local WINDOW_MENU = 2
|
local WINDOW_MENU = 2
|
||||||
local WINDOW_GAME = 3
|
local WINDOW_GAME = 3
|
||||||
local WINDOW_POPUP = 4
|
local WINDOW_POPUP = 4
|
||||||
local WINDOW_INVENTORY = 5
|
|
||||||
local WINDOW_INVENTORY_ACTION = 6
|
|
||||||
local WINDOW_CONFIGURATION = 7
|
local WINDOW_CONFIGURATION = 7
|
||||||
|
local WINDOW_MINIGAME_BUTTON_MASH = 8
|
||||||
|
local WINDOW_MINIGAME_RHYTHM = 9
|
||||||
|
local WINDOW_MINIGAME_DDR = 10
|
||||||
|
local WINDOW_AUDIOTEST = 9001
|
||||||
|
|||||||
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,
|
||||||
|
})
|
||||||
35
inc/map/map.manager.lua
Normal file
35
inc/map/map.manager.lua
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
local _maps = {}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
function Map.get_by_id(map_id)
|
||||||
|
return _maps[map_id]
|
||||||
|
end
|
||||||
|
|
||||||
|
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>
|
--luacheck: ignore max_line_length
|
||||||
-- 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>
|
|
||||||
|
|
||||||
-- <PALETTE>
|
-- <PALETTE>
|
||||||
-- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
|
-- 000:ab53375f574f0101017f2553c3c3c71d2b53fff1e929adff83779d3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
|
||||||
-- </PALETTE>
|
-- </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
|
-- title: Definitely not an Impostor
|
||||||
-- author: Zsolt Tasnadi
|
-- name: impostor
|
||||||
|
-- author: Teletype Games
|
||||||
-- desc: Life of a programmer in the Vector
|
-- desc: Life of a programmer in the Vector
|
||||||
-- site: https://github.com/rastasi/mranderson
|
-- site: https://git.teletype.hu/games/impostor
|
||||||
-- license: MIT License
|
-- license: MIT License
|
||||||
-- version: 0.10
|
-- version: 0.1
|
||||||
-- script: lua
|
-- 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",
|
||||||
|
})
|
||||||
15
inc/screen/screen.manager.lua
Normal file
15
inc/screen/screen.manager.lua
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
local _screens = {}
|
||||||
|
|
||||||
|
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
|
||||||
|
_screens[screen_data.id] = screen_data
|
||||||
|
end
|
||||||
|
|
||||||
|
function Screen.get_by_id(screen_id)
|
||||||
|
return _screens[screen_id]
|
||||||
|
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,
|
||||||
|
})
|
||||||
37
inc/situation/situation.manager.lua
Normal file
37
inc/situation/situation.manager.lua
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
local _situations = {}
|
||||||
|
|
||||||
|
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 _situations[situation.id] then
|
||||||
|
trace("Warning: Overwriting situation with id: " .. situation.id)
|
||||||
|
end
|
||||||
|
_situations[situation.id] = situation
|
||||||
|
end
|
||||||
|
|
||||||
|
function Situation.get(id)
|
||||||
|
return _situations[id]
|
||||||
|
end
|
||||||
|
|
||||||
|
function Situation.apply(id)
|
||||||
|
local situation = Situation.get(id)
|
||||||
|
if not situation then
|
||||||
|
trace("Error: No situation found with id: " .. id)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local current_screen_obj = Screen.get_by_id(Context.current_screen)
|
||||||
|
if current_screen_obj and not current_screen_obj.situations[id] then
|
||||||
|
trace("Info: Situation " .. id .. " cannot be applied to current screen (id: " .. Context.current_screen .. ").")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Context.current_situation = id
|
||||||
|
situation.handle()
|
||||||
|
end
|
||||||
|
|
||||||
72
inc/sprite/sprite.manager.lua
Normal file
72
inc/sprite/sprite.manager.lua
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
local _sprites = {}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
function Sprite.show(id, x, y, colorkey, scale, flip_x, flip_y, rot)
|
||||||
|
-- Ensure the sprite exists before attempting to show it
|
||||||
|
if not _sprites[id] then
|
||||||
|
trace("Error: Attempted to show non-registered sprite with id: " .. id)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Context.sprites[id] = {
|
||||||
|
id = id,
|
||||||
|
x = x,
|
||||||
|
y = y,
|
||||||
|
colorkey = colorkey,
|
||||||
|
scale = scale,
|
||||||
|
flip_x = flip_x,
|
||||||
|
flip_y = flip_y,
|
||||||
|
rot = rot,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function Sprite.hide(id)
|
||||||
|
Context.sprites[id] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Sprite.draw()
|
||||||
|
for id, params in pairs(Context.sprites) do
|
||||||
|
local sprite_data = _sprites[id]
|
||||||
|
if not sprite_data then
|
||||||
|
trace("Error: Sprite id " .. id .. " in Context.sprites is not registered.")
|
||||||
|
Context.sprites[id] = nil -- Clean up invalid entry
|
||||||
|
-- We should probably continue to the next sprite instead of returning
|
||||||
|
-- so that other valid sprites can still be drawn.
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Use parameters from Context.sprites, or fall back to sprite_data, then to defaults
|
||||||
|
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 -- Complex sprite
|
||||||
|
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 -- Simple sprite
|
||||||
|
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
|
||||||
|
{ s = 0, x_offset = 0, y_offset = 0 },
|
||||||
|
-- Head
|
||||||
|
{ s = 1, x_offset = 0, y_offset = -8 },
|
||||||
|
-- Left Arm
|
||||||
|
{ s = 2, x_offset = -4, y_offset = 4 },
|
||||||
|
-- Right Arm
|
||||||
|
{ s = 3, x_offset = 4, y_offset = 4, flip_x = 1 }, -- Flipped arm
|
||||||
|
-- Left Leg
|
||||||
|
{ s = 4, x_offset = -2, y_offset = 8 },
|
||||||
|
-- Right Leg
|
||||||
|
{ s = 5, x_offset = 2, y_offset = 8, flip_x = 1 } -- Flipped leg
|
||||||
|
}
|
||||||
|
})
|
||||||
14
inc/system/system.audio.lua
Normal file
14
inc/system/system.audio.lua
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
function Audio.music_stop() music() end
|
||||||
|
function Audio.music_play_mainmenu() end
|
||||||
|
function Audio.music_play_wakingup() end
|
||||||
|
function Audio.music_play_room_morning() end
|
||||||
|
function Audio.music_play_room_street_1() end
|
||||||
|
function Audio.music_play_room_street_2() end
|
||||||
|
function Audio.music_play_room_() end
|
||||||
|
function Audio.music_play_room_work() end
|
||||||
|
|
||||||
|
function Audio.sfx_select() sfx(17, 'C-7', 30) end
|
||||||
|
function Audio.sfx_deselect() sfx(18, 'C-7', 30) end
|
||||||
|
function Audio.sfx_beep() sfx(19, 'C-6', 30) end
|
||||||
|
function Audio.sfx_success() sfx(16, 'C-7', 60) end
|
||||||
|
function Audio.sfx_bloop() sfx(21, 'C-3', 60) end
|
||||||
@@ -1,8 +1,17 @@
|
|||||||
function Input.up() return btnp(0) end
|
local INPUT_KEY_UP = 0
|
||||||
function Input.down() return btnp(1) end
|
local INPUT_KEY_DOWN = 1
|
||||||
function Input.left() return btnp(2) end
|
local INPUT_KEY_LEFT = 2
|
||||||
function Input.right() return btnp(3) end
|
local INPUT_KEY_RIGHT = 3
|
||||||
function Input.player_jump() return btnp(4) end
|
local INPUT_KEY_A = 4 local INPUT_KEY_B = 5 local INPUT_KEY_Y = 7
|
||||||
function Input.menu_confirm() return btnp(4) end
|
local INPUT_KEY_SPACE = 48
|
||||||
function Input.player_interact() return btnp(5) end -- B button
|
local INPUT_KEY_BACKSPACE = 51
|
||||||
function Input.menu_back() return btnp(5) end
|
local INPUT_KEY_ENTER = 50
|
||||||
|
|
||||||
|
function Input.up() return btnp(INPUT_KEY_UP) end
|
||||||
|
function Input.down() return btnp(INPUT_KEY_DOWN) end
|
||||||
|
function Input.left() return btnp(INPUT_KEY_LEFT) end
|
||||||
|
function Input.right() return btnp(INPUT_KEY_RIGHT) end
|
||||||
|
function Input.select() return btnp(INPUT_KEY_A) or keyp(INPUT_KEY_SPACE) end
|
||||||
|
function Input.menu_confirm() return btnp(INPUT_KEY_A) or keyp(INPUT_KEY_ENTER) end
|
||||||
|
function Input.player_interact() return btnp(INPUT_KEY_B) or keyp(INPUT_KEY_ENTER) end function Input.menu_back() return btnp(INPUT_KEY_Y) or keyp(INPUT_KEY_BACKSPACE) end
|
||||||
|
function Input.toggle_popup() return keyp(INPUT_KEY_ENTER) end
|
||||||
|
|||||||
@@ -20,25 +20,46 @@ local STATE_HANDLERS = {
|
|||||||
PopupWindow.update()
|
PopupWindow.update()
|
||||||
PopupWindow.draw()
|
PopupWindow.draw()
|
||||||
end,
|
end,
|
||||||
[WINDOW_INVENTORY] = function()
|
|
||||||
InventoryWindow.update()
|
|
||||||
InventoryWindow.draw()
|
|
||||||
end,
|
|
||||||
[WINDOW_INVENTORY_ACTION] = function()
|
|
||||||
InventoryWindow.draw()
|
|
||||||
PopupWindow.draw()
|
|
||||||
PopupWindow.update()
|
|
||||||
end,
|
|
||||||
[WINDOW_CONFIGURATION] = function()
|
[WINDOW_CONFIGURATION] = function()
|
||||||
ConfigurationWindow.update()
|
ConfigurationWindow.update()
|
||||||
ConfigurationWindow.draw()
|
ConfigurationWindow.draw()
|
||||||
end,
|
end,
|
||||||
|
[WINDOW_AUDIOTEST] = function()
|
||||||
|
AudioTestWindow.update()
|
||||||
|
AudioTestWindow.draw()
|
||||||
|
end,
|
||||||
|
[WINDOW_MINIGAME_BUTTON_MASH] = function()
|
||||||
|
MinigameButtonMashWindow.update()
|
||||||
|
MinigameButtonMashWindow.draw()
|
||||||
|
end,
|
||||||
|
[WINDOW_MINIGAME_RHYTHM] = function()
|
||||||
|
MinigameRhythmWindow.update()
|
||||||
|
MinigameRhythmWindow.draw()
|
||||||
|
end,
|
||||||
|
[WINDOW_MINIGAME_DDR] = function()
|
||||||
|
MinigameDDRWindow.update()
|
||||||
|
MinigameDDRWindow.draw()
|
||||||
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local initialized_game = false
|
||||||
|
|
||||||
|
local function init_game()
|
||||||
|
if initialized_game then return end
|
||||||
|
|
||||||
|
MenuWindow.refresh_menu_items()
|
||||||
|
initialized_game = true
|
||||||
|
end
|
||||||
|
|
||||||
function TIC()
|
function TIC()
|
||||||
|
init_game()
|
||||||
cls(Config.colors.black)
|
cls(Config.colors.black)
|
||||||
local handler = STATE_HANDLERS[Context.active_window]
|
local handler = STATE_HANDLERS[Context.active_window]
|
||||||
if handler then
|
if handler then
|
||||||
handler()
|
handler()
|
||||||
end
|
end
|
||||||
|
Meters.update()
|
||||||
|
if Context.game_in_progress then
|
||||||
|
UI.draw_meters()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
15
inc/system/system.print.lua
Normal file
15
inc/system/system.print.lua
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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,6 +1,6 @@
|
|||||||
function UI.draw_top_bar(title)
|
function UI.draw_top_bar(title)
|
||||||
rect(0, 0, Config.screen.width, 10, Config.colors.dark_grey)
|
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
|
end
|
||||||
|
|
||||||
function UI.draw_dialog()
|
function UI.draw_dialog()
|
||||||
@@ -11,19 +11,21 @@ function UI.draw_menu(items, selected_item, x, y)
|
|||||||
for i, item in ipairs(items) do
|
for i, item in ipairs(items) do
|
||||||
local current_y = y + (i-1)*10
|
local current_y = y + (i-1)*10
|
||||||
if i == selected_item then
|
if i == selected_item then
|
||||||
print(">", x - 8, current_y, Config.colors.green)
|
Print.text(">", x - 8, current_y, Config.colors.green)
|
||||||
end
|
end
|
||||||
print(item.label, x, current_y, Config.colors.green)
|
Print.text(item.label, x, current_y, Config.colors.green)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function UI.update_menu(items, selected_item)
|
function UI.update_menu(items, selected_item)
|
||||||
if Input.up() then
|
if Input.up() then
|
||||||
|
Audio.sfx_beep()
|
||||||
selected_item = selected_item - 1
|
selected_item = selected_item - 1
|
||||||
if selected_item < 1 then
|
if selected_item < 1 then
|
||||||
selected_item = #items
|
selected_item = #items
|
||||||
end
|
end
|
||||||
elseif Input.down() then
|
elseif Input.down() then
|
||||||
|
Audio.sfx_beep()
|
||||||
selected_item = selected_item + 1
|
selected_item = selected_item + 1
|
||||||
if selected_item > #items then
|
if selected_item > #items then
|
||||||
selected_item = 1
|
selected_item = 1
|
||||||
@@ -35,7 +37,6 @@ end
|
|||||||
function UI.word_wrap(text, max_chars_per_line)
|
function UI.word_wrap(text, max_chars_per_line)
|
||||||
if text == nil then return {""} end
|
if text == nil then return {""} end
|
||||||
local lines = {}
|
local lines = {}
|
||||||
|
|
||||||
for input_line in (text .. "\n"):gmatch("(.-)\n") do
|
for input_line in (text .. "\n"):gmatch("(.-)\n") do
|
||||||
local current_line = ""
|
local current_line = ""
|
||||||
local words_in_line = 0
|
local words_in_line = 0
|
||||||
@@ -50,18 +51,15 @@ function UI.word_wrap(text, max_chars_per_line)
|
|||||||
current_line = word
|
current_line = word
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if words_in_line > 0 then
|
if words_in_line > 0 then
|
||||||
table.insert(lines, current_line)
|
table.insert(lines, current_line)
|
||||||
else
|
else
|
||||||
table.insert(lines, "")
|
table.insert(lines, "")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if #lines == 0 then
|
if #lines == 0 then
|
||||||
return {""}
|
return {""}
|
||||||
end
|
end
|
||||||
|
|
||||||
return lines
|
return lines
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -77,3 +75,67 @@ function UI.create_numeric_stepper(label, value_getter, value_setter, min, max,
|
|||||||
type = "numeric_stepper"
|
type = "numeric_stepper"
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function UI.create_action_item(label, action)
|
||||||
|
return {
|
||||||
|
label = label,
|
||||||
|
action = action,
|
||||||
|
type = "action_item"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 = Meters.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 = Meters.COLOR_WPM, row = 0 },
|
||||||
|
{ key = "ism", label = "ISM", color = Meters.COLOR_ISM, row = 1 },
|
||||||
|
{ key = "bm", label = "BM", color = Meters.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, Meters.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
|
||||||
|
|
||||||
|
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
|
||||||
14
inc/system/system.util.lua
Normal file
14
inc/system/system.util.lua
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
Util = {}
|
||||||
|
|
||||||
|
function Util.safeindex(array, index)
|
||||||
|
return ((index - 1 + #array) % #array) + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function Util.go_to_screen_by_id(screen_id)
|
||||||
|
local screen_index = Context.screen_indices_by_id[screen_id]
|
||||||
|
if screen_index then
|
||||||
|
Context.current_screen = screen_index
|
||||||
|
Context.selected_decision_index = 1 else
|
||||||
|
PopupWindow.show({"Error: Screen '" .. screen_id .. "' not found or not indexed!"})
|
||||||
|
end
|
||||||
|
end
|
||||||
97
inc/window/window.audiotest.lua
Normal file
97
inc/window/window.audiotest.lua
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
AudioTestWindow = {
|
||||||
|
index_menu = 1,
|
||||||
|
index_func = 1,
|
||||||
|
list_func = {},
|
||||||
|
menuitems = {},
|
||||||
|
last_pressed = false
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
function AudioTestWindow.back()
|
||||||
|
Audio.sfx_deselect()
|
||||||
|
GameWindow.set_state(WINDOW_MENU)
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
function AudioTestWindow.draw()
|
||||||
|
UI.draw_top_bar("Audio test")
|
||||||
|
UI.draw_menu(AudioTestWindow.menuitems, AudioTestWindow.index_menu, 20, 50)
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
@@ -5,17 +5,13 @@ ConfigurationWindow = {
|
|||||||
|
|
||||||
function ConfigurationWindow.init()
|
function ConfigurationWindow.init()
|
||||||
ConfigurationWindow.controls = {
|
ConfigurationWindow.controls = {
|
||||||
UI.create_numeric_stepper(
|
UI.create_decision_item(
|
||||||
"Move Speed",
|
"Save",
|
||||||
function() return Config.physics.move_speed end,
|
function() Config.save() end
|
||||||
function(v) Config.physics.move_speed = v end,
|
|
||||||
0.5, 3, 0.1, "%.1f"
|
|
||||||
),
|
),
|
||||||
UI.create_numeric_stepper(
|
UI.create_decision_item(
|
||||||
"Max Jumps",
|
"Restore Defaults",
|
||||||
function() return Config.physics.max_jumps end,
|
function() Config.restore_defaults() end
|
||||||
function(v) Config.physics.max_jumps = v end,
|
|
||||||
1, 5, 1, "%d"
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@@ -23,46 +19,48 @@ end
|
|||||||
function ConfigurationWindow.draw()
|
function ConfigurationWindow.draw()
|
||||||
UI.draw_top_bar("Configuration")
|
UI.draw_top_bar("Configuration")
|
||||||
|
|
||||||
local x_start = 10 -- Left margin for labels
|
local x_start = 10 local y_start = 40
|
||||||
local y_start = 40
|
local x_value_right_align = Config.screen.width - 10
|
||||||
local x_value_right_align = Config.screen.width - 10 -- Right margin for values
|
local char_width = 4
|
||||||
local char_width = 4 -- Approximate character width for default font
|
|
||||||
|
|
||||||
for i, control in ipairs(ConfigurationWindow.controls) do
|
for i, control in ipairs(ConfigurationWindow.controls) do
|
||||||
local current_y = y_start + (i - 1) * 12
|
local current_y = y_start + (i - 1) * 12
|
||||||
local color = Config.colors.green
|
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)
|
||||||
|
|
||||||
local value = control.get()
|
local value_x = x_value_right_align - (#value_text * char_width)
|
||||||
local label_text = control.label
|
|
||||||
local value_text = string.format(control.format, value)
|
|
||||||
|
|
||||||
-- Calculate x position for right-aligned value
|
if i == ConfigurationWindow.selected_control then
|
||||||
local value_x = x_value_right_align - (#value_text * char_width)
|
color = Config.colors.item
|
||||||
|
Print.text("<", x_start -8, current_y, color)
|
||||||
if i == ConfigurationWindow.selected_control then
|
Print.text(label_text, x_start, current_y, color) Print.text(value_text, value_x, current_y, color)
|
||||||
color = Config.colors.item
|
Print.text(">", x_value_right_align + 4, current_y, color) else
|
||||||
print("<", x_start -8, current_y, color)
|
Print.text(label_text, x_start, current_y, color)
|
||||||
print(label_text, x_start, current_y, color) -- Shift label due to '<'
|
Print.text(value_text, value_x, current_y, color)
|
||||||
print(value_text, value_x, current_y, color)
|
end
|
||||||
print(">", x_value_right_align + 4, current_y, color) -- Print '>' after value
|
elseif control.type == "decision_item" then
|
||||||
else
|
local label_text = control.label
|
||||||
print(label_text, x_start, current_y, color)
|
if i == ConfigurationWindow.selected_control then
|
||||||
print(value_text, value_x, current_y, color)
|
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.text(label_text, x_start, current_y, color)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Print.text("Press B to go back", x_start, 120, Config.colors.light_grey)
|
||||||
print("Press B to go back", x_start, 120, Config.colors.light_grey)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function ConfigurationWindow.update()
|
function ConfigurationWindow.update()
|
||||||
if Input.menu_back() then
|
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(WINDOW_MENU)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Navigate between controls
|
|
||||||
if Input.up() then
|
if Input.up() then
|
||||||
ConfigurationWindow.selected_control = ConfigurationWindow.selected_control - 1
|
ConfigurationWindow.selected_control = ConfigurationWindow.selected_control - 1
|
||||||
if ConfigurationWindow.selected_control < 1 then
|
if ConfigurationWindow.selected_control < 1 then
|
||||||
@@ -75,16 +73,19 @@ function ConfigurationWindow.update()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Modify control value
|
|
||||||
local control = ConfigurationWindow.controls[ConfigurationWindow.selected_control]
|
local control = ConfigurationWindow.controls[ConfigurationWindow.selected_control]
|
||||||
if control then
|
if control then
|
||||||
local current_value = control.get()
|
if control.type == "numeric_stepper" then
|
||||||
if Input.left() then
|
local current_value = control.get()
|
||||||
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)
|
control.set(new_value)
|
||||||
elseif Input.right() then
|
elseif btnp(3) then local new_value = math.min(control.max, current_value + control.step)
|
||||||
local new_value = math.min(control.max, current_value + control.step)
|
control.set(new_value)
|
||||||
control.set(new_value)
|
end
|
||||||
|
elseif control.type == "decision_item" then
|
||||||
|
if Input.menu_confirm() then
|
||||||
|
control.decision()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,35 +1,69 @@
|
|||||||
function GameWindow.draw()
|
function GameWindow.draw()
|
||||||
local currentScreenData = Context.screens[Context.current_screen]
|
local screen = Context.screens[Context.current_screen]
|
||||||
|
Map.draw(screen.background)
|
||||||
UI.draw_top_bar(currentScreenData.name)
|
UI.draw_top_bar(screen.name)
|
||||||
|
if screen and screen.decisions and #screen.decisions > 0 then
|
||||||
-- Draw platforms
|
local available_decisions = {}
|
||||||
for _, p in ipairs(currentScreenData.platforms) do
|
for _, decision_id in ipairs(screen.decisions) do
|
||||||
rect(p.x, p.y, p.w, p.h, Config.colors.green)
|
local decision = Decision.get(decision_id)
|
||||||
|
if decision and decision.condition() then
|
||||||
|
table.insert(available_decisions, decision)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #available_decisions > 0 then
|
||||||
|
UI.draw_decision_selector(available_decisions, Context.selected_decision_index)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
Sprite.draw()
|
||||||
-- 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()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameWindow.update()
|
function GameWindow.update()
|
||||||
Player.update() -- Call the encapsulated player update logic
|
if Input.menu_back() then
|
||||||
|
Context.active_window = WINDOW_MENU
|
||||||
|
MenuWindow.refresh_menu_items()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if Input.up() then
|
||||||
|
Context.current_screen = Context.current_screen - 1
|
||||||
|
if Context.current_screen < 1 then
|
||||||
|
Context.current_screen = #Context.screens
|
||||||
|
end
|
||||||
|
Context.selected_decision_index = 1 elseif Input.down() then
|
||||||
|
Context.current_screen = Context.current_screen + 1
|
||||||
|
if Context.current_screen > #Context.screens then
|
||||||
|
Context.current_screen = 1
|
||||||
|
end
|
||||||
|
Context.selected_decision_index = 1 end
|
||||||
|
|
||||||
|
local screen = Context.screens[Context.current_screen]
|
||||||
|
if screen and screen.decisions and #screen.decisions > 0 then
|
||||||
|
local available_decisions = {}
|
||||||
|
for _, decision_id in ipairs(screen.decisions) do
|
||||||
|
local decision = Decision.get(decision_id)
|
||||||
|
if decision and decision.condition() then table.insert(available_decisions, decision)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #available_decisions == 0 then return end
|
||||||
|
|
||||||
|
local new_selected_decision_index = UI.update_decision_selector(
|
||||||
|
available_decisions,
|
||||||
|
Context.selected_decision_index
|
||||||
|
)
|
||||||
|
|
||||||
|
if new_selected_decision_index ~= Context.selected_decision_index then
|
||||||
|
Context.selected_decision_index = new_selected_decision_index
|
||||||
|
end
|
||||||
|
|
||||||
|
if Input.select() then
|
||||||
|
local selected_decision = available_decisions[Context.selected_decision_index]
|
||||||
|
if selected_decision and selected_decision.handle then Audio.sfx_select() selected_decision.handle()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameWindow.set_state(new_state)
|
function GameWindow.set_state(new_state)
|
||||||
Context.active_window = new_state
|
Context.active_window = new_state
|
||||||
-- Add any state-specific initialization/cleanup here later if needed
|
end
|
||||||
end
|
|
||||||
@@ -1,25 +1,21 @@
|
|||||||
function IntroWindow.draw()
|
function IntroWindow.draw()
|
||||||
local x = (Config.screen.width - 132) / 2 -- Centered text
|
local x = (Config.screen.width - 132) / 2
|
||||||
print(Context.intro.text, x, Context.intro.y, Config.colors.green)
|
Print.text(Context.intro.text, x, Context.intro.y, Config.colors.green)
|
||||||
end
|
end
|
||||||
|
|
||||||
function IntroWindow.update()
|
function IntroWindow.update()
|
||||||
Context.intro.y = Context.intro.y - Context.intro.speed
|
Context.intro.y = Context.intro.y - Context.intro.speed
|
||||||
|
|
||||||
-- Count lines in intro text to determine when scrolling is done
|
local lines = 1
|
||||||
local lines = 1
|
|
||||||
for _ in string.gmatch(Context.intro.text, "\n") do
|
for _ in string.gmatch(Context.intro.text, "\n") do
|
||||||
lines = lines + 1
|
lines = lines + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- When text is off-screen, go to menu
|
if Context.intro.y < -lines * 8 then
|
||||||
if Context.intro.y < -lines * 8 then
|
|
||||||
GameWindow.set_state(WINDOW_MENU)
|
GameWindow.set_state(WINDOW_MENU)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Skip intro by pressing A
|
if Input.menu_confirm() then
|
||||||
if Input.menu_confirm() then
|
|
||||||
GameWindow.set_state(WINDOW_MENU)
|
GameWindow.set_state(WINDOW_MENU)
|
||||||
end
|
end
|
||||||
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
|
|
||||||
@@ -8,20 +8,25 @@ function MenuWindow.update()
|
|||||||
|
|
||||||
if Input.menu_confirm() then
|
if Input.menu_confirm() then
|
||||||
local selected_item = Context.menu_items[Context.selected_menu_item]
|
local selected_item = Context.menu_items[Context.selected_menu_item]
|
||||||
if selected_item and selected_item.action then
|
if selected_item and selected_item.decision then
|
||||||
selected_item.action()
|
Audio.sfx_select()
|
||||||
|
selected_item.decision()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function MenuWindow.play()
|
function MenuWindow.new_game()
|
||||||
-- Reset player state and screen for a new game
|
Context.new_game() GameWindow.set_state(WINDOW_GAME)
|
||||||
Context.player.x = Config.player.start_x
|
end
|
||||||
Context.player.y = Config.player.start_y
|
|
||||||
Context.player.vx = 0
|
function MenuWindow.load_game()
|
||||||
Context.player.vy = 0
|
Context.load_game() GameWindow.set_state(WINDOW_GAME)
|
||||||
Context.player.jumps = 0
|
end
|
||||||
Context.current_screen = 1
|
|
||||||
|
function MenuWindow.save_game()
|
||||||
|
Context.save_game() end
|
||||||
|
|
||||||
|
function MenuWindow.resume_game()
|
||||||
GameWindow.set_state(WINDOW_GAME)
|
GameWindow.set_state(WINDOW_GAME)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -34,9 +39,22 @@ function MenuWindow.configuration()
|
|||||||
GameWindow.set_state(WINDOW_CONFIGURATION)
|
GameWindow.set_state(WINDOW_CONFIGURATION)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Initialize menu items after actions are defined
|
function MenuWindow.audio_test()
|
||||||
Context.menu_items = {
|
AudioTestWindow.init()
|
||||||
{label = "Play", action = MenuWindow.play},
|
GameWindow.set_state(WINDOW_AUDIOTEST)
|
||||||
{label = "Configuration", action = MenuWindow.configuration},
|
end
|
||||||
{label = "Exit", action = MenuWindow.exit}
|
|
||||||
}
|
function MenuWindow.refresh_menu_items()
|
||||||
|
Context.menu_items = {}
|
||||||
|
if Context.game_in_progress then
|
||||||
|
table.insert(Context.menu_items, {label = "Resume Game", decision = MenuWindow.resume_game})
|
||||||
|
table.insert(Context.menu_items, {label = "Save Game", decision = MenuWindow.save_game})
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(Context.menu_items, {label = "New Game", decision = MenuWindow.new_game})
|
||||||
|
table.insert(Context.menu_items, {label = "Load Game", decision = MenuWindow.load_game})
|
||||||
|
table.insert(Context.menu_items, {label = "Configuration", decision = MenuWindow.configuration})
|
||||||
|
table.insert(Context.menu_items, {label = "Audio Test", decision = MenuWindow.audio_test})
|
||||||
|
table.insert(Context.menu_items, {label = "Exit", decision = MenuWindow.exit})
|
||||||
|
|
||||||
|
Context.selected_menu_item = 1 end
|
||||||
|
|||||||
235
inc/window/window.minigame.ddr.lua
Normal file
235
inc/window/window.minigame.ddr.lua
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
function MinigameDDRWindow.init(params)
|
||||||
|
Context.minigame_ddr = Minigames.configure_ddr(params)
|
||||||
|
end
|
||||||
|
|
||||||
|
function MinigameDDRWindow.start(return_window, song_key, params)
|
||||||
|
MinigameDDRWindow.init(params)
|
||||||
|
Context.minigame_ddr.return_window = return_window or WINDOW_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.active_window = WINDOW_MINIGAME_DDR
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
local function check_miss(arrow)
|
||||||
|
local mg = Context.minigame_ddr
|
||||||
|
return arrow.y > mg.target_y + mg.hit_threshold
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
function MinigameDDRWindow.update()
|
||||||
|
local mg = Context.minigame_ddr
|
||||||
|
if mg.bar_fill >= mg.max_fill then
|
||||||
|
Meters.on_minigame_complete()
|
||||||
|
Meters.show()
|
||||||
|
Context.active_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
|
||||||
|
Meters.on_minigame_complete()
|
||||||
|
Meters.show()
|
||||||
|
Context.active_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
|
||||||
|
|
||||||
|
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.active_window = WINDOW_GAME
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if mg.return_window == 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
|
||||||
66
inc/window/window.minigame.mash.lua
Normal file
66
inc/window/window.minigame.mash.lua
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
function MinigameButtonMashWindow.init(params)
|
||||||
|
Context.minigame_button_mash = Minigames.configure_button_mash(params)
|
||||||
|
end
|
||||||
|
|
||||||
|
function MinigameButtonMashWindow.start(return_window, params)
|
||||||
|
MinigameButtonMashWindow.init(params)
|
||||||
|
Context.minigame_button_mash.return_window = return_window or WINDOW_GAME
|
||||||
|
Context.active_window = WINDOW_MINIGAME_BUTTON_MASH
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
Meters.on_minigame_complete()
|
||||||
|
Meters.show()
|
||||||
|
Context.active_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
|
||||||
|
|
||||||
|
function MinigameButtonMashWindow.draw()
|
||||||
|
local mg = Context.minigame_button_mash
|
||||||
|
if mg.return_window == 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
|
||||||
85
inc/window/window.minigame.rhythm.lua
Normal file
85
inc/window/window.minigame.rhythm.lua
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
function MinigameRhythmWindow.init(params)
|
||||||
|
Context.minigame_rhythm = Minigames.configure_rhythm(params)
|
||||||
|
end
|
||||||
|
|
||||||
|
function MinigameRhythmWindow.start(return_window, params)
|
||||||
|
MinigameRhythmWindow.init(params)
|
||||||
|
Context.minigame_rhythm.return_window = return_window or WINDOW_GAME
|
||||||
|
Context.active_window = WINDOW_MINIGAME_RHYTHM
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
Meters.on_minigame_complete()
|
||||||
|
Meters.show()
|
||||||
|
Context.active_window = mg.return_window
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if mg.button_pressed_timer > 0 then
|
||||||
|
mg.button_pressed_timer = mg.button_pressed_timer - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MinigameRhythmWindow.draw()
|
||||||
|
local mg = Context.minigame_rhythm
|
||||||
|
if mg.return_window == 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,36 @@
|
|||||||
function PopupWindow.set_dialog_node(node_key)
|
local POPUP_X = 40
|
||||||
local npc = Context.dialog.active_entity
|
local POPUP_Y = 40
|
||||||
local node = npc.dialog[node_key]
|
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
|
||||||
|
function PopupWindow.show(content_strings)
|
||||||
|
Context.popup.show = true
|
||||||
|
Context.popup.content = content_strings or {} GameWindow.set_state(WINDOW_POPUP) end
|
||||||
|
|
||||||
if not node then
|
function PopupWindow.hide()
|
||||||
GameWindow.set_state(WINDOW_GAME)
|
Context.popup.show = false
|
||||||
return
|
Context.popup.content = {} GameWindow.set_state(WINDOW_GAME) end
|
||||||
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
|
|
||||||
|
|
||||||
function PopupWindow.update()
|
function PopupWindow.update()
|
||||||
if Context.dialog.showing_description then
|
if Context.popup.show then
|
||||||
if Input.menu_confirm() or Input.menu_back() then
|
if Input.menu_confirm() or Input.menu_back() then PopupWindow.hide()
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
end
|
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
|
|
||||||
|
|
||||||
function PopupWindow.draw()
|
function PopupWindow.draw()
|
||||||
rect(40, 40, 160, 80, Config.colors.black)
|
if Context.popup.show then
|
||||||
rectb(40, 40, 160, 80, Config.colors.green)
|
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
|
local current_y = TEXT_MARGIN_Y
|
||||||
if Context.dialog.active_entity and Context.dialog.active_entity.name then
|
for _, line in ipairs(Context.popup.content) do
|
||||||
print(Context.dialog.active_entity.name, 120 - #Context.dialog.active_entity.name * 2, 45, Config.colors.green)
|
Print.text(line, TEXT_MARGIN_X, current_y, Config.colors.light_grey)
|
||||||
end
|
current_y = current_y + LINE_HEIGHT
|
||||||
|
end
|
||||||
|
|
||||||
-- Display the dialog content (description for "look at", or initial name/dialog for others)
|
Print.text("[A] Close", TEXT_MARGIN_X, POPUP_Y + POPUP_HEIGHT - LINE_HEIGHT - 2, Config.colors.green)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
function SplashWindow.draw()
|
function SplashWindow.draw()
|
||||||
print("Mr. Anderson's", 78, 60, Config.colors.green)
|
local txt = "Definitely not an Impostor"
|
||||||
print("Addventure", 90, 70, Config.colors.green)
|
local w = #txt * 6
|
||||||
|
local x = (240 - w) / 2
|
||||||
|
local y = (136 - 6) / 2
|
||||||
|
print(txt, x, y, 12)
|
||||||
end
|
end
|
||||||
|
|
||||||
function SplashWindow.update()
|
function SplashWindow.update()
|
||||||
|
|||||||
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