forked from tas/major_tom
Compare commits
37 Commits
fix/atmosp
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c62aae16dc | |||
| 3b45572d38 | |||
| 66a7b9e7e6 | |||
| 59f76d6aa7 | |||
| 29c620a9e8 | |||
| 27dc726839 | |||
| 477c299d9f | |||
| f65e8dd9ea | |||
| 84a257f9b9 | |||
| f7c498d7ad | |||
| 7080d7fefc | |||
| 69614f058c | |||
| cc582e1f0e | |||
| c44ace5804 | |||
| ec63ce6701 | |||
| 767d821534 | |||
| 59b6728ce8 | |||
| 4e3e17ced4 | |||
| bb23f2e3a1 | |||
| ec4bc9bb82 | |||
| 4a2d199904 | |||
| 58bf89f2f2 | |||
| 4bef8f37b4 | |||
| 096b0eb096 | |||
| 89e0c483ad | |||
| 7d0e134a56 | |||
| 198f639289 | |||
| f71d140af3 | |||
| 651ac7703f | |||
| 587fd210a2 | |||
| 79e9d0e2ad | |||
| 81ebbd9eec | |||
| 90e3d5aec0 | |||
| 68856fb8c5 | |||
| 7605f0ca8c | |||
| 6d64c6426f | |||
| 93ae351959 |
28
.gitea/workflows/ci.yaml
Normal file
28
.gitea/workflows/ci.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
libsdl2-dev \
|
||||
libsdl2-image-dev \
|
||||
libsdl2-mixer-dev \
|
||||
pkg-config
|
||||
|
||||
- name: Checkout
|
||||
run: |
|
||||
git config --global http.https://git.kimchi.sslVerify false
|
||||
git clone --depth 1 https://${{ secrets.REGISTRY_USER }}:${{ secrets.REGISTRY_PASSWORD }}@git.kimchi/tas/major_tom.git .
|
||||
|
||||
- name: Build (native Linux)
|
||||
run: make
|
||||
50
.gitea/workflows/deploy.yaml
Normal file
50
.gitea/workflows/deploy.yaml
Normal file
@@ -0,0 +1,50 @@
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
REGISTRY: git.kimchi
|
||||
IMAGE: git.kimchi/tas/major_tom
|
||||
NAMESPACE: jnr-web
|
||||
DEPLOYMENT: jnr-web
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
git config --global http.https://git.kimchi.sslVerify false
|
||||
git clone --depth 1 https://${{ secrets.REGISTRY_USER }}:${{ secrets.REGISTRY_PASSWORD }}@git.kimchi/tas/major_tom.git .
|
||||
|
||||
- name: Build and push container image
|
||||
run: |
|
||||
set -ex
|
||||
|
||||
IMAGE_TAG="${{ env.IMAGE }}:sha-${GITHUB_SHA::8}"
|
||||
IMAGE_LATEST="${{ env.IMAGE }}:latest"
|
||||
|
||||
buildah bud --tls-verify=false -f Containerfile -t "$IMAGE_TAG" -t "$IMAGE_LATEST" .
|
||||
|
||||
CREDS="${{ secrets.REGISTRY_USER }}:${{ secrets.REGISTRY_PASSWORD }}"
|
||||
|
||||
echo "=== buildah push tag ==="
|
||||
buildah push --tls-verify=false --creds "$CREDS" "$IMAGE_TAG"
|
||||
|
||||
echo "=== buildah push latest ==="
|
||||
buildah push --tls-verify=false --creds "$CREDS" "$IMAGE_LATEST"
|
||||
|
||||
echo "IMAGE_TAG=$IMAGE_TAG" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Restart deployment on k3s
|
||||
run: |
|
||||
mkdir -p ~/.kube
|
||||
echo "${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config
|
||||
chmod 600 ~/.kube/config
|
||||
kubectl delete pod -l app=${{ env.DEPLOYMENT }} -n ${{ env.NAMESPACE }}
|
||||
|
||||
POD_NAME=$(kubectl get pods -l app=jnr-web -n jnr-web -o jsonpath='{.items[0].metadata.name}')
|
||||
echo "Deleting running pod" "$POD_NAME"
|
||||
|
||||
17
AGENTS.md
17
AGENTS.md
@@ -256,6 +256,23 @@ incremental progress.
|
||||
- Draw layers: BG → entities → FG → particles → HUD
|
||||
- Camera transforms world coords to screen coords
|
||||
|
||||
## Remote Git Server
|
||||
|
||||
The remote git server is a Gitea instance. Use the **`tea` CLI** (not `gh`) for all remote
|
||||
operations: pull requests, issues, releases, and repository management.
|
||||
|
||||
```bash
|
||||
tea pr create --title "..." --description "..." # Create a pull request
|
||||
tea pr list # List open PRs
|
||||
tea issue list # List issues
|
||||
tea issue create --title "..." --description "..."
|
||||
tea pr merge <number> # Merge a PR
|
||||
```
|
||||
|
||||
Do NOT use `gh` (GitHub CLI) — it will not work with this remote.
|
||||
|
||||
After creating a pull request, always check out the `main` branch (`git checkout main`).
|
||||
|
||||
## Commit Messages
|
||||
- Imperative mood, concise
|
||||
- No co-authored-by or AI attribution
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
# JNR Web — static file server for the Emscripten build
|
||||
#
|
||||
# Prerequisites:
|
||||
# make web (produces dist-web/)
|
||||
# JNR Web — multi-stage build: compile WASM then serve static files
|
||||
#
|
||||
# Build:
|
||||
# podman build -t jnr-web .
|
||||
@@ -15,9 +12,21 @@
|
||||
# podman save jnr-web | sudo k3s ctr images import -
|
||||
# sudo k3s kubectl apply -f k8s/
|
||||
|
||||
# ── Stage 1: Build WASM artifacts ──
|
||||
FROM docker.io/emscripten/emsdk:3.1.51 AS builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY Makefile ./
|
||||
COPY include/ include/
|
||||
COPY src/ src/
|
||||
COPY assets/ assets/
|
||||
COPY web/ web/
|
||||
RUN make web
|
||||
|
||||
# ── Stage 2: Serve static files ──
|
||||
FROM docker.io/joseluisq/static-web-server:2
|
||||
|
||||
COPY dist-web/ /public/
|
||||
COPY --from=builder /src/dist-web/ /public/
|
||||
|
||||
# Disable the default cache-control headers which cache .js for 1 year.
|
||||
# The .js and .wasm files must always be fetched together (EM_ASM address
|
||||
|
||||
@@ -146,7 +146,7 @@ adding a new def. See `src/game/projectile.h` for the full definition.
|
||||
## Levels
|
||||
|
||||
### Format (.lvl)
|
||||
Current directives: `TILESET`, `SIZE`, `SPAWN`, `GRAVITY`, `WIND`, `BG_COLOR`, `MUSIC`, `PARALLAX_FAR`, `PARALLAX_NEAR`, `TILEDEF`, `ENTITY`, `EXIT`, `LAYER`
|
||||
Current directives: `TILESET`, `SIZE`, `SPAWN`, `GRAVITY`, `WIND`, `BG_COLOR`, `MUSIC`, `PARALLAX_FAR`, `PARALLAX_NEAR`, `TRANSITION_IN`, `TRANSITION_OUT`, `TILEDEF`, `ENTITY`, `EXIT`, `LAYER`
|
||||
|
||||
**Needed additions:**
|
||||
- `STORM`, `DRAG` — Remaining atmosphere settings
|
||||
|
||||
30
TODO.md
30
TODO.md
@@ -160,11 +160,25 @@ The spacecraft fly-in animation should only play on surface levels (moon01,
|
||||
mars01, etc.). Interior/base levels (mars02, mars03, generated mars_base,
|
||||
generated station) should skip it — the player is already indoors.
|
||||
|
||||
## New level transition styles: elevator and teleporter
|
||||
Two new transition animations to complement the spacecraft fly-in:
|
||||
- **Elevator** — Doors slide shut, brief pause (screen shake / rumble),
|
||||
doors slide open onto the new level. Good for base/station interior
|
||||
transitions (mars02 → mars_base, between generated station levels).
|
||||
- **Teleporter** — Energy charge-up effect around the player, flash/warp
|
||||
distortion, player materialises in the new level. Good for cross-planet
|
||||
jumps or generated-to-handcrafted transitions.
|
||||
## ~~New level transition styles: elevator and teleporter~~ ✓
|
||||
Implemented: `src/game/transition.h` / `transition.c` module with two-phase
|
||||
transition state machine (outro on old level → level swap → intro on new level).
|
||||
|
||||
- **Elevator** — Two horizontal doors slide inward from top/bottom (0.6 s),
|
||||
hold closed with screen-shake rumble (0.3 s), then slide apart on the new
|
||||
level (0.6 s). Smooth ease-in-out motion, dark gray industrial color,
|
||||
bright seam at the meeting edge. Used for base/station interior transitions.
|
||||
- **Teleporter** — Scanline dissolve: 3 px-tall horizontal bands sweep
|
||||
across the screen in alternating directions with staggered top-to-bottom
|
||||
timing (0.5 s), then white flash (0.15 s). Intro reverses the sweep
|
||||
bottom-to-top (0.5 s). Uses `teleport.wav` sound effect.
|
||||
|
||||
New `MODE_TRANSITION` game state in `main.c` pauses gameplay during the
|
||||
animation. Level-load dispatch extracted into `dispatch_level_load()` helper,
|
||||
called both from instant transitions and from the transition state machine.
|
||||
|
||||
New `.lvl` directives `TRANSITION_IN` and `TRANSITION_OUT` with values
|
||||
`none`, `spacecraft`, `elevator`, `teleporter`. Parsed in `tilemap.c`,
|
||||
saved by editor and level generator dump. All three procedural generators
|
||||
(generic, station, mars_base) set `TRANS_ELEVATOR` for interior themes.
|
||||
Handcrafted levels updated: mars02, mars03, level01, level02.
|
||||
|
||||
@@ -8,6 +8,7 @@ SPAWN 3 18
|
||||
GRAVITY 400
|
||||
BG_COLOR 15 15 30
|
||||
MUSIC assets/sounds/algardalgar.ogg
|
||||
TRANSITION_OUT elevator
|
||||
|
||||
# Spacecraft landing intro (arriving from moon)
|
||||
ENTITY spacecraft 1 14
|
||||
|
||||
@@ -7,6 +7,8 @@ SPAWN 3 18
|
||||
GRAVITY 600
|
||||
BG_COLOR 10 10 25
|
||||
MUSIC assets/sounds/algardalgar.ogg
|
||||
TRANSITION_IN elevator
|
||||
TRANSITION_OUT elevator
|
||||
|
||||
# Enemies
|
||||
ENTITY grunt 12 18
|
||||
|
||||
@@ -1,42 +1,51 @@
|
||||
# Mars Surface - Red Dusty Plains
|
||||
# ================================
|
||||
# First Mars level: low gravity, wide-open terrain with wind.
|
||||
# Spacecraft landing intro from moon. Charger enemies patrol
|
||||
# the dusty landscape. Gun pickup midway through.
|
||||
# Level created with in-game editor
|
||||
|
||||
TILESET assets/tiles/mars_tileset.png
|
||||
SIZE 250 23
|
||||
SPAWN 3 18
|
||||
SPAWN 3 19
|
||||
GRAVITY 370
|
||||
WIND 25
|
||||
BG_COLOR 30 12 8
|
||||
PARALLAX_STYLE 5
|
||||
MUSIC assets/sounds/kaffe_og_kage.ogg
|
||||
PARALLAX_STYLE 5
|
||||
|
||||
ENTITY spacecraft 1 14
|
||||
|
||||
# Charger patrols across flat sections
|
||||
ENTITY spacecraft 1 15
|
||||
ENTITY charger 40 18
|
||||
ENTITY charger 75 18
|
||||
ENTITY charger 120 14
|
||||
ENTITY charger 165 18
|
||||
ENTITY charger 120 11
|
||||
ENTITY charger 165 17
|
||||
ENTITY charger 200 18
|
||||
|
||||
# Grunts near structures
|
||||
ENTITY grunt 55 18
|
||||
ENTITY grunt 140 18
|
||||
|
||||
# Health and gun pickups
|
||||
ENTITY powerup_hp 90 15
|
||||
ENTITY powerup_gun 130 12
|
||||
ENTITY powerup_gun 130 11
|
||||
ENTITY grunt 132 18
|
||||
ENTITY grunt 116 18
|
||||
ENTITY grunt 28 19
|
||||
ENTITY powerup_jet 177 19
|
||||
ENTITY grunt 174 21
|
||||
ENTITY grunt 169 21
|
||||
ENTITY grunt 164 21
|
||||
ENTITY grunt 159 21
|
||||
ENTITY grunt 202 18
|
||||
ENTITY grunt 213 13
|
||||
ENTITY grunt 20 19
|
||||
ENTITY powerup_fuel 24 8
|
||||
ENTITY powerup_fuel 90 11
|
||||
ENTITY charger 127 18
|
||||
ENTITY charger 175 21
|
||||
ENTITY asteroid 54 5
|
||||
ENTITY asteroid 53 5
|
||||
ENTITY asteroid 48 5
|
||||
|
||||
EXIT 246 17 2 3 assets/levels/mars02.lvl
|
||||
|
||||
# Tile definitions (Mars tileset)
|
||||
TILEDEF 1 0 0 1
|
||||
TILEDEF 2 1 0 1
|
||||
TILEDEF 3 2 0 1
|
||||
TILEDEF 4 0 1 2
|
||||
TILEDEF 5 1 1 0
|
||||
TILEDEF 6 2 1 0
|
||||
|
||||
LAYER collision
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
@@ -48,15 +57,18 @@ LAYER collision
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 4 0 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 4 0 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1 2 1 1 1 2 1 1 1 1 2 1 1 1 1 1 1 1 1 2 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 1 2 1 1 1 1 1 1 1 2 1 1 1 2 1 1 1 1 0 0 0 0 0 1 2 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 2 1 1 2 1 1 2 1 1 1 1 1 0 0 0 0 0 1 2 1 1 1 1 1 2 1 1 1 1 2 1 1 2 1 1 1 1 1 1 1 1 2 1 1 1 1 1 2 1 1 1 1 1 1 2 1 1 1 2 1 1 1 2 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 2 1 1 2 1 1 1 1 1 1 1 2 1 1 1 1 2 1 1 1 1 1 1 1 1 1
|
||||
1 3 1 1 3 1 1 3 1 3 1 1 1 3 1 1 3 1 3 1 1 3 1 1 3 1 1 3 1 1 3 1 1 3 1 0 0 0 0 1 3 1 1 1 3 1 1 3 1 3 1 1 3 1 1 3 1 1 3 1 3 1 0 0 0 0 0 1 3 1 1 1 3 1 1 3 1 3 1 1 3 1 1 3 1 1 1 1 3 1 1 3 1 3 1 1 3 1 0 0 0 0 0 0 1 1 3 1 1 3 1 1 3 1 1 1 1 3 1 1 3 1 3 1 1 3 1 1 1 3 1 3 1 1 1 3 1 1 1 3 1 1 3 1 1 3 1 1 3 0 0 0 0 0 1 3 1 1 3 1 3 1 1 1 3 1 1 3 1 1 1 3 1 1 3 1 3 1 1 1 3 1 1 3 1 1 3 1 3 1 1 1 3 1 3 1 1 3 1 1 3 1 3 1 1 3 1 1 3 1 1 1 3 1 1 3 1 1 3 1 3 1 1 1 3 1 3 1 1 1 3 1 3 1 1 1 3 1 1 3 1 1 3 1 3 1 1 3 1 3
|
||||
3 1 3 3 1 3 3 1 3 1 3 3 3 1 3 3 1 3 1 3 3 1 3 3 1 3 3 1 3 3 1 3 3 1 3 0 0 0 0 3 1 3 3 3 1 3 3 1 3 1 3 3 1 3 3 1 3 3 1 3 1 3 0 0 0 0 0 3 1 3 3 3 1 3 3 1 3 1 3 3 1 3 3 1 3 3 3 3 1 3 3 1 3 1 3 3 1 3 0 0 0 0 0 0 3 3 1 3 3 1 3 3 1 3 3 3 3 1 3 3 1 3 1 3 3 1 3 3 3 1 3 1 3 3 3 1 3 3 3 1 3 3 1 3 3 1 3 3 1 0 0 0 0 0 3 1 3 3 1 3 1 3 3 3 1 3 3 1 3 3 3 1 3 3 1 3 1 3 3 3 1 3 3 1 3 3 1 3 1 3 3 3 1 3 1 3 3 1 3 3 1 3 1 3 3 1 3 3 1 3 3 3 1 3 3 1 3 3 1 3 1 3 3 3 1 3 1 3 3 3 1 3 1 3 3 3 1 3 3 1 3 3 1 3 1 3 3 1 3 1
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 2 1 1 1 1 1 2 1 1 1 1 2 1 1 2 1 1 1 1 1 2 1 1 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 3 1 1 1 3 1 1 3 1 3 1 1 3 1 1 3 1 1 3 1 3 1 0 0 0 0 0 1 3 1 1 1 3 1 1 3 1 3 1 1 3 1 1 3 1 1 1 1 3 1 1 3 1 3 1 1 3 1 0 0 0 0 0 0 1 1 3 1 1 3 1 1 3 1 1 1 1 3 1 1 3 1 3 1 1 3 1 1 1 3 1 3 1 1 1 3 1 1 1 3 1 1 3 1 1 3 1 1 3 0 0 0 0 0 1 5 5 5 5 5 5 5 5 5 3 1 1 3 1 1 1 3 1 1 3 1 5 5 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3 1 3 3 1 3 3 1 3 1 3 3 3 1 3 3 1 3 1 3 3 1 3 3 1 3 3 1 3 3 1 3 3 1 3 0 0 0 0 3 1 3 3 3 1 3 3 1 3 1 3 3 1 3 3 1 3 3 1 3 1 3 0 0 0 0 0 3 1 3 3 3 1 3 3 1 3 1 3 3 1 3 3 1 3 3 3 3 1 3 3 1 3 1 3 3 1 3 0 0 0 0 0 0 3 3 1 3 3 1 3 3 1 3 3 3 3 1 3 3 1 3 1 3 3 1 3 3 3 1 3 1 3 3 3 1 3 3 3 1 3 3 1 3 3 1 3 3 1 0 0 0 0 0 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 4 1 3 1 3 3 1 3 1
|
||||
2 2 2 3 1 3 3 2 1 1 2 2 3 1 1 1 1 1 2 3 2 1 1 1 1 1 1 1 2 3 3 3 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
2 1 3 3 2 2 3 2 2 1 3 2 2 2 1 1 1 3 1 2 2 2 2 1 3 3 3 3 3 3 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@ SIZE 40 46
|
||||
SPAWN 3 7
|
||||
GRAVITY 700
|
||||
BG_COLOR 20 10 6
|
||||
PARALLAX_STYLE 5
|
||||
PARALLAX_STYLE 2
|
||||
MUSIC assets/sounds/kaffe_og_kage.ogg
|
||||
TRANSITION_OUT elevator
|
||||
|
||||
ENTITY spacecraft 1 3
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ GRAVITY 700
|
||||
BG_COLOR 15 8 5
|
||||
PARALLAX_STYLE 3
|
||||
MUSIC assets/sounds/kaffe_og_kage.ogg
|
||||
TRANSITION_IN elevator
|
||||
TRANSITION_OUT elevator
|
||||
|
||||
# Gun pickup right at spawn — the player needs it
|
||||
ENTITY powerup_gun 5 18
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
# Moon Surface - Crater Fields
|
||||
# ============================
|
||||
# Second moon level: tighter platforming over deep craters.
|
||||
# Spacecraft landing intro. Unarmed — pure jump-and-run with asteroids.
|
||||
# Level created with in-game editor
|
||||
|
||||
TILESET assets/tiles/moon_tileset.png
|
||||
SIZE 200 23
|
||||
SPAWN 3 18
|
||||
GRAVITY 300
|
||||
BG_COLOR 5 5 15
|
||||
PARALLAX_STYLE 4
|
||||
MUSIC assets/sounds/algardalgar.ogg
|
||||
PARALLAX_STYLE 4
|
||||
PLAYER_UNARMED
|
||||
|
||||
ENTITY spacecraft 1 14
|
||||
@@ -20,10 +17,22 @@ ENTITY asteroid 105 3
|
||||
ENTITY asteroid 130 0
|
||||
ENTITY asteroid 155 2
|
||||
ENTITY asteroid 180 1
|
||||
ENTITY powerup_fuel 43 16
|
||||
ENTITY powerup_fuel 46 11
|
||||
ENTITY powerup_fuel 75 15
|
||||
ENTITY powerup_fuel 94 12
|
||||
ENTITY powerup_fuel 106 16
|
||||
ENTITY powerup_fuel 130 12
|
||||
ENTITY powerup_fuel 166 12
|
||||
ENTITY asteroid 167 0
|
||||
ENTITY asteroid 117 0
|
||||
ENTITY asteroid 96 0
|
||||
ENTITY asteroid 66 0
|
||||
ENTITY asteroid 44 0
|
||||
ENTITY asteroid 40 0
|
||||
|
||||
EXIT 196 17 2 3 assets/levels/moon03.lvl
|
||||
|
||||
# Tile definitions
|
||||
TILEDEF 1 0 0 1
|
||||
TILEDEF 2 1 0 1
|
||||
TILEDEF 3 2 0 1
|
||||
@@ -53,3 +62,4 @@ LAYER collision
|
||||
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1
|
||||
1 2 2 2 1 1 1 2 1 2 2 1 2 2 1 0 0 0 0 0 1 2 1 1 2 1 1 1 2 0 0 0 0 0 0 0 1 1 2 2 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2 2 1 1 1 2 2 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 2 1 2 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 2 2 1 2 2 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 2 1 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 2 1 1 2 2 2 1 1 1 2 0 0 0 0 0 0 1 1 1 1 1 2 1 1 1 2 1 2 1
|
||||
1 1 1 3 1 3 1 1 3 1 1 3 1 1 1 0 0 0 0 0 1 3 1 3 1 1 1 1 3 0 0 0 0 0 0 0 1 1 3 1 3 1 1 3 1 1 3 1 1 0 0 0 0 0 0 0 0 0 0 3 1 1 3 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 3 3 1 1 3 3 3 1 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 3 3 1 3 3 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 3 3 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 3 1 1 1 3 3 1 1 3 3 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 3 1 1 1 1 1 0 0 0 0 0 0 3 1 3 1 3 1 1 3 1 1 1 1 1
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
# Moon Surface - Dark Side
|
||||
# ========================
|
||||
# Third moon level: the hardest moon terrain with massive chasms.
|
||||
# Spacecraft landing intro. Unarmed until gun powerup near exit.
|
||||
# Level created with in-game editor
|
||||
|
||||
TILESET assets/tiles/moon_tileset.png
|
||||
SIZE 150 23
|
||||
SPAWN 3 18
|
||||
GRAVITY 300
|
||||
BG_COLOR 5 5 15
|
||||
PARALLAX_STYLE 4
|
||||
MUSIC assets/sounds/algardalgar.ogg
|
||||
PARALLAX_STYLE 4
|
||||
PLAYER_UNARMED
|
||||
|
||||
ENTITY spacecraft 1 14
|
||||
@@ -18,13 +15,16 @@ ENTITY asteroid 50 2
|
||||
ENTITY asteroid 75 1
|
||||
ENTITY asteroid 100 3
|
||||
ENTITY asteroid 125 0
|
||||
|
||||
# Gun powerup near the exit — the player finally gets armed
|
||||
ENTITY powerup_gun 130 18
|
||||
ENTITY asteroid 25 1
|
||||
ENTITY asteroid 60 0
|
||||
ENTITY asteroid 86 0
|
||||
ENTITY asteroid 98 0
|
||||
ENTITY asteroid 110 0
|
||||
ENTITY asteroid 120 1
|
||||
|
||||
EXIT 146 17 2 3 assets/levels/mars01.lvl
|
||||
|
||||
# Tile definitions
|
||||
TILEDEF 1 0 0 1
|
||||
TILEDEF 2 1 0 1
|
||||
TILEDEF 3 2 0 1
|
||||
@@ -40,17 +40,18 @@ LAYER collision
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
|
||||
1 2 1 1 1 2 2 1 2 1 1 1 0 0 0 0 0 0 1 2 1 1 1 1 1 1 1 0 0 0 0 0 0 0 2 2 2 1 1 2 1 2 2 0 0 0 0 0 0 0 0 0 0 1 1 1 2 1 2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2 1 2 1 1 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 2 2 1 1 1 1 0 0 0 0 0 0 2 1 1 1 2 2 1 1 1 2 2 1 1 2 1 1 2 1 1 2 1
|
||||
1 1 1 3 3 1 3 1 1 3 1 3 0 0 0 0 0 0 1 3 1 1 1 1 1 1 3 0 0 0 0 0 0 0 3 3 3 1 3 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 1 3 3 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 3 3 1 3 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 3 3 1 0 0 0 0 0 0 1 3 1 1 1 1 1 3 1 1 1 3 1 1 3 1 1 1 1 3 1
|
||||
1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
|
||||
1 2 1 1 1 2 2 1 2 1 1 1 0 0 0 0 0 0 1 2 1 1 1 1 1 1 1 0 0 0 0 0 0 0 2 2 2 1 1 2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 2 1 2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2 1 2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 1 1 0 0 0 0 0 0 0 0 2 1 1 1 2 2 1 1 1 2 2 1 1 2 1 1 2 1 1 2 1
|
||||
1 1 1 3 3 1 3 1 1 3 1 3 0 0 0 0 0 0 1 3 1 1 1 1 1 1 3 0 0 0 0 0 0 0 3 3 3 1 3 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 3 3 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 3 3 1 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 3 0 0 0 0 0 0 0 0 1 3 1 1 1 1 1 3 1 1 1 3 1 1 3 1 1 1 1 3 1
|
||||
|
||||
|
||||
@@ -28,6 +28,14 @@
|
||||
/* ── Level transitions ─────────────────────────────── */
|
||||
#define MAX_EXIT_ZONES 16 /* max exit zones per level */
|
||||
|
||||
typedef enum TransitionStyle {
|
||||
TRANS_NONE, /* instant cut (default) */
|
||||
TRANS_SPACECRAFT, /* handled by spacecraft entity */
|
||||
TRANS_ELEVATOR, /* doors close, rumble, doors open */
|
||||
TRANS_TELEPORTER, /* scanline dissolve, flash, materialize */
|
||||
TRANS_STYLE_COUNT
|
||||
} TransitionStyle;
|
||||
|
||||
/* ── Rendering ──────────────────────────────────────── */
|
||||
#define MAX_SPRITES 2048 /* max queued sprites per frame */
|
||||
|
||||
@@ -41,4 +49,7 @@
|
||||
#define MAX_ASSETS 128
|
||||
#define ASSET_PATH_MAX 256
|
||||
|
||||
/* ── Debug log ──────────────────────────────────────── */
|
||||
#define DEBUGLOG_BUFFER_SIZE (4 * 1024 * 1024) /* 4 MB ring buffer */
|
||||
|
||||
#endif /* JNR_CONFIG_H */
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "engine/renderer.h"
|
||||
#include "engine/audio.h"
|
||||
#include "engine/assets.h"
|
||||
#include "engine/debuglog.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
@@ -118,6 +119,7 @@ static void engine_frame(void) {
|
||||
if (s_callbacks.update) {
|
||||
s_callbacks.update(DT);
|
||||
}
|
||||
debuglog_record_tick();
|
||||
input_consume();
|
||||
g_engine.tick++;
|
||||
s_accumulator -= DT;
|
||||
|
||||
474
src/engine/debuglog.c
Normal file
474
src/engine/debuglog.c
Normal file
@@ -0,0 +1,474 @@
|
||||
#include "engine/debuglog.h"
|
||||
#include "engine/core.h"
|
||||
#include "engine/input.h"
|
||||
#include "engine/physics.h"
|
||||
#include "engine/entity.h"
|
||||
#include "game/level.h"
|
||||
#include "game/player.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* File-scope state
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
/* Ring buffer header stored at the start of the memory block. */
|
||||
typedef struct RingHeader {
|
||||
uint32_t magic; /* 0x44424C47 = "DBLG" */
|
||||
uint32_t version;
|
||||
uint32_t write_cursor; /* byte offset into data region */
|
||||
uint32_t total_written; /* total snapshots written (wraps) */
|
||||
uint32_t data_size; /* usable data bytes after header */
|
||||
} RingHeader;
|
||||
|
||||
#define RING_MAGIC 0x44424C47
|
||||
#define RING_VERSION 1
|
||||
|
||||
/* Flush the buffer to disk every this many ticks (10 s at 60 Hz). */
|
||||
#define FLUSH_INTERVAL 600
|
||||
|
||||
static bool s_enabled;
|
||||
static uint8_t *s_buffer; /* full allocation: header + data */
|
||||
static RingHeader *s_header;
|
||||
static uint8_t *s_data; /* start of ring data region */
|
||||
static Level *s_level;
|
||||
static char s_level_name[32]; /* human-readable level label */
|
||||
static int s_flush_counter;
|
||||
|
||||
/* Path used for periodic safety-net flushes. */
|
||||
static const char *s_flush_path = "debug_log_autosave.bin";
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* Internal helpers
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
/* Write raw bytes into the ring, wrapping at the boundary. */
|
||||
static void ring_write(const void *src, uint32_t len) {
|
||||
uint32_t cursor = s_header->write_cursor;
|
||||
uint32_t cap = s_header->data_size;
|
||||
const uint8_t *p = (const uint8_t *)src;
|
||||
|
||||
uint32_t first = cap - cursor;
|
||||
if (first >= len) {
|
||||
memcpy(s_data + cursor, p, len);
|
||||
} else {
|
||||
memcpy(s_data + cursor, p, first);
|
||||
memcpy(s_data, p + first, len - first);
|
||||
}
|
||||
s_header->write_cursor = (cursor + len) % cap;
|
||||
}
|
||||
|
||||
/* Read raw bytes from an arbitrary ring offset, wrapping. */
|
||||
static void ring_read(uint32_t offset, void *dst, uint32_t len) {
|
||||
uint32_t cap = s_header->data_size;
|
||||
offset = offset % cap;
|
||||
uint8_t *out = (uint8_t *)dst;
|
||||
|
||||
uint32_t first = cap - offset;
|
||||
if (first >= len) {
|
||||
memcpy(out, s_data + offset, len);
|
||||
} else {
|
||||
memcpy(out, s_data + offset, first);
|
||||
memcpy(out + first, s_data, len - first);
|
||||
}
|
||||
}
|
||||
|
||||
/* Flush the entire buffer (header + data) to a binary file. */
|
||||
static void flush_to_file(const char *path) {
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) {
|
||||
fprintf(stderr, "Warning: debuglog flush failed — cannot open %s\n", path);
|
||||
return;
|
||||
}
|
||||
uint32_t total = (uint32_t)sizeof(RingHeader) + s_header->data_size;
|
||||
size_t written = fwrite(s_buffer, 1, total, f);
|
||||
fclose(f);
|
||||
if (written != total) {
|
||||
fprintf(stderr, "Warning: debuglog flush incomplete (%zu / %u bytes)\n",
|
||||
written, total);
|
||||
}
|
||||
}
|
||||
|
||||
/* Find the player entity in the current level, or NULL. */
|
||||
static Entity *find_player(void) {
|
||||
if (!s_level) return NULL;
|
||||
EntityManager *em = &s_level->entities;
|
||||
for (int i = 0; i < MAX_ENTITIES; i++) {
|
||||
Entity *e = &em->entities[i];
|
||||
if (e->active && e->type == ENT_PLAYER) return e;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Pack player-specific flags into a single byte.
|
||||
* Bit layout: 0=on_ground, 1=jumping, 2=dashing, 3=invincible, 4=has_gun */
|
||||
static uint8_t pack_player_flags(const Entity *player) {
|
||||
uint8_t f = 0;
|
||||
if (player->body.on_ground) f |= (1 << 0);
|
||||
PlayerData *pd = (PlayerData *)player->data;
|
||||
if (pd) {
|
||||
if (pd->jumping) f |= (1 << 1);
|
||||
if (pd->dash_timer > 0) f |= (1 << 2);
|
||||
if (pd->inv_timer > 0) f |= (1 << 3);
|
||||
if (pd->has_gun) f |= (1 << 4);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
/* Pack entity-level flags into a single byte.
|
||||
* Bit layout: 0=active, 1=facing_left, 2=dead, 3=invincible */
|
||||
static uint8_t pack_entity_flags(const Entity *e) {
|
||||
uint8_t f = 0;
|
||||
if (e->active) f |= (1 << 0);
|
||||
if (e->flags & ENTITY_FACING_LEFT) f |= (1 << 1);
|
||||
if (e->flags & ENTITY_DEAD) f |= (1 << 2);
|
||||
if (e->flags & ENTITY_INVINCIBLE) f |= (1 << 3);
|
||||
return f;
|
||||
}
|
||||
|
||||
/* Names for entity types — kept in sync with EntityType enum. */
|
||||
static const char *entity_type_name(uint8_t type) {
|
||||
switch ((EntityType)type) {
|
||||
case ENT_NONE: return "NONE";
|
||||
case ENT_PLAYER: return "PLAYER";
|
||||
case ENT_ENEMY_GRUNT: return "GRUNT";
|
||||
case ENT_ENEMY_FLYER: return "FLYER";
|
||||
case ENT_PROJECTILE: return "PROJ";
|
||||
case ENT_PICKUP: return "PICKUP";
|
||||
case ENT_PARTICLE: return "PARTICLE";
|
||||
case ENT_TURRET: return "TURRET";
|
||||
case ENT_MOVING_PLATFORM: return "PLATFORM";
|
||||
case ENT_FLAME_VENT: return "FLAME";
|
||||
case ENT_FORCE_FIELD: return "FORCEFIELD";
|
||||
case ENT_POWERUP: return "POWERUP";
|
||||
case ENT_DRONE: return "DRONE";
|
||||
case ENT_ASTEROID: return "ASTEROID";
|
||||
case ENT_SPACECRAFT: return "SPACECRAFT";
|
||||
case ENT_LASER_TURRET: return "LASER_TURRET";
|
||||
case ENT_ENEMY_CHARGER: return "CHARGER";
|
||||
case ENT_SPAWNER: return "SPAWNER";
|
||||
default: return "???";
|
||||
}
|
||||
}
|
||||
|
||||
/* Names for aim directions. */
|
||||
static const char *aim_dir_name(uint8_t aim) {
|
||||
switch ((AimDir)aim) {
|
||||
case AIM_FORWARD: return "FORWARD";
|
||||
case AIM_UP: return "UP";
|
||||
case AIM_DIAG_UP: return "DIAG_UP";
|
||||
default: return "???";
|
||||
}
|
||||
}
|
||||
|
||||
/* Names for input actions. */
|
||||
static const char *action_name(int a) {
|
||||
switch ((Action)a) {
|
||||
case ACTION_LEFT: return "LEFT";
|
||||
case ACTION_RIGHT: return "RIGHT";
|
||||
case ACTION_UP: return "UP";
|
||||
case ACTION_DOWN: return "DOWN";
|
||||
case ACTION_JUMP: return "JUMP";
|
||||
case ACTION_SHOOT: return "SHOOT";
|
||||
case ACTION_DASH: return "DASH";
|
||||
case ACTION_PAUSE: return "PAUSE";
|
||||
default: return "???";
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* Public API
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
void debuglog_init(void) {
|
||||
uint32_t total = (uint32_t)sizeof(RingHeader) + DEBUGLOG_BUFFER_SIZE;
|
||||
s_buffer = calloc(1, total);
|
||||
if (!s_buffer) {
|
||||
fprintf(stderr, "Warning: debuglog_init failed to allocate %u bytes\n", total);
|
||||
return;
|
||||
}
|
||||
|
||||
s_header = (RingHeader *)s_buffer;
|
||||
s_data = s_buffer + sizeof(RingHeader);
|
||||
|
||||
s_header->magic = RING_MAGIC;
|
||||
s_header->version = RING_VERSION;
|
||||
s_header->write_cursor = 0;
|
||||
s_header->total_written = 0;
|
||||
s_header->data_size = DEBUGLOG_BUFFER_SIZE;
|
||||
|
||||
s_enabled = false;
|
||||
s_level = NULL;
|
||||
s_flush_counter = 0;
|
||||
|
||||
printf("Debug log initialized (%u KB ring buffer)\n",
|
||||
DEBUGLOG_BUFFER_SIZE / 1024);
|
||||
}
|
||||
|
||||
void debuglog_shutdown(void) {
|
||||
if (!s_buffer) return;
|
||||
|
||||
if (s_enabled && s_header->total_written > 0) {
|
||||
flush_to_file(s_flush_path);
|
||||
printf("Debug log flushed to %s on shutdown (%u snapshots)\n",
|
||||
s_flush_path, s_header->total_written);
|
||||
}
|
||||
|
||||
free(s_buffer);
|
||||
s_buffer = NULL;
|
||||
s_header = NULL;
|
||||
s_data = NULL;
|
||||
s_enabled = false;
|
||||
s_level = NULL;
|
||||
}
|
||||
|
||||
void debuglog_enable(void) {
|
||||
s_enabled = true;
|
||||
printf("Debug log recording enabled\n");
|
||||
}
|
||||
|
||||
bool debuglog_is_enabled(void) {
|
||||
return s_enabled;
|
||||
}
|
||||
|
||||
void debuglog_set_level(Level *lvl, const char *name) {
|
||||
s_level = lvl;
|
||||
if (name && name[0]) {
|
||||
snprintf(s_level_name, sizeof(s_level_name), "%s", name);
|
||||
} else {
|
||||
s_level_name[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void debuglog_record_tick(void) {
|
||||
if (!s_enabled || !s_buffer) return;
|
||||
|
||||
/* Count active entities (skip player — recorded separately). */
|
||||
uint16_t ent_count = 0;
|
||||
if (s_level) {
|
||||
EntityManager *em = &s_level->entities;
|
||||
for (int i = 0; i < MAX_ENTITIES && ent_count < MAX_ENTITIES; i++) {
|
||||
Entity *e = &em->entities[i];
|
||||
if (e->active && e->type != ENT_PLAYER) ent_count++;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t frame_size = (uint32_t)sizeof(TickSnapshot)
|
||||
+ (uint32_t)(ent_count * sizeof(EntitySnapshot));
|
||||
|
||||
/* Don't write if the frame won't fit in the ring at all. */
|
||||
if (frame_size > s_header->data_size) return;
|
||||
|
||||
/* Build the header portion on the stack. */
|
||||
TickSnapshot snap;
|
||||
memset(&snap, 0, sizeof(snap));
|
||||
snap.tick = g_engine.tick;
|
||||
snap.frame_size = frame_size;
|
||||
|
||||
/* Input. */
|
||||
input_get_snapshot(&snap.input);
|
||||
|
||||
/* Player state. */
|
||||
Entity *player = find_player();
|
||||
if (player) {
|
||||
snap.player_x = player->body.pos.x;
|
||||
snap.player_y = player->body.pos.y;
|
||||
snap.player_vx = player->body.vel.x;
|
||||
snap.player_vy = player->body.vel.y;
|
||||
snap.player_health = (int8_t)player->health;
|
||||
snap.player_flags = pack_player_flags(player);
|
||||
PlayerData *pd = (PlayerData *)player->data;
|
||||
if (pd) {
|
||||
snap.player_dash_timer = pd->dash_timer;
|
||||
snap.player_dash_charges = (int8_t)pd->dash_charges;
|
||||
snap.player_inv_timer = pd->inv_timer;
|
||||
snap.player_coyote = pd->coyote_timer;
|
||||
snap.player_aim_dir = (uint8_t)pd->aim_dir;
|
||||
}
|
||||
}
|
||||
|
||||
/* Camera. */
|
||||
if (s_level) {
|
||||
snap.cam_x = s_level->camera.pos.x;
|
||||
snap.cam_y = s_level->camera.pos.y;
|
||||
}
|
||||
|
||||
/* Physics globals. */
|
||||
snap.gravity = physics_get_gravity();
|
||||
snap.wind = physics_get_wind();
|
||||
|
||||
/* Level name (truncated to 31 chars + NUL). */
|
||||
if (s_level_name[0]) {
|
||||
snprintf(snap.level_name, sizeof(snap.level_name), "%s", s_level_name);
|
||||
}
|
||||
|
||||
snap.entity_count = ent_count;
|
||||
|
||||
/* Write header into ring. */
|
||||
ring_write(&snap, sizeof(TickSnapshot));
|
||||
|
||||
/* Write entity snapshots. */
|
||||
if (s_level && ent_count > 0) {
|
||||
EntityManager *em = &s_level->entities;
|
||||
for (int i = 0; i < MAX_ENTITIES; i++) {
|
||||
Entity *e = &em->entities[i];
|
||||
if (!e->active || e->type == ENT_PLAYER) continue;
|
||||
|
||||
EntitySnapshot es;
|
||||
es.type = (uint8_t)e->type;
|
||||
es.flags = pack_entity_flags(e);
|
||||
es.health = (int16_t)e->health;
|
||||
es.pos_x = e->body.pos.x;
|
||||
es.pos_y = e->body.pos.y;
|
||||
es.vel_x = e->body.vel.x;
|
||||
es.vel_y = e->body.vel.y;
|
||||
ring_write(&es, sizeof(EntitySnapshot));
|
||||
}
|
||||
}
|
||||
|
||||
s_header->total_written++;
|
||||
|
||||
/* Periodic safety-net flush. */
|
||||
s_flush_counter++;
|
||||
if (s_flush_counter >= FLUSH_INTERVAL) {
|
||||
s_flush_counter = 0;
|
||||
flush_to_file(s_flush_path);
|
||||
}
|
||||
}
|
||||
|
||||
void debuglog_dump(const char *path) {
|
||||
if (!s_buffer || s_header->total_written == 0) {
|
||||
printf("Debug log: nothing to dump (0 snapshots recorded)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *f = fopen(path, "w");
|
||||
if (!f) {
|
||||
fprintf(stderr, "Error: cannot open %s for writing\n", path);
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(f, "=== Debug Log Dump ===\n");
|
||||
fprintf(f, "Total snapshots written: %u\n\n", s_header->total_written);
|
||||
|
||||
/* Walk backwards from write_cursor to find snapshots.
|
||||
* We read forward through the ring, starting from the oldest data. */
|
||||
|
||||
/* To find the oldest snapshot: if the buffer has wrapped, oldest
|
||||
* starts at write_cursor. Otherwise it starts at 0. */
|
||||
uint32_t cap = s_header->data_size;
|
||||
uint32_t total_data_bytes;
|
||||
uint32_t read_pos;
|
||||
|
||||
/* Estimate how much data we've written total. If total_written
|
||||
* snapshots have been recorded and we assume average frame size
|
||||
* is frame_size, the buffer may have wrapped. We use a simpler
|
||||
* approach: scan forward from the oldest point. */
|
||||
if (s_header->total_written * sizeof(TickSnapshot) >= cap) {
|
||||
/* Buffer has likely wrapped. Start reading at write_cursor
|
||||
* (which is where the oldest data begins). */
|
||||
read_pos = s_header->write_cursor;
|
||||
total_data_bytes = cap;
|
||||
} else {
|
||||
read_pos = 0;
|
||||
total_data_bytes = s_header->write_cursor;
|
||||
}
|
||||
|
||||
uint32_t bytes_read = 0;
|
||||
uint32_t snapshots_dumped = 0;
|
||||
uint32_t first_tick = 0;
|
||||
uint32_t last_tick = 0;
|
||||
|
||||
while (bytes_read + sizeof(TickSnapshot) <= total_data_bytes) {
|
||||
TickSnapshot snap;
|
||||
ring_read(read_pos, &snap, sizeof(TickSnapshot));
|
||||
|
||||
/* Sanity check. */
|
||||
if (snap.frame_size < sizeof(TickSnapshot) ||
|
||||
snap.frame_size > cap) {
|
||||
break;
|
||||
}
|
||||
if (bytes_read + snap.frame_size > total_data_bytes) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (snapshots_dumped == 0) first_tick = snap.tick;
|
||||
last_tick = snap.tick;
|
||||
|
||||
fprintf(f, "--- Tick %u ---\n", snap.tick);
|
||||
|
||||
/* Input. */
|
||||
fprintf(f, "Input:");
|
||||
for (int a = 0; a < ACTION_COUNT && a < 8; a++) {
|
||||
if (snap.input.held & (1 << a)) {
|
||||
fprintf(f, " [%s]", action_name(a));
|
||||
}
|
||||
if (snap.input.pressed & (1 << a)) {
|
||||
fprintf(f, " [%s_pressed]", action_name(a));
|
||||
}
|
||||
if (snap.input.released & (1 << a)) {
|
||||
fprintf(f, " [%s_released]", action_name(a));
|
||||
}
|
||||
}
|
||||
fprintf(f, "\n");
|
||||
|
||||
/* Player. */
|
||||
fprintf(f, "Player: pos=(%.1f, %.1f) vel=(%.1f, %.1f) hp=%d",
|
||||
snap.player_x, snap.player_y,
|
||||
snap.player_vx, snap.player_vy,
|
||||
snap.player_health);
|
||||
fprintf(f, " on_ground=%d", (snap.player_flags >> 0) & 1);
|
||||
fprintf(f, " jumping=%d", (snap.player_flags >> 1) & 1);
|
||||
fprintf(f, " dashing=%d", (snap.player_flags >> 2) & 1);
|
||||
fprintf(f, " inv=%d", (snap.player_flags >> 3) & 1);
|
||||
fprintf(f, " has_gun=%d", (snap.player_flags >> 4) & 1);
|
||||
fprintf(f, " aim=%s", aim_dir_name(snap.player_aim_dir));
|
||||
fprintf(f, " dash_charges=%d dash_timer=%.2f inv_timer=%.2f coyote=%.3f\n",
|
||||
snap.player_dash_charges, snap.player_dash_timer,
|
||||
snap.player_inv_timer, snap.player_coyote);
|
||||
|
||||
/* Camera + physics. */
|
||||
fprintf(f, "Camera: (%.1f, %.1f)\n", snap.cam_x, snap.cam_y);
|
||||
fprintf(f, "Physics: gravity=%.1f wind=%.1f\n",
|
||||
snap.gravity, snap.wind);
|
||||
|
||||
if (snap.level_name[0]) {
|
||||
fprintf(f, "Level: %s\n", snap.level_name);
|
||||
}
|
||||
|
||||
/* Entities. */
|
||||
fprintf(f, "Entities (%u active):\n", snap.entity_count);
|
||||
|
||||
uint32_t ent_offset = (read_pos + sizeof(TickSnapshot)) % cap;
|
||||
uint16_t count = snap.entity_count;
|
||||
if (count > MAX_ENTITIES) count = MAX_ENTITIES;
|
||||
for (uint16_t i = 0; i < count; i++) {
|
||||
EntitySnapshot es;
|
||||
ring_read(ent_offset, &es, sizeof(EntitySnapshot));
|
||||
ent_offset = (ent_offset + sizeof(EntitySnapshot)) % cap;
|
||||
|
||||
fprintf(f, " [%u] %-12s pos=(%.0f, %.0f) vel=(%.0f, %.0f) hp=%d\n",
|
||||
i, entity_type_name(es.type),
|
||||
es.pos_x, es.pos_y,
|
||||
es.vel_x, es.vel_y,
|
||||
es.health);
|
||||
}
|
||||
|
||||
fprintf(f, "\n");
|
||||
|
||||
read_pos = (read_pos + snap.frame_size) % cap;
|
||||
bytes_read += snap.frame_size;
|
||||
snapshots_dumped++;
|
||||
}
|
||||
|
||||
/* Write summary at the top. */
|
||||
if (snapshots_dumped > 0) {
|
||||
float seconds = (float)(last_tick - first_tick) / TICK_RATE;
|
||||
fprintf(f, "=== Summary: ticks %u to %u (%u snapshots, %.1f seconds) ===\n",
|
||||
first_tick, last_tick, snapshots_dumped, seconds);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
printf("Debug log dumped to %s (%u snapshots)\n", path, snapshots_dumped);
|
||||
}
|
||||
91
src/engine/debuglog.h
Normal file
91
src/engine/debuglog.h
Normal file
@@ -0,0 +1,91 @@
|
||||
#ifndef JNR_DEBUGLOG_H
|
||||
#define JNR_DEBUGLOG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "config.h"
|
||||
|
||||
/* Forward declaration — game layer type, registered via pointer. */
|
||||
typedef struct Level Level;
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* Snapshot data structures
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
/* Packed input snapshot — 3 bytes (ACTION_COUNT actions × 3 states). */
|
||||
typedef struct InputSnapshot {
|
||||
uint8_t held; /* bitmask of ACTION_* held this tick */
|
||||
uint8_t pressed; /* bitmask of ACTION_* pressed this tick */
|
||||
uint8_t released; /* bitmask of ACTION_* released this tick */
|
||||
} InputSnapshot;
|
||||
|
||||
/* Per-entity summary — 20 bytes each. */
|
||||
typedef struct EntitySnapshot {
|
||||
uint8_t type; /* EntityType */
|
||||
uint8_t flags; /* active, facing, dead, invincible */
|
||||
int16_t health;
|
||||
float pos_x, pos_y;
|
||||
float vel_x, vel_y;
|
||||
} EntitySnapshot;
|
||||
|
||||
/* Full tick snapshot — fixed-size header + variable entity list.
|
||||
* Written sequentially into the ring buffer; frame_size stores
|
||||
* the total byte count so the reader can skip forward. */
|
||||
typedef struct TickSnapshot {
|
||||
uint32_t tick; /* g_engine.tick */
|
||||
uint32_t frame_size; /* total bytes of this record */
|
||||
/* Input */
|
||||
InputSnapshot input; /* 3 bytes */
|
||||
uint8_t _pad0; /* align to 4 bytes */
|
||||
/* Player state (expanded) */
|
||||
float player_x, player_y;
|
||||
float player_vx, player_vy;
|
||||
int8_t player_health;
|
||||
uint8_t player_flags; /* on_ground, jumping, dashing, inv, has_gun */
|
||||
uint8_t player_aim_dir;
|
||||
int8_t player_dash_charges;
|
||||
float player_dash_timer;
|
||||
float player_inv_timer;
|
||||
float player_coyote;
|
||||
/* Camera */
|
||||
float cam_x, cam_y;
|
||||
/* Physics globals */
|
||||
float gravity;
|
||||
float wind;
|
||||
/* Level info */
|
||||
char level_name[32]; /* truncated level path/tag */
|
||||
/* Entity summary */
|
||||
uint16_t entity_count; /* number of active entities */
|
||||
uint16_t _pad1; /* alignment padding */
|
||||
/* EntitySnapshot entities[] follows immediately in the buffer. */
|
||||
} TickSnapshot;
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* Public API
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
/* Allocate ring buffer. Safe to call even if logging stays disabled. */
|
||||
void debuglog_init(void);
|
||||
|
||||
/* Final flush + free. */
|
||||
void debuglog_shutdown(void);
|
||||
|
||||
/* Turn on recording. */
|
||||
void debuglog_enable(void);
|
||||
|
||||
/* Query state. */
|
||||
bool debuglog_is_enabled(void);
|
||||
|
||||
/* Register the active Level pointer so the debuglog can read game state.
|
||||
* Pass NULL before freeing a level.
|
||||
* name is an optional label (e.g. file path); NULL or "" to clear. */
|
||||
void debuglog_set_level(Level *lvl, const char *name);
|
||||
|
||||
/* Called once per tick from engine_frame, after update, before consume.
|
||||
* Captures input + game state into the ring buffer. */
|
||||
void debuglog_record_tick(void);
|
||||
|
||||
/* Dump the ring buffer contents to a human-readable text file. */
|
||||
void debuglog_dump(const char *path);
|
||||
|
||||
#endif /* JNR_DEBUGLOG_H */
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "engine/input.h"
|
||||
#include "engine/debuglog.h"
|
||||
#include <string.h>
|
||||
|
||||
static bool s_current[ACTION_COUNT];
|
||||
@@ -196,6 +197,19 @@ bool input_key_held(SDL_Scancode key) {
|
||||
return s_key_state && s_key_state[key];
|
||||
}
|
||||
|
||||
/* ── Debug log snapshot ───────────────────────────── */
|
||||
|
||||
void input_get_snapshot(InputSnapshot *out) {
|
||||
out->held = 0;
|
||||
out->pressed = 0;
|
||||
out->released = 0;
|
||||
for (int i = 0; i < ACTION_COUNT && i < 8; i++) {
|
||||
if (s_current[i]) out->held |= (uint8_t)(1 << i);
|
||||
if (s_latched_pressed[i]) out->pressed |= (uint8_t)(1 << i);
|
||||
if (s_latched_released[i]) out->released |= (uint8_t)(1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
void input_shutdown(void) {
|
||||
/* Nothing to clean up */
|
||||
}
|
||||
|
||||
@@ -50,4 +50,10 @@ int input_mouse_scroll(void);
|
||||
bool input_key_pressed(SDL_Scancode key);
|
||||
bool input_key_held(SDL_Scancode key);
|
||||
|
||||
/* Pack current input state into a compact bitmask snapshot.
|
||||
* Used by the debug log to record per-tick input without
|
||||
* exposing internal arrays. */
|
||||
typedef struct InputSnapshot InputSnapshot; /* defined in debuglog.h */
|
||||
void input_get_snapshot(InputSnapshot *out);
|
||||
|
||||
#endif /* JNR_INPUT_H */
|
||||
|
||||
@@ -530,6 +530,124 @@ void particle_emit_wall_slide_dust(Vec2 pos, int wall_dir) {
|
||||
particle_emit(&dust);
|
||||
}
|
||||
|
||||
void particle_emit_hit_sparks(Vec2 pos) {
|
||||
/* Bright orange-white sparks — fast, short-lived, spray outward */
|
||||
ParticleBurst sparks = {
|
||||
.origin = pos,
|
||||
.count = 8,
|
||||
.speed_min = 60.0f,
|
||||
.speed_max = 180.0f,
|
||||
.life_min = 0.08f,
|
||||
.life_max = 0.2f,
|
||||
.size_min = 1.0f,
|
||||
.size_max = 2.0f,
|
||||
.spread = (float)M_PI,
|
||||
.direction = 0,
|
||||
.drag = 3.0f,
|
||||
.gravity_scale = 0.4f,
|
||||
.color = {255, 200, 100, 255}, /* orange-white */
|
||||
.color_vary = true,
|
||||
};
|
||||
particle_emit(&sparks);
|
||||
|
||||
/* Brief white flash at impact point */
|
||||
ParticleBurst flash = {
|
||||
.origin = pos,
|
||||
.count = 3,
|
||||
.speed_min = 5.0f,
|
||||
.speed_max = 15.0f,
|
||||
.life_min = 0.03f,
|
||||
.life_max = 0.06f,
|
||||
.size_min = 2.0f,
|
||||
.size_max = 3.5f,
|
||||
.spread = (float)M_PI,
|
||||
.direction = 0,
|
||||
.drag = 8.0f,
|
||||
.gravity_scale = 0.0f,
|
||||
.color = {255, 255, 230, 255}, /* bright white */
|
||||
.color_vary = false,
|
||||
};
|
||||
particle_emit(&flash);
|
||||
}
|
||||
|
||||
void particle_emit_metal_explosion(Vec2 pos) {
|
||||
/* Metal shrapnel — fast grey/silver chunks flying outward */
|
||||
ParticleBurst shrapnel = {
|
||||
.origin = pos,
|
||||
.count = 16,
|
||||
.speed_min = 60.0f,
|
||||
.speed_max = 200.0f,
|
||||
.life_min = 0.2f,
|
||||
.life_max = 0.6f,
|
||||
.size_min = 1.5f,
|
||||
.size_max = 3.5f,
|
||||
.spread = (float)M_PI,
|
||||
.direction = 0,
|
||||
.drag = 2.0f,
|
||||
.gravity_scale = 0.5f,
|
||||
.color = {180, 180, 190, 255}, /* silver-grey */
|
||||
.color_vary = true,
|
||||
};
|
||||
particle_emit(&shrapnel);
|
||||
|
||||
/* Hot orange sparks — electrical/mechanical innards */
|
||||
ParticleBurst sparks = {
|
||||
.origin = pos,
|
||||
.count = 10,
|
||||
.speed_min = 40.0f,
|
||||
.speed_max = 150.0f,
|
||||
.life_min = 0.15f,
|
||||
.life_max = 0.35f,
|
||||
.size_min = 1.0f,
|
||||
.size_max = 2.5f,
|
||||
.spread = (float)M_PI,
|
||||
.direction = 0,
|
||||
.drag = 2.5f,
|
||||
.gravity_scale = 0.3f,
|
||||
.color = {255, 160, 40, 255}, /* hot orange */
|
||||
.color_vary = true,
|
||||
};
|
||||
particle_emit(&sparks);
|
||||
|
||||
/* Bright white flash at center — brief pop */
|
||||
ParticleBurst flash = {
|
||||
.origin = pos,
|
||||
.count = 5,
|
||||
.speed_min = 5.0f,
|
||||
.speed_max = 20.0f,
|
||||
.life_min = 0.04f,
|
||||
.life_max = 0.08f,
|
||||
.size_min = 3.0f,
|
||||
.size_max = 5.0f,
|
||||
.spread = (float)M_PI,
|
||||
.direction = 0,
|
||||
.drag = 8.0f,
|
||||
.gravity_scale = 0.0f,
|
||||
.color = {255, 240, 200, 255}, /* bright flash */
|
||||
.color_vary = false,
|
||||
};
|
||||
particle_emit(&flash);
|
||||
|
||||
/* Smoke cloud — slower, lingers */
|
||||
ParticleBurst smoke = {
|
||||
.origin = pos,
|
||||
.count = 8,
|
||||
.speed_min = 15.0f,
|
||||
.speed_max = 50.0f,
|
||||
.life_min = 0.3f,
|
||||
.life_max = 0.7f,
|
||||
.size_min = 2.5f,
|
||||
.size_max = 4.5f,
|
||||
.spread = (float)M_PI,
|
||||
.direction = 0,
|
||||
.drag = 3.5f,
|
||||
.gravity_scale = -0.1f, /* floats up */
|
||||
.color = {120, 120, 130, 180}, /* dark smoke */
|
||||
.color_vary = true,
|
||||
};
|
||||
particle_emit(&smoke);
|
||||
}
|
||||
|
||||
/* Spawn a single dust mote with the given visual properties. */
|
||||
static void spawn_dust_mote(Vec2 pos, Vec2 vel,
|
||||
float life_min, float life_max,
|
||||
|
||||
@@ -89,6 +89,12 @@ void particle_emit_muzzle_flash(Vec2 pos, Vec2 shoot_dir);
|
||||
/* Wall slide dust (small puffs while scraping against a wall) */
|
||||
void particle_emit_wall_slide_dust(Vec2 pos, int wall_dir);
|
||||
|
||||
/* Metal hit sparks (turret/machine takes non-lethal damage) */
|
||||
void particle_emit_hit_sparks(Vec2 pos);
|
||||
|
||||
/* Metal explosion (turret/machine death — shrapnel + flash) */
|
||||
void particle_emit_metal_explosion(Vec2 pos);
|
||||
|
||||
/* Ambient atmosphere dust (call each frame for Mars Surface levels).
|
||||
* Spawns subtle dust motes around the camera viewport that drift with wind.
|
||||
* cam_pos = camera top-left world position, vp = viewport size in pixels. */
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ── Transition style name → enum mapping ── */
|
||||
static TransitionStyle parse_transition_style(const char *name) {
|
||||
if (strcmp(name, "spacecraft") == 0) return TRANS_SPACECRAFT;
|
||||
if (strcmp(name, "elevator") == 0) return TRANS_ELEVATOR;
|
||||
if (strcmp(name, "teleporter") == 0) return TRANS_TELEPORTER;
|
||||
return TRANS_NONE;
|
||||
}
|
||||
|
||||
/* Read a full line from f into a dynamically growing buffer.
|
||||
* *buf and *cap track the heap buffer; the caller must free *buf.
|
||||
* Returns the line length, or -1 on EOF/error. */
|
||||
@@ -120,6 +128,16 @@ bool tilemap_load(Tilemap *map, const char *path, SDL_Renderer *renderer) {
|
||||
}
|
||||
} else if (strncmp(line, "PLAYER_UNARMED", 14) == 0) {
|
||||
map->player_unarmed = true;
|
||||
} else if (strncmp(line, "TRANSITION_IN ", 14) == 0) {
|
||||
char tname[32] = {0};
|
||||
if (sscanf(line + 14, "%31s", tname) == 1) {
|
||||
map->transition_in = parse_transition_style(tname);
|
||||
}
|
||||
} else if (strncmp(line, "TRANSITION_OUT ", 15) == 0) {
|
||||
char tname[32] = {0};
|
||||
if (sscanf(line + 15, "%31s", tname) == 1) {
|
||||
map->transition_out = parse_transition_style(tname);
|
||||
}
|
||||
} else if (strncmp(line, "EXIT ", 5) == 0) {
|
||||
if (map->exit_zone_count < MAX_EXIT_ZONES) {
|
||||
ExitZone *ez = &map->exit_zones[map->exit_zone_count];
|
||||
|
||||
@@ -58,6 +58,8 @@ typedef struct Tilemap {
|
||||
char parallax_near_path[ASSET_PATH_MAX]; /* near bg image path */
|
||||
int parallax_style; /* procedural bg style (0=default) */
|
||||
bool player_unarmed; /* if true, player starts without gun */
|
||||
TransitionStyle transition_in; /* transition animation for level entry */
|
||||
TransitionStyle transition_out; /* transition animation for level exit */
|
||||
EntitySpawn entity_spawns[MAX_ENTITY_SPAWNS];
|
||||
int entity_spawn_count;
|
||||
ExitZone exit_zones[MAX_EXIT_ZONES];
|
||||
|
||||
@@ -9,9 +9,20 @@
|
||||
/* Initialize client_id in localStorage and store the analytics
|
||||
* API URL + key. Called once at startup. */
|
||||
EM_JS(void, js_analytics_init, (), {
|
||||
/* Generate or retrieve a persistent client UUID */
|
||||
/* Generate or retrieve a persistent client UUID.
|
||||
* crypto.randomUUID() requires a secure context (HTTPS) and is
|
||||
* absent in older browsers, so fall back to a manual v4 UUID. */
|
||||
if (!localStorage.getItem('jnr_client_id')) {
|
||||
localStorage.setItem('jnr_client_id', crypto.randomUUID());
|
||||
var uuid;
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||
uuid = crypto.randomUUID();
|
||||
} else {
|
||||
uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
var r = Math.random() * 16 | 0;
|
||||
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
|
||||
});
|
||||
}
|
||||
localStorage.setItem('jnr_client_id', uuid);
|
||||
}
|
||||
/* Store config on the Module for later use by other EM_JS calls.
|
||||
* ANALYTICS_URL and ANALYTICS_KEY are replaced at build time via
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "game/editor.h"
|
||||
#include "game/entity_registry.h"
|
||||
#include "game/transition.h"
|
||||
#include "engine/core.h"
|
||||
#include "engine/input.h"
|
||||
#include "engine/renderer.h"
|
||||
@@ -359,6 +360,16 @@ static bool save_tilemap(const Tilemap *map, const char *path) {
|
||||
if (map->player_unarmed)
|
||||
fprintf(f, "PLAYER_UNARMED\n");
|
||||
|
||||
/* Transition styles */
|
||||
if (map->transition_in != TRANS_NONE) {
|
||||
fprintf(f, "TRANSITION_IN %s\n",
|
||||
transition_style_name(map->transition_in));
|
||||
}
|
||||
if (map->transition_out != TRANS_NONE) {
|
||||
fprintf(f, "TRANSITION_OUT %s\n",
|
||||
transition_style_name(map->transition_out));
|
||||
}
|
||||
|
||||
fprintf(f, "\n");
|
||||
|
||||
/* Entity spawns */
|
||||
|
||||
@@ -5,9 +5,46 @@
|
||||
#include "engine/renderer.h"
|
||||
#include "engine/particle.h"
|
||||
#include "engine/audio.h"
|
||||
#include "config.h"
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
/* ── Shared helpers ───────────────────────────────── */
|
||||
|
||||
/* Kill enemy if it fell past the bottom of the level. */
|
||||
static bool enemy_check_level_bottom(Entity *self, const Tilemap *map,
|
||||
EntityManager *em) {
|
||||
float level_bottom = (float)(map->height * TILE_SIZE);
|
||||
if (self->body.pos.y > level_bottom) {
|
||||
self->flags |= ENTITY_DEAD;
|
||||
self->health = 0;
|
||||
entity_destroy(em, self);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check for cliff ahead of a ground enemy. Returns true if cliff detected.
|
||||
* Uses a wider lookahead that scales with speed for fast-moving enemies. */
|
||||
static bool enemy_detect_cliff(const Body *body, float patrol_dir,
|
||||
float speed, const Tilemap *map) {
|
||||
if (!body->on_ground) return false;
|
||||
|
||||
/* Scale lookahead with speed: at least 4px, up to speed/10 */
|
||||
float lookahead = speed * 0.1f;
|
||||
if (lookahead < 4.0f) lookahead = 4.0f;
|
||||
|
||||
float check_x = (patrol_dir > 0) ?
|
||||
body->pos.x + body->size.x + lookahead :
|
||||
body->pos.x - lookahead;
|
||||
float check_y = body->pos.y + body->size.y + 4.0f;
|
||||
|
||||
int tx = world_to_tile(check_x);
|
||||
int ty = world_to_tile(check_y);
|
||||
|
||||
return !tilemap_is_solid(map, tx, ty);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════
|
||||
* GRUNT - ground patrol enemy
|
||||
* ════════════════════════════════════════════════════ */
|
||||
@@ -20,6 +57,9 @@ static void grunt_update(Entity *self, float dt, const Tilemap *map) {
|
||||
|
||||
Body *body = &self->body;
|
||||
|
||||
/* Kill if fallen off bottom of level */
|
||||
if (enemy_check_level_bottom(self, map, s_grunt_em)) return;
|
||||
|
||||
/* Death sequence */
|
||||
if (self->flags & ENTITY_DEAD) {
|
||||
animation_set(&self->anim, &anim_grunt_death);
|
||||
@@ -48,19 +88,10 @@ static void grunt_update(Entity *self, float dt, const Tilemap *map) {
|
||||
gd->patrol_dir = -gd->patrol_dir;
|
||||
}
|
||||
|
||||
/* Turn around at ledge: check if there's ground ahead */
|
||||
if (body->on_ground) {
|
||||
float check_x = (gd->patrol_dir > 0) ?
|
||||
body->pos.x + body->size.x + 2.0f :
|
||||
body->pos.x - 2.0f;
|
||||
float check_y = body->pos.y + body->size.y + 4.0f;
|
||||
|
||||
int tx = world_to_tile(check_x);
|
||||
int ty = world_to_tile(check_y);
|
||||
|
||||
if (!tilemap_is_solid(map, tx, ty)) {
|
||||
gd->patrol_dir = -gd->patrol_dir;
|
||||
}
|
||||
/* Turn around at ledge */
|
||||
if (enemy_detect_cliff(body, gd->patrol_dir, GRUNT_SPEED, map)) {
|
||||
gd->patrol_dir = -gd->patrol_dir;
|
||||
body->vel.x = 0;
|
||||
}
|
||||
|
||||
/* Animation */
|
||||
@@ -146,12 +177,14 @@ static Entity *find_player(EntityManager *em) {
|
||||
}
|
||||
|
||||
static void flyer_update(Entity *self, float dt, const Tilemap *map) {
|
||||
(void)map; /* flyers don't collide with tiles */
|
||||
FlyerData *fd = (FlyerData *)self->data;
|
||||
if (!fd) return;
|
||||
|
||||
Body *body = &self->body;
|
||||
|
||||
/* Kill if fallen off bottom of level */
|
||||
if (enemy_check_level_bottom(self, map, s_flyer_em)) return;
|
||||
|
||||
/* Death sequence */
|
||||
if (self->flags & ENTITY_DEAD) {
|
||||
animation_set(&self->anim, &anim_flyer_death);
|
||||
@@ -289,6 +322,9 @@ static void charger_update(Entity *self, float dt, const Tilemap *map) {
|
||||
|
||||
Body *body = &self->body;
|
||||
|
||||
/* Kill if fallen off bottom of level */
|
||||
if (enemy_check_level_bottom(self, map, s_charger_em)) return;
|
||||
|
||||
/* Death sequence */
|
||||
if (self->flags & ENTITY_DEAD) {
|
||||
animation_set(&self->anim, &anim_charger_death);
|
||||
@@ -319,14 +355,10 @@ static void charger_update(Entity *self, float dt, const Tilemap *map) {
|
||||
}
|
||||
|
||||
/* Reverse at ledge */
|
||||
if (body->on_ground) {
|
||||
float cx = (cd->patrol_dir > 0) ?
|
||||
body->pos.x + body->size.x + 2.0f :
|
||||
body->pos.x - 2.0f;
|
||||
float cy = body->pos.y + body->size.y + 4.0f;
|
||||
if (!tilemap_is_solid(map, world_to_tile(cx), world_to_tile(cy))) {
|
||||
cd->patrol_dir = -cd->patrol_dir;
|
||||
}
|
||||
if (enemy_detect_cliff(body, cd->patrol_dir,
|
||||
CHARGER_PATROL_SPEED, map)) {
|
||||
cd->patrol_dir = -cd->patrol_dir;
|
||||
body->vel.x = 0;
|
||||
}
|
||||
|
||||
/* Detect player — horizontal line-of-sight */
|
||||
@@ -510,10 +542,12 @@ static int count_alive_grunts(EntityManager *em) {
|
||||
}
|
||||
|
||||
static void spawner_update(Entity *self, float dt, const Tilemap *map) {
|
||||
(void)map;
|
||||
SpawnerData *sd = (SpawnerData *)self->data;
|
||||
if (!sd) return;
|
||||
|
||||
/* Kill if fallen off bottom of level */
|
||||
if (enemy_check_level_bottom(self, map, s_spawner_em)) return;
|
||||
|
||||
/* Death sequence */
|
||||
if (self->flags & ENTITY_DEAD) {
|
||||
animation_set(&self->anim, &anim_spawner_death);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "engine/input.h"
|
||||
#include "engine/camera.h"
|
||||
#include "engine/assets.h"
|
||||
#include "engine/font.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
@@ -176,36 +177,45 @@ static Camera *s_active_camera = NULL;
|
||||
|
||||
static void damage_entity(Entity *target, int damage) {
|
||||
target->health -= damage;
|
||||
|
||||
Vec2 center = vec2(
|
||||
target->body.pos.x + target->body.size.x * 0.5f,
|
||||
target->body.pos.y + target->body.size.y * 0.5f
|
||||
);
|
||||
|
||||
if (target->health <= 0) {
|
||||
target->flags |= ENTITY_DEAD;
|
||||
|
||||
/* Death particles — centered on entity */
|
||||
Vec2 center = vec2(
|
||||
target->body.pos.x + target->body.size.x * 0.5f,
|
||||
target->body.pos.y + target->body.size.y * 0.5f
|
||||
);
|
||||
SDL_Color death_color;
|
||||
if (target->type == ENT_ENEMY_GRUNT) {
|
||||
death_color = (SDL_Color){200, 60, 60, 255}; /* red debris */
|
||||
} else if (target->type == ENT_ENEMY_FLYER) {
|
||||
death_color = (SDL_Color){140, 80, 200, 255}; /* purple puff */
|
||||
} else if (target->type == ENT_TURRET || target->type == ENT_LASER_TURRET) {
|
||||
death_color = (SDL_Color){160, 160, 160, 255}; /* metal scraps */
|
||||
} else if (target->type == ENT_ENEMY_CHARGER) {
|
||||
death_color = (SDL_Color){220, 140, 40, 255}; /* orange spark */
|
||||
} else if (target->type == ENT_SPAWNER) {
|
||||
death_color = (SDL_Color){180, 60, 180, 255}; /* purple burst */
|
||||
/* Death particles — turrets get a metal explosion, others a puff */
|
||||
if (target->type == ENT_TURRET || target->type == ENT_LASER_TURRET) {
|
||||
particle_emit_metal_explosion(center);
|
||||
} else {
|
||||
death_color = (SDL_Color){200, 200, 200, 255}; /* grey */
|
||||
SDL_Color death_color;
|
||||
if (target->type == ENT_ENEMY_GRUNT) {
|
||||
death_color = (SDL_Color){200, 60, 60, 255};
|
||||
} else if (target->type == ENT_ENEMY_FLYER) {
|
||||
death_color = (SDL_Color){140, 80, 200, 255};
|
||||
} else if (target->type == ENT_ENEMY_CHARGER) {
|
||||
death_color = (SDL_Color){220, 140, 40, 255};
|
||||
} else if (target->type == ENT_SPAWNER) {
|
||||
death_color = (SDL_Color){180, 60, 180, 255};
|
||||
} else {
|
||||
death_color = (SDL_Color){200, 200, 200, 255};
|
||||
}
|
||||
particle_emit_death_puff(center, death_color);
|
||||
}
|
||||
particle_emit_death_puff(center, death_color);
|
||||
|
||||
/* Screen shake on kill */
|
||||
/* Screen shake on kill — stronger for turret explosions */
|
||||
if (s_active_camera) {
|
||||
camera_shake(s_active_camera, 2.0f, 0.15f);
|
||||
float intensity = (target->type == ENT_TURRET ||
|
||||
target->type == ENT_LASER_TURRET) ? 3.5f : 2.0f;
|
||||
camera_shake(s_active_camera, intensity, 0.15f);
|
||||
}
|
||||
|
||||
audio_play_sound_at(s_sfx_enemy_death, 80, center, 0);
|
||||
} else if (target->type == ENT_TURRET || target->type == ENT_LASER_TURRET) {
|
||||
/* Hit marker sparks on non-lethal turret damage */
|
||||
particle_emit_hit_sparks(center);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,11 +237,14 @@ static void damage_player(Entity *player, int damage, Entity *source) {
|
||||
ppd->inv_timer = PLAYER_INV_TIME;
|
||||
player->flags |= ENTITY_INVINCIBLE;
|
||||
|
||||
/* Knockback away from source */
|
||||
/* Knockback away from source, scaled by source speed */
|
||||
if (source) {
|
||||
float knock_dir = (player->body.pos.x < source->body.pos.x) ?
|
||||
-1.0f : 1.0f;
|
||||
player->body.vel.x = knock_dir * 150.0f;
|
||||
float src_speed = fabsf(source->body.vel.x);
|
||||
float knock_str = 150.0f;
|
||||
if (src_speed > knock_str) knock_str = src_speed;
|
||||
player->body.vel.x = knock_dir * knock_str;
|
||||
player->body.vel.y = -150.0f;
|
||||
}
|
||||
}
|
||||
@@ -293,8 +306,7 @@ static void handle_collisions(EntityManager *em) {
|
||||
}
|
||||
|
||||
/* ── Enemy contact damage to player ──── */
|
||||
if (player && !(player->flags & ENTITY_INVINCIBLE) &&
|
||||
entity_is_enemy(a) && !(a->flags & ENTITY_DEAD)) {
|
||||
if (player && entity_is_enemy(a) && !(a->flags & ENTITY_DEAD)) {
|
||||
if (physics_overlap(&a->body, &player->body)) {
|
||||
/* Check if player is stomping (falling onto enemy from above) */
|
||||
bool stomping = (player->body.vel.y > 0) &&
|
||||
@@ -306,8 +318,16 @@ static void handle_collisions(EntityManager *em) {
|
||||
damage_entity(a, 2);
|
||||
if (a->flags & ENTITY_DEAD) stats_record_kill();
|
||||
player->body.vel.y = -PLAYER_JUMP_FORCE * 0.7f;
|
||||
} else {
|
||||
damage_player(player, a->damage, a);
|
||||
} else if (!(player->flags & ENTITY_INVINCIBLE)) {
|
||||
/* Charger deals extra damage and knockback while charging */
|
||||
int dmg = a->damage;
|
||||
if (a->type == ENT_ENEMY_CHARGER) {
|
||||
ChargerData *cd = (ChargerData *)a->data;
|
||||
if (cd && cd->state == CHARGER_CHARGE) {
|
||||
dmg = 2;
|
||||
}
|
||||
}
|
||||
damage_player(player, dmg, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -779,6 +799,20 @@ void level_render(Level *level, float interpolation) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw score in top-right corner */
|
||||
{
|
||||
GameStats *stats = stats_get_active();
|
||||
if (stats) {
|
||||
stats_update_score(stats);
|
||||
char score_buf[16];
|
||||
snprintf(score_buf, sizeof(score_buf), "%d", stats->score);
|
||||
int text_w = font_text_width(score_buf);
|
||||
font_draw_text(g_engine.renderer, score_buf,
|
||||
SCREEN_WIDTH - text_w - 8, 8,
|
||||
(SDL_Color){255, 220, 80, 255});
|
||||
}
|
||||
}
|
||||
|
||||
/* Flush the renderer */
|
||||
renderer_flush(cam);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "game/levelgen.h"
|
||||
#include "game/transition.h"
|
||||
#include "engine/parallax.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -1317,6 +1318,14 @@ bool levelgen_generate(Tilemap *map, const LevelGenConfig *config) {
|
||||
snprintf(map->music_path, sizeof(map->music_path), "assets/sounds/algardalgar.ogg");
|
||||
}
|
||||
|
||||
/* Transition style — interior themes use elevator, surface uses none
|
||||
* (spacecraft entity handles surface transitions). */
|
||||
if (primary_theme == THEME_PLANET_BASE || primary_theme == THEME_MARS_BASE
|
||||
|| primary_theme == THEME_SPACE_STATION) {
|
||||
map->transition_in = TRANS_ELEVATOR;
|
||||
map->transition_out = TRANS_ELEVATOR;
|
||||
}
|
||||
|
||||
/* Tileset */
|
||||
/* NOTE: tileset texture will be loaded by level_load_generated */
|
||||
|
||||
@@ -1826,6 +1835,10 @@ bool levelgen_generate_station(Tilemap *map, const LevelGenConfig *config) {
|
||||
/* Music */
|
||||
snprintf(map->music_path, sizeof(map->music_path), "assets/sounds/algardalgar.ogg");
|
||||
|
||||
/* Interior levels use elevator transitions. */
|
||||
map->transition_in = TRANS_ELEVATOR;
|
||||
map->transition_out = TRANS_ELEVATOR;
|
||||
|
||||
printf("levelgen_station: generated %dx%d level (%d segments, seed=%u, gravity=%.0f)\n",
|
||||
map->width, map->height, num_segs, s_rng_state, map->gravity);
|
||||
printf(" segments:");
|
||||
@@ -2485,6 +2498,10 @@ bool levelgen_generate_mars_base(Tilemap *map, const LevelGenConfig *config) {
|
||||
/* Music */
|
||||
snprintf(map->music_path, sizeof(map->music_path), "assets/sounds/kaffe_og_kage.ogg");
|
||||
|
||||
/* Interior levels use elevator transitions. */
|
||||
map->transition_in = TRANS_ELEVATOR;
|
||||
map->transition_out = TRANS_ELEVATOR;
|
||||
|
||||
printf("levelgen_mars_base: generated %dx%d level (%d segments, seed=%u)\n",
|
||||
map->width, map->height, num_segs, s_rng_state);
|
||||
printf(" segments:");
|
||||
@@ -2541,6 +2558,16 @@ bool levelgen_dump_lvl(const Tilemap *map, const char *path) {
|
||||
fprintf(f, "PLAYER_UNARMED\n");
|
||||
}
|
||||
|
||||
/* Transition styles */
|
||||
if (map->transition_in != TRANS_NONE) {
|
||||
fprintf(f, "TRANSITION_IN %s\n",
|
||||
transition_style_name(map->transition_in));
|
||||
}
|
||||
if (map->transition_out != TRANS_NONE) {
|
||||
fprintf(f, "TRANSITION_OUT %s\n",
|
||||
transition_style_name(map->transition_out));
|
||||
}
|
||||
|
||||
fprintf(f, "\n");
|
||||
|
||||
/* Entity spawns */
|
||||
|
||||
@@ -300,6 +300,33 @@ void player_update(Entity *self, float dt, const Tilemap *map) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* ── Double-tap down for free downward jetpack ── */
|
||||
if (!body->on_ground && input_pressed(ACTION_DOWN)) {
|
||||
if (pd->down_tap_timer > 0) {
|
||||
/* Second tap — trigger free downward dash */
|
||||
pd->down_tap_timer = 0;
|
||||
pd->dash_timer = PLAYER_DASH_DURATION;
|
||||
pd->dash_dir = vec2(0.0f, 1.0f);
|
||||
|
||||
/* Invincibility during dash + short grace period after */
|
||||
pd->inv_timer = PLAYER_DASH_DURATION + PLAYER_DASH_INV_GRACE;
|
||||
self->flags |= ENTITY_INVINCIBLE;
|
||||
|
||||
/* Jetpack burst downward */
|
||||
Vec2 exhaust_pos = vec2(
|
||||
body->pos.x + body->size.x * 0.5f,
|
||||
body->pos.y + body->size.y * 0.5f
|
||||
);
|
||||
particle_emit_jetpack_burst(exhaust_pos, pd->dash_dir);
|
||||
audio_play_sound(s_sfx_dash, 96);
|
||||
return;
|
||||
}
|
||||
pd->down_tap_timer = 0.3f; /* window for second tap */
|
||||
}
|
||||
if (pd->down_tap_timer > 0) {
|
||||
pd->down_tap_timer -= dt;
|
||||
}
|
||||
|
||||
if (input_pressed(ACTION_DASH) && pd->dash_charges > 0) {
|
||||
pd->dash_charges--;
|
||||
stats_record_dash();
|
||||
@@ -478,6 +505,7 @@ void player_update(Entity *self, float dt, const Tilemap *map) {
|
||||
|
||||
/* ── Landing detection ───────────────────── */
|
||||
if (body->on_ground && !pd->was_on_ground) {
|
||||
pd->down_tap_timer = 0; /* reset double-tap on landing */
|
||||
/* Just landed — emit dust at feet */
|
||||
Vec2 feet = vec2(
|
||||
body->pos.x + body->size.x * 0.5f,
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
|
||||
/* Invincibility after taking damage */
|
||||
#define PLAYER_INV_TIME 1.5f /* seconds of invincibility */
|
||||
#define PLAYER_DASH_INV_GRACE 0.15f /* extra invincibility after dash */
|
||||
|
||||
/* Aim direction (for shooting) */
|
||||
typedef enum AimDir {
|
||||
@@ -67,6 +68,8 @@ typedef struct PlayerData {
|
||||
AimDir aim_dir; /* current aim direction */
|
||||
bool looking_up; /* holding up without moving */
|
||||
float look_up_timer; /* how long up has been held */
|
||||
/* Down-arrow double-tap (free downward jetpack) */
|
||||
float down_tap_timer; /* time since last mid-air down press */
|
||||
/* Death / Respawn */
|
||||
float respawn_timer; /* countdown after death anim finishes */
|
||||
Vec2 spawn_point; /* where to respawn */
|
||||
|
||||
@@ -8,10 +8,10 @@ void stats_reset(GameStats *s) {
|
||||
}
|
||||
|
||||
void stats_update_score(GameStats *s) {
|
||||
int score = s->levels_completed * 100
|
||||
+ s->enemies_killed * 10
|
||||
- s->deaths * 25
|
||||
+ s->pickups_collected * 5;
|
||||
int score = s->enemies_killed * 100
|
||||
+ s->levels_completed * 500
|
||||
- s->deaths * 200
|
||||
- (int)s->time_elapsed;
|
||||
s->score = score > 0 ? score : 0;
|
||||
}
|
||||
|
||||
|
||||
351
src/game/transition.c
Normal file
351
src/game/transition.c
Normal file
@@ -0,0 +1,351 @@
|
||||
#include "game/transition.h"
|
||||
#include "engine/core.h"
|
||||
#include "engine/audio.h"
|
||||
#include "engine/particle.h"
|
||||
#include "config.h"
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* Constants
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
/* ── Elevator timing ── */
|
||||
#define ELEVATOR_CLOSE_DURATION 0.6f /* doors slide shut */
|
||||
#define ELEVATOR_HOLD_DURATION 0.3f /* closed with rumble */
|
||||
#define ELEVATOR_OPEN_DURATION 0.6f /* doors slide open */
|
||||
|
||||
#define ELEVATOR_OUT_DURATION (ELEVATOR_CLOSE_DURATION + ELEVATOR_HOLD_DURATION)
|
||||
#define ELEVATOR_IN_DURATION (ELEVATOR_HOLD_DURATION + ELEVATOR_OPEN_DURATION)
|
||||
|
||||
/* ── Teleporter timing ── */
|
||||
#define TELEPORT_DISSOLVE_DURATION 0.5f /* scanline sweep out */
|
||||
#define TELEPORT_FLASH_DURATION 0.15f /* white flash */
|
||||
#define TELEPORT_MATERIALIZE_DURATION 0.5f /* scanline sweep in */
|
||||
|
||||
#define TELEPORT_OUT_DURATION (TELEPORT_DISSOLVE_DURATION + TELEPORT_FLASH_DURATION)
|
||||
#define TELEPORT_IN_DURATION (TELEPORT_FLASH_DURATION + TELEPORT_MATERIALIZE_DURATION)
|
||||
|
||||
/* ── Scanline dissolve parameters ── */
|
||||
#define SCANLINE_HEIGHT 3 /* pixel height per band */
|
||||
#define SCANLINE_STAGGER 0.3f /* time spread between first/last band */
|
||||
|
||||
/* ── Elevator colors ── */
|
||||
#define ELEV_R 40
|
||||
#define ELEV_G 42
|
||||
#define ELEV_B 48
|
||||
|
||||
/* ── Sound effects ── */
|
||||
static Sound s_sfx_teleport;
|
||||
static bool s_sfx_loaded = false;
|
||||
|
||||
static void ensure_sfx(void) {
|
||||
if (s_sfx_loaded) return;
|
||||
s_sfx_teleport = audio_load_sound("assets/sounds/teleport.wav");
|
||||
s_sfx_loaded = true;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* Outro duration for a given style
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
static float outro_duration(TransitionStyle style) {
|
||||
switch (style) {
|
||||
case TRANS_ELEVATOR: return ELEVATOR_OUT_DURATION;
|
||||
case TRANS_TELEPORTER: return TELEPORT_OUT_DURATION;
|
||||
default: return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static float intro_duration(TransitionStyle style) {
|
||||
switch (style) {
|
||||
case TRANS_ELEVATOR: return ELEVATOR_IN_DURATION;
|
||||
case TRANS_TELEPORTER: return TELEPORT_IN_DURATION;
|
||||
default: return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* Public API
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
void transition_start_out(TransitionState *ts, TransitionStyle out_style) {
|
||||
ensure_sfx();
|
||||
ts->out_style = out_style;
|
||||
ts->in_style = TRANS_NONE;
|
||||
ts->phase = TRANS_PHASE_OUT;
|
||||
ts->timer = 0.0f;
|
||||
ts->phase_dur = outro_duration(out_style);
|
||||
ts->sound_played = false;
|
||||
}
|
||||
|
||||
void transition_set_in_style(TransitionState *ts, TransitionStyle in_style) {
|
||||
ts->in_style = in_style;
|
||||
}
|
||||
|
||||
void transition_update(TransitionState *ts, float dt, Camera *cam) {
|
||||
if (ts->phase == TRANS_IDLE || ts->phase == TRANS_PHASE_DONE) return;
|
||||
|
||||
ts->timer += dt;
|
||||
|
||||
/* ── Outro phase ── */
|
||||
if (ts->phase == TRANS_PHASE_OUT) {
|
||||
/* Play sound once at start of teleporter dissolve. */
|
||||
if (ts->out_style == TRANS_TELEPORTER && !ts->sound_played) {
|
||||
audio_play_sound(s_sfx_teleport, 80);
|
||||
ts->sound_played = true;
|
||||
}
|
||||
|
||||
/* Elevator rumble during the hold period. */
|
||||
if (ts->out_style == TRANS_ELEVATOR && cam) {
|
||||
if (ts->timer > ELEVATOR_CLOSE_DURATION) {
|
||||
camera_shake(cam, 3.0f, 0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
if (ts->timer >= ts->phase_dur) {
|
||||
ts->phase = TRANS_PHASE_LOAD;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* ── Load phase: caller must call transition_begin_intro() ── */
|
||||
if (ts->phase == TRANS_PHASE_LOAD) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* ── Intro phase ── */
|
||||
if (ts->phase == TRANS_PHASE_IN) {
|
||||
/* Elevator rumble at the start of intro (before doors open). */
|
||||
if (ts->in_style == TRANS_ELEVATOR && cam) {
|
||||
if (ts->timer < ELEVATOR_HOLD_DURATION) {
|
||||
camera_shake(cam, 2.5f, 0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
if (ts->timer >= ts->phase_dur) {
|
||||
ts->phase = TRANS_PHASE_DONE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool transition_needs_load(const TransitionState *ts) {
|
||||
return ts->phase == TRANS_PHASE_LOAD;
|
||||
}
|
||||
|
||||
void transition_begin_intro(TransitionState *ts) {
|
||||
ts->phase = TRANS_PHASE_IN;
|
||||
ts->timer = 0.0f;
|
||||
ts->phase_dur = intro_duration(ts->in_style);
|
||||
ts->sound_played = false;
|
||||
}
|
||||
|
||||
bool transition_is_done(const TransitionState *ts) {
|
||||
return ts->phase == TRANS_PHASE_DONE;
|
||||
}
|
||||
|
||||
void transition_reset(TransitionState *ts) {
|
||||
ts->phase = TRANS_IDLE;
|
||||
ts->timer = 0.0f;
|
||||
ts->phase_dur = 0.0f;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* Elevator rendering helpers
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
/* Returns door coverage 0.0 (fully open) to 1.0 (fully closed). */
|
||||
static float elevator_coverage(const TransitionState *ts) {
|
||||
if (ts->phase == TRANS_PHASE_OUT) {
|
||||
/* Closing: 0 → 1 over ELEVATOR_CLOSE_DURATION, then hold at 1. */
|
||||
float t = ts->timer / ELEVATOR_CLOSE_DURATION;
|
||||
if (t > 1.0f) t = 1.0f;
|
||||
/* Ease-in-out for smooth motion. */
|
||||
return t * t * (3.0f - 2.0f * t);
|
||||
}
|
||||
if (ts->phase == TRANS_PHASE_IN) {
|
||||
/* Hold closed during ELEVATOR_HOLD_DURATION, then open. */
|
||||
float open_t = ts->timer - ELEVATOR_HOLD_DURATION;
|
||||
if (open_t <= 0.0f) return 1.0f;
|
||||
float t = open_t / ELEVATOR_OPEN_DURATION;
|
||||
if (t > 1.0f) t = 1.0f;
|
||||
float ease = t * t * (3.0f - 2.0f * t);
|
||||
return 1.0f - ease;
|
||||
}
|
||||
/* PHASE_LOAD: fully closed. */
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
static void render_elevator(const TransitionState *ts) {
|
||||
float coverage = elevator_coverage(ts);
|
||||
if (coverage <= 0.0f) return;
|
||||
|
||||
int half_h = (int)(coverage * (float)SCREEN_HEIGHT * 0.5f + 0.5f);
|
||||
|
||||
SDL_Renderer *r = g_engine.renderer;
|
||||
SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_NONE);
|
||||
SDL_SetRenderDrawColor(r, ELEV_R, ELEV_G, ELEV_B, 255);
|
||||
|
||||
/* Top door. */
|
||||
SDL_Rect top = {0, 0, SCREEN_WIDTH, half_h};
|
||||
SDL_RenderFillRect(r, &top);
|
||||
|
||||
/* Bottom door. */
|
||||
SDL_Rect bot = {0, SCREEN_HEIGHT - half_h, SCREEN_WIDTH, half_h};
|
||||
SDL_RenderFillRect(r, &bot);
|
||||
|
||||
/* Thin bright seam at the meeting edge (visual detail). */
|
||||
if (coverage > 0.7f) {
|
||||
int seam_y = half_h - 1;
|
||||
SDL_SetRenderDrawColor(r, 100, 110, 130, 255);
|
||||
SDL_Rect seam_top = {0, seam_y, SCREEN_WIDTH, 1};
|
||||
SDL_RenderFillRect(r, &seam_top);
|
||||
|
||||
int seam_bot_y = SCREEN_HEIGHT - half_h;
|
||||
SDL_Rect seam_bot = {0, seam_bot_y, SCREEN_WIDTH, 1};
|
||||
SDL_RenderFillRect(r, &seam_bot);
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* Teleporter rendering helpers
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
/* Scanline dissolve progress: each band sweeps across the screen.
|
||||
* band_idx: which band (0 = top), total_bands: how many bands,
|
||||
* global_progress: 0.0–1.0 across the dissolve duration,
|
||||
* reverse: if true, sweep right-to-left / bottom-to-top. */
|
||||
static float band_progress(int band_idx, int total_bands,
|
||||
float global_progress, bool reverse) {
|
||||
float band_offset;
|
||||
if (reverse) {
|
||||
band_offset = (float)(total_bands - 1 - band_idx) / (float)total_bands
|
||||
* SCANLINE_STAGGER;
|
||||
} else {
|
||||
band_offset = (float)band_idx / (float)total_bands * SCANLINE_STAGGER;
|
||||
}
|
||||
float local = (global_progress - band_offset) / (1.0f - SCANLINE_STAGGER);
|
||||
if (local < 0.0f) local = 0.0f;
|
||||
if (local > 1.0f) local = 1.0f;
|
||||
return local;
|
||||
}
|
||||
|
||||
static void render_teleporter_scanlines(float global_progress,
|
||||
bool reverse) {
|
||||
int total_bands = SCREEN_HEIGHT / SCANLINE_HEIGHT;
|
||||
SDL_Renderer *r = g_engine.renderer;
|
||||
SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_NONE);
|
||||
SDL_SetRenderDrawColor(r, 0, 0, 0, 255);
|
||||
|
||||
for (int i = 0; i < total_bands; i++) {
|
||||
float bp = band_progress(i, total_bands, global_progress, reverse);
|
||||
if (bp <= 0.0f) continue;
|
||||
|
||||
int y = i * SCANLINE_HEIGHT;
|
||||
int w = (int)(bp * (float)SCREEN_WIDTH + 0.5f);
|
||||
if (w <= 0) continue;
|
||||
if (w > SCREEN_WIDTH) w = SCREEN_WIDTH;
|
||||
|
||||
/* Alternate sweep direction per band for visual interest. */
|
||||
int x = (i % 2 == 0) ? 0 : (SCREEN_WIDTH - w);
|
||||
|
||||
SDL_Rect band = {x, y, w, SCANLINE_HEIGHT};
|
||||
SDL_RenderFillRect(r, &band);
|
||||
}
|
||||
}
|
||||
|
||||
static void render_teleporter_flash(float alpha_f) {
|
||||
if (alpha_f <= 0.0f) return;
|
||||
uint8_t alpha = (uint8_t)(alpha_f * 255.0f);
|
||||
if (alpha == 0) return;
|
||||
|
||||
SDL_Renderer *r = g_engine.renderer;
|
||||
SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetRenderDrawColor(r, 255, 255, 255, alpha);
|
||||
SDL_Rect rect = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT};
|
||||
SDL_RenderFillRect(r, &rect);
|
||||
}
|
||||
|
||||
static void render_teleporter(const TransitionState *ts) {
|
||||
if (ts->phase == TRANS_PHASE_OUT) {
|
||||
/* Phase 1: scanline dissolve, then flash builds up. */
|
||||
if (ts->timer < TELEPORT_DISSOLVE_DURATION) {
|
||||
float progress = ts->timer / TELEPORT_DISSOLVE_DURATION;
|
||||
render_teleporter_scanlines(progress, false);
|
||||
} else {
|
||||
/* Dissolve complete — full black + rising flash. */
|
||||
render_teleporter_scanlines(1.0f, false);
|
||||
float flash_t = (ts->timer - TELEPORT_DISSOLVE_DURATION)
|
||||
/ TELEPORT_FLASH_DURATION;
|
||||
if (flash_t > 1.0f) flash_t = 1.0f;
|
||||
render_teleporter_flash(flash_t);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (ts->phase == TRANS_PHASE_LOAD) {
|
||||
/* Fully covered: black + white flash. */
|
||||
render_teleporter_scanlines(1.0f, false);
|
||||
render_teleporter_flash(1.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ts->phase == TRANS_PHASE_IN) {
|
||||
/* Phase 2: flash fades out, then scanlines recede. */
|
||||
if (ts->timer < TELEPORT_FLASH_DURATION) {
|
||||
/* Flash fading over black. */
|
||||
render_teleporter_scanlines(1.0f, true);
|
||||
float flash_t = 1.0f - ts->timer / TELEPORT_FLASH_DURATION;
|
||||
render_teleporter_flash(flash_t);
|
||||
} else {
|
||||
/* Scanlines receding to reveal the new level. */
|
||||
float mat_t = (ts->timer - TELEPORT_FLASH_DURATION)
|
||||
/ TELEPORT_MATERIALIZE_DURATION;
|
||||
if (mat_t > 1.0f) mat_t = 1.0f;
|
||||
/* Progress inverted: 1.0 = fully covered, 0.0 = revealed. */
|
||||
render_teleporter_scanlines(1.0f - mat_t, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* Render dispatch
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
void transition_render(const TransitionState *ts) {
|
||||
if (ts->phase == TRANS_IDLE || ts->phase == TRANS_PHASE_DONE) return;
|
||||
|
||||
/* Pick the active style based on which phase we are in. */
|
||||
TransitionStyle style = (ts->phase == TRANS_PHASE_IN)
|
||||
? ts->in_style : ts->out_style;
|
||||
|
||||
switch (style) {
|
||||
case TRANS_ELEVATOR: render_elevator(ts); break;
|
||||
case TRANS_TELEPORTER: render_teleporter(ts); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* Name ↔ enum conversion
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
TransitionStyle transition_style_from_name(const char *name) {
|
||||
if (!name) return TRANS_NONE;
|
||||
if (strcmp(name, "spacecraft") == 0) return TRANS_SPACECRAFT;
|
||||
if (strcmp(name, "elevator") == 0) return TRANS_ELEVATOR;
|
||||
if (strcmp(name, "teleporter") == 0) return TRANS_TELEPORTER;
|
||||
return TRANS_NONE;
|
||||
}
|
||||
|
||||
const char *transition_style_name(TransitionStyle style) {
|
||||
switch (style) {
|
||||
case TRANS_SPACECRAFT: return "spacecraft";
|
||||
case TRANS_ELEVATOR: return "elevator";
|
||||
case TRANS_TELEPORTER: return "teleporter";
|
||||
default: return "none";
|
||||
}
|
||||
}
|
||||
68
src/game/transition.h
Normal file
68
src/game/transition.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#ifndef JNR_TRANSITION_H
|
||||
#define JNR_TRANSITION_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "engine/camera.h"
|
||||
#include "config.h"
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* Level transition animations
|
||||
*
|
||||
* Two-phase system: outro (on the old level) then
|
||||
* intro (on the new level). The caller is responsible
|
||||
* for freeing the old level and loading the new one
|
||||
* between phases (when transition_needs_load() is true).
|
||||
*
|
||||
* TransitionStyle enum is defined in config.h so that
|
||||
* both engine (tilemap) and game code can reference it
|
||||
* without circular includes.
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
typedef enum TransitionPhase {
|
||||
TRANS_IDLE, /* no transition active */
|
||||
TRANS_PHASE_OUT, /* outro animation on the old level */
|
||||
TRANS_PHASE_LOAD, /* ready for the caller to swap levels */
|
||||
TRANS_PHASE_IN, /* intro animation on the new level */
|
||||
TRANS_PHASE_DONE, /* transition complete, return to play */
|
||||
} TransitionPhase;
|
||||
|
||||
typedef struct TransitionState {
|
||||
TransitionStyle out_style; /* outro style (from departing level) */
|
||||
TransitionStyle in_style; /* intro style (from arriving level) */
|
||||
TransitionPhase phase;
|
||||
float timer; /* elapsed time in current phase */
|
||||
float phase_dur; /* total duration of current phase */
|
||||
bool sound_played; /* flag to fire a sound once per phase */
|
||||
} TransitionState;
|
||||
|
||||
/* Start the outro phase. out_style comes from the departing level. */
|
||||
void transition_start_out(TransitionState *ts, TransitionStyle out_style);
|
||||
|
||||
/* Set the intro style (call after loading the new level). */
|
||||
void transition_set_in_style(TransitionState *ts, TransitionStyle in_style);
|
||||
|
||||
/* Advance the transition. cam may be NULL during the load gap. */
|
||||
void transition_update(TransitionState *ts, float dt, Camera *cam);
|
||||
|
||||
/* True when the outro is done and the caller should swap levels. */
|
||||
bool transition_needs_load(const TransitionState *ts);
|
||||
|
||||
/* Acknowledge the load — advance to the intro phase. */
|
||||
void transition_begin_intro(TransitionState *ts);
|
||||
|
||||
/* True when the full transition is finished. */
|
||||
bool transition_is_done(const TransitionState *ts);
|
||||
|
||||
/* Render the transition overlay (call AFTER rendering the level). */
|
||||
void transition_render(const TransitionState *ts);
|
||||
|
||||
/* Reset to idle. */
|
||||
void transition_reset(TransitionState *ts);
|
||||
|
||||
/* Parse a style name string ("none", "elevator", etc.). */
|
||||
TransitionStyle transition_style_from_name(const char *name);
|
||||
|
||||
/* Return the directive string for a style. */
|
||||
const char *transition_style_name(TransitionStyle style);
|
||||
|
||||
#endif /* JNR_TRANSITION_H */
|
||||
174
src/main.c
174
src/main.c
@@ -1,11 +1,13 @@
|
||||
#include "engine/core.h"
|
||||
#include "engine/input.h"
|
||||
#include "engine/font.h"
|
||||
#include "engine/debuglog.h"
|
||||
#include "game/level.h"
|
||||
#include "game/levelgen.h"
|
||||
#include "game/editor.h"
|
||||
#include "game/stats.h"
|
||||
#include "game/analytics.h"
|
||||
#include "game/transition.h"
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@@ -24,6 +26,7 @@ typedef enum GameMode {
|
||||
MODE_PLAY,
|
||||
MODE_EDITOR,
|
||||
MODE_PAUSED,
|
||||
MODE_TRANSITION,
|
||||
} GameMode;
|
||||
|
||||
static Level s_level;
|
||||
@@ -32,6 +35,7 @@ static GameMode s_mode = MODE_PLAY;
|
||||
static bool s_use_procgen = false;
|
||||
static bool s_dump_lvl = false;
|
||||
static bool s_use_editor = false;
|
||||
static bool s_use_debuglog = false;
|
||||
static uint32_t s_gen_seed = 0;
|
||||
static char s_edit_path[256] = {0};
|
||||
static char s_level_path[ASSET_PATH_MAX] = {0}; /* path of active play-mode level */
|
||||
@@ -56,6 +60,10 @@ static bool s_session_active = false;
|
||||
#define PAUSE_ITEM_COUNT 3
|
||||
static int s_pause_selection = 0; /* 0=Resume, 1=Restart, 2=Quit */
|
||||
|
||||
/* ── Level transition state ── */
|
||||
static TransitionState s_transition;
|
||||
static char s_pending_target[ASSET_PATH_MAX] = {0}; /* exit target stashed during transition */
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
/* JS-initiated level load request (level-select dropdown in shell). */
|
||||
static int s_js_load_request = 0;
|
||||
@@ -88,6 +96,7 @@ static const char *theme_name(LevelTheme t) {
|
||||
static bool load_level_file(const char *path) {
|
||||
if (!level_load(&s_level, path)) return false;
|
||||
snprintf(s_level_path, sizeof(s_level_path), "%s", path);
|
||||
debuglog_set_level(&s_level, path);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -186,6 +195,7 @@ static void load_generated_level(void) {
|
||||
g_engine.running = false;
|
||||
}
|
||||
s_level_path[0] = '\0'; /* generated levels have no file path */
|
||||
debuglog_set_level(&s_level, "generated");
|
||||
}
|
||||
|
||||
static void load_station_level(void) {
|
||||
@@ -211,6 +221,7 @@ static void load_station_level(void) {
|
||||
g_engine.running = false;
|
||||
}
|
||||
s_level_path[0] = '\0'; /* generated levels have no file path */
|
||||
debuglog_set_level(&s_level, "generated:station");
|
||||
}
|
||||
|
||||
static void load_mars_base_level(void) {
|
||||
@@ -242,6 +253,7 @@ static void load_mars_base_level(void) {
|
||||
g_engine.running = false;
|
||||
}
|
||||
s_level_path[0] = '\0';
|
||||
debuglog_set_level(&s_level, "generated:mars_base");
|
||||
}
|
||||
|
||||
/* ── Analytics session helpers ── */
|
||||
@@ -262,6 +274,7 @@ static void end_session(const char *reason) {
|
||||
/* ── Switch to editor mode ── */
|
||||
static void enter_editor(void) {
|
||||
if (s_mode == MODE_PLAY) {
|
||||
debuglog_set_level(NULL, NULL);
|
||||
level_free(&s_level);
|
||||
}
|
||||
s_mode = MODE_EDITOR;
|
||||
@@ -301,6 +314,7 @@ static void enter_test_play(void) {
|
||||
|
||||
/* ── Return from test play to editor ── */
|
||||
static void return_to_editor(void) {
|
||||
debuglog_set_level(NULL, NULL);
|
||||
level_free(&s_level);
|
||||
s_mode = MODE_EDITOR;
|
||||
s_testing_from_editor = false;
|
||||
@@ -309,6 +323,7 @@ static void return_to_editor(void) {
|
||||
|
||||
/* ── Restart current level (file-based or generated) ── */
|
||||
static void restart_level(void) {
|
||||
debuglog_set_level(NULL, NULL);
|
||||
level_free(&s_level);
|
||||
if (s_level_path[0]) {
|
||||
if (!load_level_file(s_level_path)) {
|
||||
@@ -321,6 +336,49 @@ static void restart_level(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Level load dispatch — loads the next level based on target string ── */
|
||||
static void dispatch_level_load(const char *target) {
|
||||
debuglog_set_level(NULL, NULL);
|
||||
if (target[0] == '\0') {
|
||||
/* Empty target = victory / end of game. */
|
||||
printf("Level complete! (no next level)\n");
|
||||
end_session("completed");
|
||||
level_free(&s_level);
|
||||
s_station_depth = 0;
|
||||
s_mars_depth = 0;
|
||||
if (!load_level_file("assets/levels/moon01.lvl")) {
|
||||
g_engine.running = false;
|
||||
}
|
||||
begin_session();
|
||||
} else if (strcmp(target, "generate") == 0) {
|
||||
printf("Transitioning to generated level\n");
|
||||
level_free(&s_level);
|
||||
s_gen_seed = (uint32_t)time(NULL);
|
||||
load_generated_level();
|
||||
} else if (strcmp(target, "generate:station") == 0) {
|
||||
printf("Transitioning to space station level\n");
|
||||
level_free(&s_level);
|
||||
s_gen_seed = (uint32_t)time(NULL);
|
||||
load_station_level();
|
||||
} else if (strcmp(target, "generate:mars_base") == 0) {
|
||||
printf("Transitioning to Mars Base level\n");
|
||||
level_free(&s_level);
|
||||
s_gen_seed = (uint32_t)time(NULL);
|
||||
load_mars_base_level();
|
||||
} else {
|
||||
printf("Transitioning to: %s\n", target);
|
||||
char path[ASSET_PATH_MAX];
|
||||
snprintf(path, sizeof(path), "%s", target);
|
||||
level_free(&s_level);
|
||||
if (!load_level_file(path)) {
|
||||
fprintf(stderr, "Failed to load next level: %s\n", path);
|
||||
if (!load_level_file("assets/levels/moon01.lvl")) {
|
||||
g_engine.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* Game callbacks
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
@@ -392,13 +450,26 @@ static void game_update(float dt) {
|
||||
/* Handle deferred level load from JS shell dropdown. */
|
||||
if (s_js_load_request && s_js_load_path[0]) {
|
||||
s_js_load_request = 0;
|
||||
|
||||
if (s_mode == MODE_EDITOR) {
|
||||
/* Load the selected level into the editor, not gameplay. */
|
||||
if (!editor_load(&s_editor, s_js_load_path)) {
|
||||
fprintf(stderr, "Failed to load level in editor: %s\n",
|
||||
s_js_load_path);
|
||||
}
|
||||
snprintf(s_edit_path, sizeof(s_edit_path), "%s", s_js_load_path);
|
||||
s_js_load_path[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
end_session("quit");
|
||||
|
||||
/* Tear down whatever mode we are in. */
|
||||
if (s_mode == MODE_PLAY || s_mode == MODE_PAUSED) {
|
||||
if (s_mode == MODE_PLAY || s_mode == MODE_PAUSED
|
||||
|| s_mode == MODE_TRANSITION) {
|
||||
debuglog_set_level(NULL, NULL);
|
||||
transition_reset(&s_transition);
|
||||
level_free(&s_level);
|
||||
} else if (s_mode == MODE_EDITOR) {
|
||||
editor_free(&s_editor);
|
||||
}
|
||||
|
||||
s_mode = MODE_PLAY;
|
||||
@@ -440,8 +511,35 @@ static void game_update(float dt) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_mode == MODE_TRANSITION) {
|
||||
transition_update(&s_transition, dt, &s_level.camera);
|
||||
|
||||
/* Outro finished — swap levels. */
|
||||
if (transition_needs_load(&s_transition)) {
|
||||
dispatch_level_load(s_pending_target);
|
||||
s_pending_target[0] = '\0';
|
||||
|
||||
/* Use the new level's intro style. */
|
||||
TransitionStyle in_style = s_level.map.transition_in;
|
||||
transition_set_in_style(&s_transition, in_style);
|
||||
transition_begin_intro(&s_transition);
|
||||
}
|
||||
|
||||
/* Intro finished — return to play. */
|
||||
if (transition_is_done(&s_transition)) {
|
||||
transition_reset(&s_transition);
|
||||
s_mode = MODE_PLAY;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* ── Play mode ── */
|
||||
|
||||
/* F12: dump debug log to text file. */
|
||||
if (input_key_pressed(SDL_SCANCODE_F12) && debuglog_is_enabled()) {
|
||||
debuglog_dump("debug_log.txt");
|
||||
}
|
||||
|
||||
/* Pause on escape (return to editor during test play) */
|
||||
if (input_pressed(ACTION_PAUSE)) {
|
||||
if (s_testing_from_editor) {
|
||||
@@ -457,6 +555,7 @@ static void game_update(float dt) {
|
||||
if (!s_testing_from_editor && input_key_pressed(SDL_SCANCODE_E)) {
|
||||
/* Load the current level file into the editor if available */
|
||||
snprintf(s_edit_path, sizeof(s_edit_path), "%s", s_level_path);
|
||||
debuglog_set_level(NULL, NULL);
|
||||
level_free(&s_level);
|
||||
enter_editor();
|
||||
return;
|
||||
@@ -468,6 +567,7 @@ static void game_update(float dt) {
|
||||
if (r_pressed && !r_was_pressed) {
|
||||
printf("\n=== Regenerating level ===\n");
|
||||
end_session("quit");
|
||||
debuglog_set_level(NULL, NULL);
|
||||
level_free(&s_level);
|
||||
s_gen_seed = (uint32_t)time(NULL);
|
||||
s_use_procgen = true;
|
||||
@@ -492,49 +592,16 @@ static void game_update(float dt) {
|
||||
s_stats.levels_completed++;
|
||||
}
|
||||
|
||||
if (target[0] == '\0') {
|
||||
/* Empty target = victory / end of game */
|
||||
printf("Level complete! (no next level)\n");
|
||||
end_session("completed");
|
||||
/* Loop back to the beginning, reset progression state */
|
||||
level_free(&s_level);
|
||||
s_station_depth = 0;
|
||||
s_mars_depth = 0;
|
||||
if (!load_level_file("assets/levels/moon01.lvl")) {
|
||||
g_engine.running = false;
|
||||
}
|
||||
begin_session();
|
||||
} else if (strcmp(target, "generate") == 0) {
|
||||
/* Procedurally generated next level */
|
||||
printf("Transitioning to generated level\n");
|
||||
level_free(&s_level);
|
||||
s_gen_seed = (uint32_t)time(NULL);
|
||||
load_generated_level();
|
||||
} else if (strcmp(target, "generate:station") == 0) {
|
||||
/* Procedurally generated space station level */
|
||||
printf("Transitioning to space station level\n");
|
||||
level_free(&s_level);
|
||||
s_gen_seed = (uint32_t)time(NULL);
|
||||
load_station_level();
|
||||
} else if (strcmp(target, "generate:mars_base") == 0) {
|
||||
/* Procedurally generated Mars Base level */
|
||||
printf("Transitioning to Mars Base level\n");
|
||||
level_free(&s_level);
|
||||
s_gen_seed = (uint32_t)time(NULL);
|
||||
load_mars_base_level();
|
||||
TransitionStyle out_style = s_level.map.transition_out;
|
||||
|
||||
if (out_style == TRANS_ELEVATOR || out_style == TRANS_TELEPORTER) {
|
||||
/* Animated transition: stash target, start outro. */
|
||||
snprintf(s_pending_target, sizeof(s_pending_target), "%s", target);
|
||||
transition_start_out(&s_transition, out_style);
|
||||
s_mode = MODE_TRANSITION;
|
||||
} else {
|
||||
/* Load a specific level file */
|
||||
printf("Transitioning to: %s\n", target);
|
||||
char path[ASSET_PATH_MAX];
|
||||
snprintf(path, sizeof(path), "%s", target);
|
||||
level_free(&s_level);
|
||||
if (!load_level_file(path)) {
|
||||
fprintf(stderr, "Failed to load next level: %s\n", path);
|
||||
/* Fallback to moon01 */
|
||||
if (!load_level_file("assets/levels/moon01.lvl")) {
|
||||
g_engine.running = false;
|
||||
}
|
||||
}
|
||||
/* Instant transition (none or spacecraft-driven). */
|
||||
dispatch_level_load(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -587,6 +654,10 @@ static void game_render(float interpolation) {
|
||||
/* Render frozen game frame, then overlay the pause menu. */
|
||||
level_render(&s_level, interpolation);
|
||||
pause_render();
|
||||
} else if (s_mode == MODE_TRANSITION) {
|
||||
/* Render the level (frozen) with the transition overlay on top. */
|
||||
level_render(&s_level, interpolation);
|
||||
transition_render(&s_transition);
|
||||
} else {
|
||||
level_render(&s_level, interpolation);
|
||||
}
|
||||
@@ -594,11 +665,13 @@ static void game_render(float interpolation) {
|
||||
|
||||
static void game_shutdown(void) {
|
||||
end_session("quit");
|
||||
debuglog_set_level(NULL, NULL);
|
||||
|
||||
/* Always free both — editor may have been initialized even if we're
|
||||
* currently in play mode (e.g. shutdown during test play). editor_free
|
||||
* and level_free are safe to call on zeroed/already-freed structs. */
|
||||
if (s_mode == MODE_PLAY || s_mode == MODE_PAUSED || s_testing_from_editor) {
|
||||
if (s_mode == MODE_PLAY || s_mode == MODE_PAUSED
|
||||
|| s_mode == MODE_TRANSITION || s_testing_from_editor) {
|
||||
level_free(&s_level);
|
||||
}
|
||||
if (s_mode == MODE_EDITOR || s_use_editor) {
|
||||
@@ -621,6 +694,8 @@ int main(int argc, char *argv[]) {
|
||||
if (i + 1 < argc) {
|
||||
s_gen_seed = (uint32_t)atoi(argv[++i]);
|
||||
}
|
||||
} else if (strcmp(argv[i], "--debug-log") == 0) {
|
||||
s_use_debuglog = true;
|
||||
} else if (strcmp(argv[i], "--edit") == 0 || strcmp(argv[i], "-e") == 0) {
|
||||
s_use_editor = true;
|
||||
/* Optional: next arg is a file path */
|
||||
@@ -633,6 +708,7 @@ int main(int argc, char *argv[]) {
|
||||
printf(" --dump, -d Dump generated level to assets/levels/generated.lvl\n");
|
||||
printf(" --seed N, -s N Set RNG seed for generation\n");
|
||||
printf(" --edit [file], -e [file] Open level editor (optionally load a .lvl file)\n");
|
||||
printf(" --debug-log Record game state every tick (F12 to dump)\n");
|
||||
printf("\nIn-game:\n");
|
||||
printf(" R Regenerate level with new random seed\n");
|
||||
printf(" E Open level editor\n");
|
||||
@@ -685,6 +761,11 @@ int main(int argc, char *argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
debuglog_init();
|
||||
if (s_use_debuglog) {
|
||||
debuglog_enable();
|
||||
}
|
||||
|
||||
engine_set_callbacks((GameCallbacks){
|
||||
.init = game_init,
|
||||
.update = game_update,
|
||||
@@ -693,6 +774,7 @@ int main(int argc, char *argv[]) {
|
||||
});
|
||||
|
||||
engine_run();
|
||||
debuglog_shutdown();
|
||||
engine_shutdown();
|
||||
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user