Add pause menu, laser turret, charger/spawner enemies, and Mars campaign
Implement four feature phases: Phase 1 - Pause menu: extract bitmap font into shared engine/font module, add MODE_PAUSED with Resume/Restart/Quit overlay. Phase 2 - Laser turret hazard: ENT_LASER_TURRET with charge/fire/ cooldown state machine, per-pixel beam raycast, two variants (fixed and tracking). Registered in entity registry with editor icons. Phase 3 - Charger and Spawner enemies: charger ground patrol with detect/telegraph/charge/stun cycle (2s charge timeout), spawner that periodically creates grunts up to a global cap of 3. Phase 4 - Mars campaign: two handcrafted levels (mars01 surface, mars02 base), mars_tileset.png, PARALLAX_STYLE_MARS with salmon sky and red mesas, THEME_MARS_SURFACE/THEME_MARS_BASE for the procedural generator with per-theme gravity/tileset/parallax. Moon campaign now chains moon03 -> mars01 -> mars02 -> victory. Also fix review findings: deterministic seed on generated level restart, NULL checks on calloc in spawn functions, charge timeout to prevent infinite charge on flat terrain, and stop suppressing stderr in Makefile web-serve target so real errors are visible.
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
#include "engine/renderer.h"
|
||||
#include "engine/assets.h"
|
||||
#include "engine/camera.h"
|
||||
#include "engine/font.h"
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -104,124 +105,7 @@ void editor_load_vfs_file(const char *path) {
|
||||
|
||||
#endif /* __EMSCRIPTEN__ */
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* Minimal 4x6 bitmap font
|
||||
*
|
||||
* Each character is 4 pixels wide, 6 pixels tall.
|
||||
* Stored as 6 rows of 4 bits (packed in a uint32_t).
|
||||
* Covers ASCII 32-95 (space through underscore).
|
||||
* Lowercase maps to uppercase automatically.
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
#define FONT_W 4
|
||||
#define FONT_H 7
|
||||
|
||||
/* 4-bit rows packed: row0 in bits 20-23, row1 in 16-19, etc.
|
||||
* Bit order: MSB = leftmost pixel */
|
||||
static const uint32_t s_font_glyphs[64] = {
|
||||
/* */ 0x000000,
|
||||
/* ! */ 0x4444404,
|
||||
/* " */ 0xAA0000,
|
||||
/* # */ 0xAFAFA0,
|
||||
/* $ */ 0x4E6E40, /* simplified $ */
|
||||
/* % */ 0x924924, /* simplified % */
|
||||
/* & */ 0x4A4AC0,
|
||||
/* ' */ 0x440000,
|
||||
/* ( */ 0x248840,
|
||||
/* ) */ 0x842240,
|
||||
/* * */ 0xA4A000,
|
||||
/* + */ 0x04E400,
|
||||
/* , */ 0x000048,
|
||||
/* - */ 0x00E000,
|
||||
/* . */ 0x000040,
|
||||
/* / */ 0x224880,
|
||||
/* 0 */ 0x6999960,
|
||||
/* 1 */ 0x2622620,
|
||||
/* 2 */ 0x6912460,
|
||||
/* 3 */ 0x6921960,
|
||||
/* 4 */ 0x2AAF220,
|
||||
/* 5 */ 0xF88E1E0,
|
||||
/* 6 */ 0x688E960,
|
||||
/* 7 */ 0xF112440,
|
||||
/* 8 */ 0x6966960,
|
||||
/* 9 */ 0x6997120,
|
||||
/* : */ 0x040400,
|
||||
/* ; */ 0x040480,
|
||||
/* < */ 0x248420,
|
||||
/* = */ 0x0E0E00,
|
||||
/* > */ 0x842480,
|
||||
/* ? */ 0x6920400,
|
||||
/* @ */ 0x69B9860,
|
||||
/* A */ 0x699F990,
|
||||
/* B */ 0xE99E9E0,
|
||||
/* C */ 0x6988960,
|
||||
/* D */ 0xE999E00,
|
||||
/* E */ 0xF8E8F00, /* simplified E/F overlap */
|
||||
/* F */ 0xF8E8800,
|
||||
/* G */ 0x698B960,
|
||||
/* H */ 0x99F9900,
|
||||
/* I */ 0xE444E00,
|
||||
/* J */ 0x7111960,
|
||||
/* K */ 0x9ACA900,
|
||||
/* L */ 0x8888F00,
|
||||
/* M */ 0x9FF9900,
|
||||
/* N */ 0x9DDB900,
|
||||
/* O */ 0x6999600,
|
||||
/* P */ 0xE99E800,
|
||||
/* Q */ 0x6999A70,
|
||||
/* R */ 0xE99EA90,
|
||||
/* S */ 0x698E960, /* reuse from earlier; close enough */
|
||||
/* T */ 0xF444400,
|
||||
/* U */ 0x9999600,
|
||||
/* V */ 0x999A400,
|
||||
/* W */ 0x999FF90, /* simplified W */
|
||||
/* X */ 0x996690, /* simplified X */
|
||||
/* Y */ 0x996440,
|
||||
/* Z */ 0xF12480, /* simplified Z */
|
||||
/* [ */ 0x688860,
|
||||
/* \ */ 0x884220,
|
||||
/* ] */ 0x622260,
|
||||
/* ^ */ 0x4A0000,
|
||||
/* _ */ 0x00000F,
|
||||
};
|
||||
|
||||
static void draw_char(SDL_Renderer *r, char ch, int x, int y, SDL_Color col) {
|
||||
int idx = 0;
|
||||
if (ch >= 'a' && ch <= 'z') ch -= 32; /* to uppercase */
|
||||
if (ch >= 32 && ch <= 95) idx = ch - 32;
|
||||
else return;
|
||||
|
||||
uint32_t glyph = s_font_glyphs[idx];
|
||||
SDL_SetRenderDrawColor(r, col.r, col.g, col.b, col.a);
|
||||
|
||||
for (int row = 0; row < FONT_H; row++) {
|
||||
/* Extract 4 bits for this row */
|
||||
int shift = (FONT_H - 1 - row) * 4;
|
||||
int bits = (glyph >> shift) & 0xF;
|
||||
for (int col_bit = 0; col_bit < FONT_W; col_bit++) {
|
||||
if (bits & (1 << (FONT_W - 1 - col_bit))) {
|
||||
SDL_RenderDrawPoint(r, x + col_bit, y + row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_text(SDL_Renderer *r, const char *text, int x, int y, SDL_Color col) {
|
||||
while (*text) {
|
||||
draw_char(r, *text, x, y, col);
|
||||
x += FONT_W + 1; /* 1px spacing */
|
||||
text++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Unused for now but useful for future centered text layouts */
|
||||
#if 0
|
||||
static int text_width(const char *text) {
|
||||
int len = (int)strlen(text);
|
||||
if (len == 0) return 0;
|
||||
return len * (FONT_W + 1) - 1;
|
||||
}
|
||||
#endif
|
||||
/* Bitmap font provided by engine/font.h (included above). */
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* 8x8 pixel mini-icons for the entity palette
|
||||
@@ -263,6 +147,14 @@ static const uint64_t s_icon_bitmaps[ICON_COUNT] = {
|
||||
0x001C3E7F7F3E1C00ULL,
|
||||
/* ICON_SPACECRAFT: ship */
|
||||
0x0018183C7E7E2400ULL,
|
||||
/* ICON_LASER: laser turret (box with beam line) */
|
||||
0x003C3C18187E0000ULL,
|
||||
/* ICON_LASER_TRACK: tracking laser (box with rotating beam) */
|
||||
0x003C3C1818660000ULL,
|
||||
/* ICON_CHARGER: arrow/charging creature */
|
||||
0x0018187E7E181800ULL,
|
||||
/* ICON_SPAWNER: pulsing core with dots */
|
||||
0x24003C3C3C002400ULL,
|
||||
};
|
||||
|
||||
static void draw_icon(SDL_Renderer *r, EditorIcon icon,
|
||||
@@ -1429,7 +1321,7 @@ void editor_render(Editor *ed, float interpolation) {
|
||||
draw_icon(r, (EditorIcon)reg->icon,
|
||||
(int)sp.x + 1, (int)sp.y + 1, COL_TEXT);
|
||||
} else if (reg && reg->display[0] && zw >= 6) {
|
||||
draw_char(r, reg->display[0], (int)sp.x + 1, (int)sp.y + 1, COL_TEXT);
|
||||
font_draw_char(r, reg->display[0], (int)sp.x + 1, (int)sp.y + 1, COL_TEXT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1441,7 +1333,7 @@ void editor_render(Editor *ed, float interpolation) {
|
||||
SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND);
|
||||
SDL_Rect sr = {(int)sp.x, (int)sp.y, (int)(zs + 0.5f), (int)(zs + 0.5f)};
|
||||
SDL_RenderDrawRect(r, &sr);
|
||||
draw_text(r, "SP", (int)sp.x + 1, (int)sp.y + 1, COL_SPAWN);
|
||||
font_draw_text(r, "SP", (int)sp.x + 1, (int)sp.y + 1, COL_SPAWN);
|
||||
SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_NONE);
|
||||
}
|
||||
|
||||
@@ -1458,9 +1350,9 @@ void editor_render(Editor *ed, float interpolation) {
|
||||
SDL_RenderFillRect(r, &er);
|
||||
SDL_SetRenderDrawColor(r, COL_EXIT.r, COL_EXIT.g, COL_EXIT.b, 220);
|
||||
SDL_RenderDrawRect(r, &er);
|
||||
draw_text(r, "EXIT", (int)sp.x + 1, (int)sp.y + 1, COL_EXIT);
|
||||
font_draw_text(r, "EXIT", (int)sp.x + 1, (int)sp.y + 1, COL_EXIT);
|
||||
if (ez->target[0]) {
|
||||
draw_text(r, ez->target, (int)sp.x + 1, (int)sp.y + 8, COL_EXIT);
|
||||
font_draw_text(r, ez->target, (int)sp.x + 1, (int)sp.y + 8, COL_EXIT);
|
||||
}
|
||||
SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_NONE);
|
||||
}
|
||||
@@ -1514,7 +1406,7 @@ void editor_render(Editor *ed, float interpolation) {
|
||||
for (int i = 0; i < TOOL_COUNT; i++) {
|
||||
int bx = i * 35 + 2;
|
||||
SDL_Color tc = (i == (int)ed->tool) ? COL_HIGHLIGHT : COL_TEXT_DIM;
|
||||
draw_text(r, s_tool_names[i], bx, text_y, tc);
|
||||
font_draw_text(r, s_tool_names[i], bx, text_y, tc);
|
||||
}
|
||||
|
||||
/* Separator */
|
||||
@@ -1527,17 +1419,17 @@ void editor_render(Editor *ed, float interpolation) {
|
||||
for (int i = 0; i < EDITOR_LAYER_COUNT; i++) {
|
||||
int bx = layer_start + i * 25;
|
||||
SDL_Color lc = (i == (int)ed->active_layer) ? COL_HIGHLIGHT : COL_TEXT_DIM;
|
||||
draw_text(r, s_layer_names[i], bx, text_y, lc);
|
||||
font_draw_text(r, s_layer_names[i], bx, text_y, lc);
|
||||
}
|
||||
|
||||
/* Grid & Layers indicators */
|
||||
int grid_x = layer_start + EDITOR_LAYER_COUNT * 25 + 4;
|
||||
draw_text(r, ed->show_grid ? "[G]RID" : "[G]rid", grid_x, text_y,
|
||||
font_draw_text(r, ed->show_grid ? "[G]RID" : "[G]rid", grid_x, text_y,
|
||||
ed->show_grid ? COL_TEXT : COL_TEXT_DIM);
|
||||
|
||||
/* Tileset switch hint */
|
||||
int ts_x = grid_x + 7 * (FONT_W + 1) + 4;
|
||||
draw_text(r, "[T]SET", ts_x, text_y, COL_TEXT_DIM);
|
||||
font_draw_text(r, "[T]SET", ts_x, text_y, COL_TEXT_DIM);
|
||||
}
|
||||
|
||||
/* ── Right palette panel ── */
|
||||
@@ -1565,7 +1457,7 @@ void editor_render(Editor *ed, float interpolation) {
|
||||
{
|
||||
const char *ts_name = strrchr(ed->map.tileset_path, '/');
|
||||
ts_name = ts_name ? ts_name + 1 : ed->map.tileset_path;
|
||||
draw_text(r, ts_name[0] ? ts_name : "TILES",
|
||||
font_draw_text(r, ts_name[0] ? ts_name : "TILES",
|
||||
px + 2, py + (label_h - FONT_H) / 2, COL_TEXT);
|
||||
}
|
||||
|
||||
@@ -1645,10 +1537,10 @@ void editor_render(Editor *ed, float interpolation) {
|
||||
SDL_Color fc = (flags & TILE_HAZARD) ? (SDL_Color){255, 80, 40, 255} :
|
||||
(flags & TILE_PLATFORM) ? (SDL_Color){80, 200, 255, 255} :
|
||||
(flags & TILE_SOLID) ? COL_TEXT : COL_TEXT_DIM;
|
||||
draw_text(r, fname, px + 2, ent_section_y - FONT_H - 2, fc);
|
||||
font_draw_text(r, fname, px + 2, ent_section_y - FONT_H - 2, fc);
|
||||
/* Show [F] hint */
|
||||
int fw = (int)strlen(fname) * (FONT_W + 1);
|
||||
draw_text(r, "[F]", px + 2 + fw + 2, ent_section_y - FONT_H - 2, COL_TEXT_DIM);
|
||||
font_draw_text(r, "[F]", px + 2 + fw + 2, ent_section_y - FONT_H - 2, COL_TEXT_DIM);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1659,7 +1551,7 @@ void editor_render(Editor *ed, float interpolation) {
|
||||
/* ── Entity palette (bottom section) ── */
|
||||
{
|
||||
int label_h = FONT_H + 6;
|
||||
draw_text(r, "ENTITIES", px + 2, ent_section_y + (label_h - FONT_H) / 2, COL_TEXT);
|
||||
font_draw_text(r, "ENTITIES", px + 2, ent_section_y + (label_h - FONT_H) / 2, COL_TEXT);
|
||||
|
||||
int pal_y_start = ent_section_y + label_h;
|
||||
int ent_area_h = py + ph - pal_y_start;
|
||||
@@ -1685,7 +1577,7 @@ void editor_render(Editor *ed, float interpolation) {
|
||||
|
||||
/* Name */
|
||||
SDL_Color nc = (i == ed->selected_entity) ? COL_HIGHLIGHT : COL_TEXT;
|
||||
draw_text(r, ent->display, px + 13, ey + 2, nc);
|
||||
font_draw_text(r, ent->display, px + 13, ey + 2, nc);
|
||||
}
|
||||
|
||||
SDL_RenderSetClipRect(r, NULL);
|
||||
@@ -1712,6 +1604,6 @@ void editor_render(Editor *ed, float interpolation) {
|
||||
ed->camera.zoom * 100.0f,
|
||||
ed->has_file ? ed->file_path : "new level",
|
||||
ed->dirty ? " *" : "");
|
||||
draw_text(r, status, 2, sy + (EDITOR_STATUS_H - FONT_H) / 2, COL_TEXT);
|
||||
font_draw_text(r, status, 2, sy + (EDITOR_STATUS_H - FONT_H) / 2, COL_TEXT);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user