Compare commits
4 Commits
0d030949d0
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 85ce2e0238 | |||
| 9460a2eb73 | |||
| ebf7799674 | |||
| ac0bcf4aa4 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1 @@
|
|||||||
.local
|
mranderson.lua
|
||||||
impostor.lua
|
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
environment: &environment
|
|
||||||
GAME_NAME: impostor
|
|
||||||
GAME_LANG: lua
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: version
|
||||||
|
image: alpine
|
||||||
|
commands:
|
||||||
|
- 'apk add --no-cache make'
|
||||||
|
- 'make ci-version'
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
image: git.teletype.hu/internal/tic80pro:latest
|
image: git.teletype.hu/internal/tic80pro:latest
|
||||||
environment:
|
environment:
|
||||||
<<: *environment
|
|
||||||
XDG_RUNTIME_DIR: /tmp
|
XDG_RUNTIME_DIR: /tmp
|
||||||
commands:
|
commands:
|
||||||
- apk add --no-cache zip
|
- 'make ci-export'
|
||||||
- make build
|
|
||||||
- make export
|
|
||||||
- VERSION=$(sed -n 's/^-- version: //p' ${GAME_NAME}.${GAME_LANG} | head -n 1)
|
|
||||||
- zip -r ${GAME_NAME}.html.zip "$VERSION"
|
|
||||||
- echo $VERSION > .version
|
|
||||||
|
|
||||||
- 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
|
||||||
@@ -27,20 +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'
|
||||||
- VERSION=$(cat .version)
|
|
||||||
- sshpass -p $DROPAREA_SSH_PASSWORD ssh -p $DROPAREA_PORT $DROPAREA_USER@$DROPAREA_HOST "mkdir -p $DROPAREA_TARGET_PATH/$GAME_NAME/$VERSION"
|
|
||||||
- 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/$GAME_NAME/$VERSION/
|
|
||||||
|
|
||||||
- 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'
|
||||||
- VERSION=$(cat .version)
|
- 'make ci-update'
|
||||||
- curl "$UPDATE_SERVER/update?secret=$UPDATE_SECRET&name=$GAME_NAME&platform=tic80&version=$VERSION"
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# TIC-80 Lua Code Regularities
|
# TIC-80 Lua Code Regularities
|
||||||
|
|
||||||
Based on the analysis of `impostor.lua`, the following regularities and conventions should be followed for future modifications and development within this project:
|
Based on the analysis of `mranderson.lua`, the following regularities and conventions should be followed for future modifications and development within this project:
|
||||||
|
|
||||||
## General Structure & Lifecycle
|
## General Structure & Lifecycle
|
||||||
|
|
||||||
|
|||||||
80
Makefile
80
Makefile
@@ -2,7 +2,7 @@
|
|||||||
# Makefile – TIC-80 project builder
|
# Makefile – TIC-80 project builder
|
||||||
# -----------------------------------------
|
# -----------------------------------------
|
||||||
|
|
||||||
PROJECT = impostor
|
PROJECT = mranderson
|
||||||
|
|
||||||
ORDER = $(PROJECT).inc
|
ORDER = $(PROJECT).inc
|
||||||
OUTPUT = $(PROJECT).lua
|
OUTPUT = $(PROJECT).lua
|
||||||
@@ -14,6 +14,15 @@ SRC = $(shell sed 's|^|$(SRC_DIR)/|' $(ORDER))
|
|||||||
|
|
||||||
ASSETS_LUA = inc/meta/meta.assets.lua
|
ASSETS_LUA = inc/meta/meta.assets.lua
|
||||||
|
|
||||||
|
# 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)
|
||||||
@@ -25,6 +34,28 @@ $(OUTPUT): $(SRC) $(ORDER)
|
|||||||
echo "" >> $(OUTPUT); \
|
echo "" >> $(OUTPUT); \
|
||||||
done < $(ORDER)
|
done < $(ORDER)
|
||||||
|
|
||||||
|
export: build
|
||||||
|
@if [ -z "$(VERSION)" ]; then \
|
||||||
|
echo "ERROR: VERSION not set!"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@echo "==> Exporting HTML for version $(VERSION)"
|
||||||
|
@tic80 --cli --skip --fs=. \
|
||||||
|
--cmd="load $(OUTPUT) & save $(PROJECT)-$(VERSION) & export html $(PROJECT)-$(VERSION).html & exit"
|
||||||
|
@echo "==> Creating versioned files"
|
||||||
|
@if [ -f "$(PROJECT)-$(VERSION).tic" ]; then \
|
||||||
|
cp $(PROJECT)-$(VERSION).tic $(PROJECT).tic; \
|
||||||
|
fi
|
||||||
|
@if [ -f "$(PROJECT)-$(VERSION).html.zip" ]; then \
|
||||||
|
cp $(PROJECT)-$(VERSION).html.zip $(PROJECT).html.zip; \
|
||||||
|
fi
|
||||||
|
@echo "==> Generated files:"
|
||||||
|
@ls -lh $(PROJECT)-$(VERSION).* $(PROJECT).tic $(PROJECT).html.zip 2>/dev/null || true
|
||||||
|
|
||||||
|
watch:
|
||||||
|
make build
|
||||||
|
fswatch -o $(SRC_DIR) $(ORDER) assets | while read; do make build; done
|
||||||
|
|
||||||
import_assets:
|
import_assets:
|
||||||
@for t in $(ASSET_TYPES); do \
|
@for t in $(ASSET_TYPES); do \
|
||||||
for f in $(ASSETS_DIR)/$$t/*.png; do \
|
for f in $(ASSETS_DIR)/$$t/*.png; do \
|
||||||
@@ -45,15 +76,40 @@ export_assets: build
|
|||||||
/^-- <MUSIC>/,/^-- <\/MUSIC>/p' \
|
/^-- <MUSIC>/,/^-- <\/MUSIC>/p' \
|
||||||
$(OUTPUT) > $(ASSETS_LUA)
|
$(OUTPUT) > $(ASSETS_LUA)
|
||||||
|
|
||||||
export: build
|
clean:
|
||||||
@{ \
|
@rm -f $(PROJECT)-*.tic $(PROJECT)-*.html.zip $(OUTPUT)
|
||||||
VERSION=$$(sed -n 's/^-- version: //p' $(OUTPUT) | head -n 1); \
|
@echo "==> Cleaned build artifacts"
|
||||||
echo "==> Exporting HTML for version $$VERSION"; \
|
|
||||||
mkdir -p "$$VERSION"; \
|
# CI/CD Targets
|
||||||
tic80 --cli --skip --fs=. \
|
ci-version:
|
||||||
--cmd="load $(OUTPUT) & save $(PROJECT) & export html $$VERSION/$(PROJECT).html & exit"; \
|
@VERSION=$$(sed -n "s/^-- version: //p" inc/meta/meta.header.lua | head -n 1 | tr -d "[:space:]"); \
|
||||||
}
|
BRANCH=$${CI_COMMIT_BRANCH:-$${WOODPECKER_BRANCH}}; \
|
||||||
|
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"
|
||||||
|
|
||||||
|
.PHONY: all build export watch import_assets export_assets clean ci-version ci-export ci-upload ci-update
|
||||||
|
|
||||||
watch:
|
|
||||||
make build
|
|
||||||
fswatch -o $(SRC_DIR) $(ORDER) assets | while read; do make build; done
|
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -1,4 +1,4 @@
|
|||||||
# Definitely not an Impostor
|
# Mr. Anderson's Matrix Escape
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -8,6 +8,14 @@ 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 impostor`).
|
* Navigate to the directory where `game.lua` is located using the TIC-80 command line (`cd mranderson`).
|
||||||
* 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?
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,49 @@
|
|||||||
function Item.use()
|
function Item.use()
|
||||||
Print.text("Used item: " .. Context.dialog.active_entity.name)
|
Print.text("Used item: " .. Context.dialog.active_entity.name)
|
||||||
|
GameWindow.set_state(WINDOW_INVENTORY)
|
||||||
end
|
end
|
||||||
function Item.look_at()
|
function Item.look_at()
|
||||||
PopupWindow.show_description_dialog(Context.dialog.active_entity, Context.dialog.active_entity.desc)
|
PopupWindow.show_description_dialog(Context.dialog.active_entity, Context.dialog.active_entity.desc)
|
||||||
end
|
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
|
||||||
|
|||||||
98
inc/entity/entity.player.lua
Normal file
98
inc/entity/entity.player.lua
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
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
|
||||||
@@ -12,8 +12,20 @@ local DEFAULT_CONFIG = {
|
|||||||
item = 12 -- yellow
|
item = 12 -- yellow
|
||||||
},
|
},
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -24,24 +36,36 @@ local Config = {
|
|||||||
screen = DEFAULT_CONFIG.screen,
|
screen = DEFAULT_CONFIG.screen,
|
||||||
colors = DEFAULT_CONFIG.colors,
|
colors = DEFAULT_CONFIG.colors,
|
||||||
player = DEFAULT_CONFIG.player,
|
player = DEFAULT_CONFIG.player,
|
||||||
|
physics = DEFAULT_CONFIG.physics,
|
||||||
timing = DEFAULT_CONFIG.timing,
|
timing = DEFAULT_CONFIG.timing,
|
||||||
}
|
}
|
||||||
|
|
||||||
local CONFIG_SAVE_BANK = 7
|
local CONFIG_SAVE_BANK = 7
|
||||||
|
local CONFIG_SAVE_ADDRESS_MOVE_SPEED = 0
|
||||||
|
local CONFIG_SAVE_ADDRESS_MAX_JUMPS = 1
|
||||||
local CONFIG_MAGIC_VALUE_ADDRESS = 2
|
local CONFIG_MAGIC_VALUE_ADDRESS = 2
|
||||||
local CONFIG_MAGIC_VALUE = 0xDE -- A magic number to check if config is saved
|
local CONFIG_MAGIC_VALUE = 0xDE -- A magic number to check if config is saved
|
||||||
|
|
||||||
function Config.save()
|
function Config.save()
|
||||||
-- Save physics settings
|
-- Save physics settings
|
||||||
|
mset(Config.physics.move_speed * 10, CONFIG_SAVE_ADDRESS_MOVE_SPEED, CONFIG_SAVE_BANK)
|
||||||
|
mset(Config.physics.max_jumps, CONFIG_SAVE_ADDRESS_MAX_JUMPS, CONFIG_SAVE_BANK)
|
||||||
mset(CONFIG_MAGIC_VALUE, CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK) -- Mark as saved
|
mset(CONFIG_MAGIC_VALUE, CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK) -- Mark as saved
|
||||||
end
|
end
|
||||||
|
|
||||||
function Config.load()
|
function Config.load()
|
||||||
Config.restore_defaults()
|
|
||||||
-- Check if config has been saved before using a magic value
|
-- Check if config has been saved before using a magic value
|
||||||
|
if mget(CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK) == CONFIG_MAGIC_VALUE then
|
||||||
|
Config.physics.move_speed = mget(CONFIG_SAVE_ADDRESS_MOVE_SPEED, CONFIG_SAVE_BANK) / 10
|
||||||
|
Config.physics.max_jumps = mget(CONFIG_SAVE_ADDRESS_MAX_JUMPS, CONFIG_SAVE_BANK)
|
||||||
|
else
|
||||||
|
Config.restore_defaults()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Config.restore_defaults()
|
function Config.restore_defaults()
|
||||||
|
Config.physics.move_speed = DEFAULT_CONFIG.physics.move_speed
|
||||||
|
Config.physics.max_jumps = DEFAULT_CONFIG.physics.max_jumps
|
||||||
-- Any other configurable items should be reset here
|
-- Any other configurable items should be reset here
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ local SAVE_GAME_PLAYER_X_ADDRESS = 1
|
|||||||
local SAVE_GAME_PLAYER_Y_ADDRESS = 2
|
local SAVE_GAME_PLAYER_Y_ADDRESS = 2
|
||||||
local SAVE_GAME_PLAYER_VX_ADDRESS = 3
|
local SAVE_GAME_PLAYER_VX_ADDRESS = 3
|
||||||
local SAVE_GAME_PLAYER_VY_ADDRESS = 4
|
local SAVE_GAME_PLAYER_VY_ADDRESS = 4
|
||||||
local SAVE_GAME_selectS_ADDRESS = 5
|
local SAVE_GAME_PLAYER_JUMPS_ADDRESS = 5
|
||||||
local SAVE_GAME_CURRENT_SCREEN_ADDRESS = 6
|
local SAVE_GAME_CURRENT_SCREEN_ADDRESS = 6
|
||||||
|
|
||||||
local VX_VY_OFFSET = 128 -- Offset for negative velocities
|
local VX_VY_OFFSET = 128 -- Offset for negative velocities
|
||||||
@@ -28,10 +28,11 @@ end
|
|||||||
local function get_initial_data()
|
local function get_initial_data()
|
||||||
return {
|
return {
|
||||||
active_window = WINDOW_SPLASH,
|
active_window = WINDOW_SPLASH,
|
||||||
|
inventory = {},
|
||||||
intro = {
|
intro = {
|
||||||
y = Config.screen.height,
|
y = Config.screen.height,
|
||||||
speed = 0.5,
|
speed = 0.5,
|
||||||
text = "Norman Reds’ everyday life\nseems ordinary: work,\nmeetings, coffee, and\nendless notifications.\nBut beneath the surface\n— within him, or around\nhim — something is\nconstantly building, and\nit soon becomes clear\nthat there is more going\non than meets the eye."
|
text = "Mr. Anderson is an average\nprogrammer. His daily life\nrevolves around debugging,\npull requests, and end-of-sprint\nmeetings, all while secretly\ndreaming of being destined\nfor something more."
|
||||||
},
|
},
|
||||||
current_screen = 1,
|
current_screen = 1,
|
||||||
splash_timer = Config.timing.splash_duration,
|
splash_timer = Config.timing.splash_duration,
|
||||||
@@ -44,6 +45,13 @@ local function get_initial_data()
|
|||||||
current_node_key = nil
|
current_node_key = nil
|
||||||
},
|
},
|
||||||
player = {
|
player = {
|
||||||
|
x = Config.player.start_x,
|
||||||
|
y = Config.player.start_y,
|
||||||
|
w = Config.player.w,
|
||||||
|
h = Config.player.h,
|
||||||
|
vx = 0,
|
||||||
|
vy = 0,
|
||||||
|
jumps = 0,
|
||||||
sprite_id = Config.player.sprite_id
|
sprite_id = Config.player.sprite_id
|
||||||
},
|
},
|
||||||
ground = {
|
ground = {
|
||||||
@@ -54,13 +62,30 @@ local function get_initial_data()
|
|||||||
},
|
},
|
||||||
menu_items = {},
|
menu_items = {},
|
||||||
selected_menu_item = 1,
|
selected_menu_item = 1,
|
||||||
|
selected_inventory_item = 1,
|
||||||
game_in_progress = false, -- New flag
|
game_in_progress = false, -- New flag
|
||||||
screens = clone_table({
|
screens = clone_table({
|
||||||
{
|
{
|
||||||
-- Screen 1
|
-- Screen 1
|
||||||
name = "Screen 1",
|
name = "Screen 1",
|
||||||
|
platforms = {
|
||||||
|
{
|
||||||
|
x = 80,
|
||||||
|
y = 110,
|
||||||
|
w = 40,
|
||||||
|
h = 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x = 160,
|
||||||
|
y = 90,
|
||||||
|
w = 40,
|
||||||
|
h = 8
|
||||||
|
}
|
||||||
|
},
|
||||||
npcs = {
|
npcs = {
|
||||||
{
|
{
|
||||||
|
x = 180,
|
||||||
|
y = 82,
|
||||||
name = "Trinity",
|
name = "Trinity",
|
||||||
sprite_id = 2,
|
sprite_id = 2,
|
||||||
dialog = {
|
dialog = {
|
||||||
@@ -116,6 +141,8 @@ local function get_initial_data()
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
x = 90,
|
||||||
|
y = 102,
|
||||||
name = "Oracle",
|
name = "Oracle",
|
||||||
sprite_id = 3,
|
sprite_id = 3,
|
||||||
dialog = {
|
dialog = {
|
||||||
@@ -175,6 +202,10 @@ local function get_initial_data()
|
|||||||
},
|
},
|
||||||
items = {
|
items = {
|
||||||
{
|
{
|
||||||
|
x = 100,
|
||||||
|
y = 128,
|
||||||
|
w = 8,
|
||||||
|
h = 8,
|
||||||
name = "Key",
|
name = "Key",
|
||||||
sprite_id = 4,
|
sprite_id = 4,
|
||||||
desc = "A rusty old key. It might open something."
|
desc = "A rusty old key. It might open something."
|
||||||
@@ -184,8 +215,30 @@ local function get_initial_data()
|
|||||||
{
|
{
|
||||||
-- Screen 2
|
-- Screen 2
|
||||||
name = "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 = {
|
npcs = {
|
||||||
{
|
{
|
||||||
|
x = 120,
|
||||||
|
y = 72,
|
||||||
name = "Morpheus",
|
name = "Morpheus",
|
||||||
sprite_id = 5,
|
sprite_id = 5,
|
||||||
dialog = {
|
dialog = {
|
||||||
@@ -221,6 +274,8 @@ local function get_initial_data()
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
x = 40,
|
||||||
|
y = 92,
|
||||||
name = "Tank",
|
name = "Tank",
|
||||||
sprite_id = 6,
|
sprite_id = 6,
|
||||||
dialog = {
|
dialog = {
|
||||||
@@ -265,6 +320,10 @@ local function get_initial_data()
|
|||||||
},
|
},
|
||||||
items = {
|
items = {
|
||||||
{
|
{
|
||||||
|
x = 180,
|
||||||
|
y = 52,
|
||||||
|
w = 8,
|
||||||
|
h = 8,
|
||||||
name = "Potion",
|
name = "Potion",
|
||||||
sprite_id = 7,
|
sprite_id = 7,
|
||||||
desc = "A glowing red potion. It looks potent."
|
desc = "A glowing red potion. It looks potent."
|
||||||
@@ -274,8 +333,36 @@ local function get_initial_data()
|
|||||||
{
|
{
|
||||||
-- Screen 3
|
-- Screen 3
|
||||||
name = "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 = {
|
npcs = {
|
||||||
{
|
{
|
||||||
|
x = 210,
|
||||||
|
y = 42,
|
||||||
name = "Agent Smith",
|
name = "Agent Smith",
|
||||||
sprite_id = 8,
|
sprite_id = 8,
|
||||||
dialog = {
|
dialog = {
|
||||||
@@ -317,6 +404,8 @@ local function get_initial_data()
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
x = 160,
|
||||||
|
y = 62,
|
||||||
name = "Cypher",
|
name = "Cypher",
|
||||||
sprite_id = 9,
|
sprite_id = 9,
|
||||||
dialog = {
|
dialog = {
|
||||||
@@ -403,6 +492,11 @@ function Context.save_game()
|
|||||||
if not Context.game_in_progress then return end
|
if not Context.game_in_progress then return end
|
||||||
|
|
||||||
mset(SAVE_GAME_MAGIC_VALUE, SAVE_GAME_MAGIC_VALUE_ADDRESS, SAVE_GAME_BANK)
|
mset(SAVE_GAME_MAGIC_VALUE, SAVE_GAME_MAGIC_VALUE_ADDRESS, SAVE_GAME_BANK)
|
||||||
|
mset(Context.player.x * 10, SAVE_GAME_PLAYER_X_ADDRESS, SAVE_GAME_BANK)
|
||||||
|
mset(Context.player.y * 10, SAVE_GAME_PLAYER_Y_ADDRESS, SAVE_GAME_BANK)
|
||||||
|
mset( (Context.player.vx * 100) + VX_VY_OFFSET, SAVE_GAME_PLAYER_VX_ADDRESS, SAVE_GAME_BANK)
|
||||||
|
mset( (Context.player.vy * 100) + VX_VY_OFFSET, SAVE_GAME_PLAYER_VY_ADDRESS, SAVE_GAME_BANK)
|
||||||
|
mset(Context.player.jumps, SAVE_GAME_PLAYER_JUMPS_ADDRESS, SAVE_GAME_BANK)
|
||||||
mset(Context.current_screen, SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK)
|
mset(Context.current_screen, SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -415,6 +509,11 @@ function Context.load_game()
|
|||||||
|
|
||||||
reset_context_to_initial_state() -- Reset data, preserve methods
|
reset_context_to_initial_state() -- Reset data, preserve methods
|
||||||
|
|
||||||
|
Context.player.x = mget(SAVE_GAME_PLAYER_X_ADDRESS, SAVE_GAME_BANK) / 10
|
||||||
|
Context.player.y = mget(SAVE_GAME_PLAYER_Y_ADDRESS, SAVE_GAME_BANK) / 10
|
||||||
|
Context.player.vx = (mget(SAVE_GAME_PLAYER_VX_ADDRESS, SAVE_GAME_BANK) - VX_VY_OFFSET) / 100
|
||||||
|
Context.player.vy = (mget(SAVE_GAME_PLAYER_VY_ADDRESS, SAVE_GAME_BANK) - VX_VY_OFFSET) / 100
|
||||||
|
Context.player.jumps = mget(SAVE_GAME_PLAYER_JUMPS_ADDRESS, SAVE_GAME_BANK)
|
||||||
Context.current_screen = mget(SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK)
|
Context.current_screen = mget(SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK)
|
||||||
|
|
||||||
Context.game_in_progress = true
|
Context.game_in_progress = true
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ local IntroWindow = {}
|
|||||||
local MenuWindow = {}
|
local MenuWindow = {}
|
||||||
local GameWindow = {}
|
local GameWindow = {}
|
||||||
local PopupWindow = {}
|
local PopupWindow = {}
|
||||||
|
local InventoryWindow = {}
|
||||||
local ConfigurationWindow = {}
|
local ConfigurationWindow = {}
|
||||||
|
|
||||||
local UI = {}
|
local UI = {}
|
||||||
|
|||||||
@@ -3,4 +3,6 @@ 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
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
MapBedroom = {
|
|
||||||
"10101010101010101010101010101010",
|
|
||||||
"10141410101010101010101010101010",
|
|
||||||
"10141410101010101010101010101010",
|
|
||||||
"10101010101010101010101010101010",
|
|
||||||
"10101010101010101010101010101010",
|
|
||||||
"10101010101010101010101010101010",
|
|
||||||
"10101010101010101010101010101010",
|
|
||||||
"10101010101010101010101010101010",
|
|
||||||
"10101010101010101010101010101010",
|
|
||||||
"11111111111111111111111111111111",
|
|
||||||
"11111111111111111111111111111111",
|
|
||||||
"11111111111111111111111111111111",
|
|
||||||
"11111516111213111111111111111111",
|
|
||||||
"11111111111111111111111111111111",
|
|
||||||
"11111111111111111111111111111111",
|
|
||||||
"11111111111111111111111111111111",
|
|
||||||
"11111111111111111111111111111111"
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,29 @@
|
|||||||
|
-- <TILES>
|
||||||
|
-- 000:4444444444444444444444444444444444444444444444444444444444444444
|
||||||
|
-- 001:1111111111111111111111111111111111111111111111111111111111111111
|
||||||
|
-- 002:5555555555555555555555555555555555555555555555555555555555555555
|
||||||
|
-- 003:6666666666666666666666666666666666666666666666666666666666666666
|
||||||
|
-- 004:7777777777777777777777777777777777777777777777777777777777777777
|
||||||
|
-- 005:8888888888888888888888888888888888888888888888888888888888888888
|
||||||
|
-- 006:9999999999999999999999999999999999999999999999999999999999999999
|
||||||
|
-- 007:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
-- 008:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||||
|
-- </TILES>
|
||||||
|
|
||||||
|
-- <WAVES>
|
||||||
|
-- 000:00000000ffffffff00000000ffffffff
|
||||||
|
-- 001:0123456789abcdeffedcba9876543210
|
||||||
|
-- 02:0123456789abcdef0123456789abcdef
|
||||||
|
-- </WAVES>
|
||||||
|
|
||||||
|
-- <SFX>
|
||||||
|
-- 000:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000304000000000
|
||||||
|
-- </SFX>
|
||||||
|
|
||||||
|
-- <TRACKS>
|
||||||
|
-- 000:100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
-- </TRACKS>
|
||||||
|
|
||||||
-- <PALETTE>
|
-- <PALETTE>
|
||||||
-- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
|
-- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
|
||||||
-- </PALETTE>
|
-- </PALETTE>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
-- title: Definitely not an Impostor
|
-- title: Mr Anderson's Adventure
|
||||||
-- author: Teletype Games
|
-- name: mranderson
|
||||||
|
-- author: Zsolt Tasnadi
|
||||||
-- desc: Life of a programmer in the Vector
|
-- desc: Life of a programmer in the Vector
|
||||||
-- site: https://git.teletype.hu/games/impostor
|
-- site: https://games.teletype.hu
|
||||||
-- license: MIT License
|
-- license: MIT License
|
||||||
-- version: 0.1
|
-- version: 0.1
|
||||||
-- script: lua
|
-- script: lua
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function Input.up() return btnp(INPUT_KEY_UP) end
|
|||||||
function Input.down() return btnp(INPUT_KEY_DOWN) end
|
function Input.down() return btnp(INPUT_KEY_DOWN) end
|
||||||
function Input.left() return btn(INPUT_KEY_LEFT) end
|
function Input.left() return btn(INPUT_KEY_LEFT) end
|
||||||
function Input.right() return btn(INPUT_KEY_RIGHT) end
|
function Input.right() return btn(INPUT_KEY_RIGHT) end
|
||||||
function Input.select() return btnp(INPUT_KEY_A) or keyp(INPUT_KEY_SPACE) end
|
function Input.player_jump() 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.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 -- B button
|
function Input.player_interact() return btnp(INPUT_KEY_B) or keyp(INPUT_KEY_ENTER) end -- B button
|
||||||
function Input.menu_back() return btnp(INPUT_KEY_Y) or keyp(INPUT_KEY_BACKSPACE) end
|
function Input.menu_back() return btnp(INPUT_KEY_Y) or keyp(INPUT_KEY_BACKSPACE) end
|
||||||
|
|||||||
@@ -20,6 +20,15 @@ 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()
|
||||||
@@ -28,7 +37,7 @@ local STATE_HANDLERS = {
|
|||||||
|
|
||||||
local initialized_game = false
|
local initialized_game = false
|
||||||
|
|
||||||
local function init_game()
|
function init_game()
|
||||||
if initialized_game then return end
|
if initialized_game then return end
|
||||||
|
|
||||||
MenuWindow.refresh_menu_items()
|
MenuWindow.refresh_menu_items()
|
||||||
|
|||||||
@@ -5,6 +5,18 @@ ConfigurationWindow = {
|
|||||||
|
|
||||||
function ConfigurationWindow.init()
|
function ConfigurationWindow.init()
|
||||||
ConfigurationWindow.controls = {
|
ConfigurationWindow.controls = {
|
||||||
|
UI.create_numeric_stepper(
|
||||||
|
"Move Speed",
|
||||||
|
function() return Config.physics.move_speed end,
|
||||||
|
function(v) Config.physics.move_speed = v end,
|
||||||
|
0.5, 3, 0.1, "%.1f"
|
||||||
|
),
|
||||||
|
UI.create_numeric_stepper(
|
||||||
|
"Max Jumps",
|
||||||
|
function() return Config.physics.max_jumps end,
|
||||||
|
function(v) Config.physics.max_jumps = v end,
|
||||||
|
1, 5, 1, "%d"
|
||||||
|
),
|
||||||
UI.create_action_item(
|
UI.create_action_item(
|
||||||
"Save",
|
"Save",
|
||||||
function() Config.save() end
|
function() Config.save() end
|
||||||
|
|||||||
@@ -2,6 +2,27 @@ function GameWindow.draw()
|
|||||||
local currentScreenData = Context.screens[Context.current_screen]
|
local currentScreenData = Context.screens[Context.current_screen]
|
||||||
|
|
||||||
UI.draw_top_bar(currentScreenData.name)
|
UI.draw_top_bar(currentScreenData.name)
|
||||||
|
|
||||||
|
-- Draw platforms
|
||||||
|
for _, p in ipairs(currentScreenData.platforms) do
|
||||||
|
rect(p.x, p.y, p.w, p.h, Config.colors.green)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Draw items
|
||||||
|
for _, item in ipairs(currentScreenData.items) do
|
||||||
|
spr(item.sprite_id, item.x, item.y, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Draw NPCs
|
||||||
|
for _, npc in ipairs(currentScreenData.npcs) do
|
||||||
|
spr(npc.sprite_id, npc.x, npc.y, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Draw ground
|
||||||
|
rect(Context.ground.x, Context.ground.y, Context.ground.w, Context.ground.h, Config.colors.dark_grey)
|
||||||
|
|
||||||
|
-- Draw player
|
||||||
|
Player.draw()
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameWindow.update()
|
function GameWindow.update()
|
||||||
@@ -10,21 +31,7 @@ function GameWindow.update()
|
|||||||
MenuWindow.refresh_menu_items()
|
MenuWindow.refresh_menu_items()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if Input.select() then
|
Player.update() -- Call the encapsulated player update logic
|
||||||
if Context.current_screen == #Context.screens then
|
|
||||||
Context.current_screen = 1
|
|
||||||
else
|
|
||||||
Context.current_screen = Context.current_screen + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if Input.player_interact() 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)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameWindow.set_state(new_state)
|
function GameWindow.set_state(new_state)
|
||||||
|
|||||||
34
inc/window/window.inventory.lua
Normal file
34
inc/window/window.inventory.lua
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
function InventoryWindow.draw()
|
||||||
|
UI.draw_top_bar("Inventory")
|
||||||
|
|
||||||
|
if #Context.inventory == 0 then
|
||||||
|
Print.text("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.text(">", 60, 20 + i * 10, color)
|
||||||
|
end
|
||||||
|
Print.text(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
|
||||||
@@ -41,7 +41,7 @@ function PopupWindow.update()
|
|||||||
if Input.menu_confirm() or Input.menu_back() then
|
if Input.menu_confirm() or Input.menu_back() then
|
||||||
Context.dialog.showing_description = false
|
Context.dialog.showing_description = false
|
||||||
Context.dialog.text = "" -- Clear the description text
|
Context.dialog.text = "" -- Clear the description text
|
||||||
-- No need to change active_window, as it remains in WINDOW_POPUP
|
-- No need to change active_window, as it remains in WINDOW_POPUP or WINDOW_INVENTORY_ACTION
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
Context.dialog.selected_menu_item = UI.update_menu(Context.dialog.menu_items, Context.dialog.selected_menu_item)
|
Context.dialog.selected_menu_item = UI.update_menu(Context.dialog.menu_items, Context.dialog.selected_menu_item)
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
function SplashWindow.draw()
|
function SplashWindow.draw()
|
||||||
local txt = "Definitely not an Impostor"
|
Print.text("Mr. Anderson's", 78, 60, Config.colors.green)
|
||||||
local w = #txt * 6
|
Print.text("Addventure", 90, 70, Config.colors.green)
|
||||||
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
289
infra.md
@@ -1,289 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
|
|
||||||
@@ -6,14 +6,15 @@ init/init.context.lua
|
|||||||
system/system.print.lua
|
system/system.print.lua
|
||||||
entity/entity.npc.lua
|
entity/entity.npc.lua
|
||||||
entity/entity.item.lua
|
entity/entity.item.lua
|
||||||
|
entity/entity.player.lua
|
||||||
system/system.input.lua
|
system/system.input.lua
|
||||||
system/system.ui.lua
|
system/system.ui.lua
|
||||||
map/map.bedroom.lua
|
|
||||||
window/window.splash.lua
|
window/window.splash.lua
|
||||||
window/window.intro.lua
|
window/window.intro.lua
|
||||||
window/window.menu.lua
|
window/window.menu.lua
|
||||||
window/window.configuration.lua
|
window/window.configuration.lua
|
||||||
window/window.popup.lua
|
window/window.popup.lua
|
||||||
|
window/window.inventory.lua
|
||||||
window/window.game.lua
|
window/window.game.lua
|
||||||
system/system.main.lua
|
system/system.main.lua
|
||||||
meta/meta.assets.lua
|
meta/meta.assets.lua
|
||||||
Reference in New Issue
Block a user