Compare commits

..

No commits in common. "main" and "notashelf/push-nmuuvryuwrrl" have entirely different histories.

15 changed files with 240 additions and 906 deletions

3
.gitignore vendored
View file

@ -9,5 +9,4 @@ roguelike
# Zig
.zig-cache
zig-out
*.o
.zig-out

View file

@ -4,7 +4,7 @@ build:
# Build and run
dev:
zig build -Dadmin-controls=true run
zig build run
# Clean build artifacts
clean:

View file

@ -3,22 +3,13 @@ const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const admin_controls = b.option(bool, "admin-controls", "Enable in-game development admin controls") orelse false;
const base_c_flags = [_][]const u8{
const c_flags = [_][]const u8{
"-std=c99",
"-Wall",
"-Wextra",
"-O2",
};
const admin_c_flags = [_][]const u8{
"-std=c99",
"-Wall",
"-Wextra",
"-O2",
"-DROGGED_ADMIN_CONTROLS=1",
};
const c_flags = if (admin_controls) &admin_c_flags else &base_c_flags;
// RNG library
const rng_lib = b.addLibrary(.{
@ -29,11 +20,11 @@ pub fn build(b: *std.Build) void {
.link_libc = true,
}),
});
rng_lib.root_module.addCSourceFiles(.{
rng_lib.addCSourceFiles(.{
.files = &[_][]const u8{"libs/rng/rng.c"},
.flags = c_flags,
.flags = &c_flags,
});
rng_lib.root_module.addIncludePath(b.path("libs/rng"));
rng_lib.addIncludePath(b.path("libs/rng"));
// Map library
const map_lib = b.addLibrary(.{
@ -44,19 +35,19 @@ pub fn build(b: *std.Build) void {
.link_libc = true,
}),
});
map_lib.root_module.addCSourceFiles(.{
map_lib.addCSourceFiles(.{
.files = &[_][]const u8{
"libs/map/map.c",
"libs/map/utils.c",
},
.flags = c_flags,
.flags = &c_flags,
});
// map.h includes common.h and settings.h which live in src/
map_lib.root_module.addIncludePath(b.path("src"));
map_lib.addIncludePath(b.path("src"));
// map.c includes rng/rng.h via libs/ root
map_lib.root_module.addIncludePath(b.path("libs"));
map_lib.addIncludePath(b.path("libs"));
// utils.h is co-located with map.c
map_lib.root_module.addIncludePath(b.path("libs/map"));
map_lib.addIncludePath(b.path("libs/map"));
// Tileset library
const tileset_obj = b.addObject(.{
@ -67,18 +58,18 @@ pub fn build(b: *std.Build) void {
.link_libc = true,
}),
});
tileset_obj.root_module.addCSourceFiles(.{
tileset_obj.addCSourceFiles(.{
.files = &[_][]const u8{
"libs/tileset/tileset.c",
"libs/tileset/tileset_paint.c",
},
.flags = c_flags,
.flags = &c_flags,
});
// tileset.h includes settings.h which lives in src/
tileset_obj.root_module.addIncludePath(b.path("src"));
tileset_obj.addIncludePath(b.path("src"));
// tileset.c includes tileset.h which is co-located
tileset_obj.root_module.addIncludePath(b.path("libs/tileset"));
tileset_obj.root_module.linkSystemLibrary("raylib", .{});
tileset_obj.addIncludePath(b.path("libs/tileset"));
tileset_obj.linkSystemLibrary("raylib");
// Zig combat library. This must be compiled as an object and linked
// directly to bypassing the archive step, or it yields a corrupt
@ -93,8 +84,8 @@ pub fn build(b: *std.Build) void {
}),
});
// common.h and settings.h live in src/; rng.h exposed bare from libs/rng
combat_obj.root_module.addIncludePath(b.path("src"));
combat_obj.root_module.addIncludePath(b.path("libs/rng"));
combat_obj.addIncludePath(b.path("src"));
combat_obj.addIncludePath(b.path("libs/rng"));
// C sources remaining in src/
const c_sources = [_][]const u8{
@ -118,24 +109,24 @@ pub fn build(b: *std.Build) void {
}),
});
exe.root_module.addCSourceFiles(.{
exe.addCSourceFiles(.{
.files = &c_sources,
.flags = c_flags,
.flags = &c_flags,
});
// src/ for own headers; libs/ so "rng/rng.h" and "map/map.h" resolve
exe.root_module.addIncludePath(b.path("src"));
exe.root_module.addIncludePath(b.path("libs"));
exe.addIncludePath(b.path("src"));
exe.addIncludePath(b.path("libs"));
exe.root_module.linkLibrary(rng_lib);
exe.root_module.linkLibrary(map_lib);
exe.root_module.addObject(tileset_obj);
exe.root_module.addObject(combat_obj);
exe.root_module.linkSystemLibrary("raylib", .{});
exe.root_module.linkSystemLibrary("m", .{});
exe.root_module.linkSystemLibrary("pthread", .{});
exe.root_module.linkSystemLibrary("dl", .{});
exe.root_module.linkSystemLibrary("rt", .{});
exe.linkLibrary(rng_lib);
exe.linkLibrary(map_lib);
exe.addObject(tileset_obj);
exe.addObject(combat_obj);
exe.linkSystemLibrary("raylib");
exe.linkSystemLibrary("m");
exe.linkSystemLibrary("pthread");
exe.linkSystemLibrary("dl");
exe.linkSystemLibrary("rt");
b.installArtifact(exe);

View file

@ -28,7 +28,7 @@
// Tracks the earliest Zig version that the package considers to be a
// supported use case.
.minimum_zig_version = "0.16.0",
.minimum_zig_version = "0.15.2",
// This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`.

View file

@ -28,7 +28,7 @@ to be viable. For a semi-complete list of things that need to be done, see the
Rogged is built on a relatively simple stack. It uses C99 for the main game
logic, and Zig for the combat library. Besides `raylib` and `pkg-config`, you
only need the core Zig tooling. For now the required Zig version is 0.16.0, but
only need the core Zig tooling. For now the required Zig version is 0.15.2, but
this might change in the future. Additionally, you will need `clang-format` and
`just` for common development tasks in the case you plan to contribute. For
building, Zig is enough.
@ -53,7 +53,7 @@ $ just dev
### Manual Build
If you are allergic to good tooling and would rather use your system Zig, you
may simply invoke `zig build` after acquiring Zig 0.16.0.
may simply invoke `zig build` after acquiring Zig 0.15.2.
```sh
# Full build

6
flake.lock generated
View file

@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1780749050,
"narHash": "sha256-3av0pIjlOWQ6rDbNOmpUSvbNnJkGORQKKjb4LtCZsIY=",
"lastModified": 1775036866,
"narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a799d3e3886da994fa307f817a6bc705ae538eeb",
"rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
"type": "github"
},
"original": {

View file

@ -101,18 +101,18 @@ fn compact(effects: [*c]c.StatusEffect, count: [*c]c_int) void {
count[0] = @intCast(write);
}
fn tickOne(eff: [*c]c.StatusEffect, hp: [*c]c_int) c_int {
if (eff[0].duration <= 0) return 0;
fn tickOne(eff: *c.StatusEffect, hp: *c_int) c_int {
if (eff.duration <= 0) return 0;
var dmg: c_int = 0;
switch (eff[0].type) {
switch (eff.type) {
c.EFFECT_POISON, c.EFFECT_BLEED, c.EFFECT_BURN => {
dmg = eff[0].intensity;
hp[0] -= dmg;
dmg = eff.intensity;
hp.* -= dmg;
},
else => {},
}
eff[0].duration -= 1;
eff.duration -= 1;
return dmg;
}

View file

@ -25,8 +25,7 @@ int is_floor(const Map *map, int x, int y) {
if (!in_bounds(x, y, MAP_WIDTH, MAP_HEIGHT))
return 0;
return map->tiles[y][x] == TILE_FLOOR || map->tiles[y][x] == TILE_STAIRS || map->tiles[y][x] == TILE_DOOR_OPEN ||
map->tiles[y][x] == TILE_DOOR_RUINED || map->tiles[y][x] == TILE_DOOR_CLOSED ||
map->tiles[y][x] == TILE_RUBBLE || map->tiles[y][x] == TILE_SHALLOW_WATER;
map->tiles[y][x] == TILE_DOOR_RUINED || map->tiles[y][x] == TILE_DOOR_CLOSED;
}
void get_room_center(Room *room, int *cx, int *cy) {
@ -38,25 +37,9 @@ void get_room_center(Room *room, int *cx, int *cy) {
static void carve_room(Map *map, Room *room) {
for (int y = room->y; y < room->y + room->h; y++) {
for (int x = room->x; x < room->x + room->w; x++) {
if (!in_bounds(x, y, MAP_WIDTH, MAP_HEIGHT))
continue;
int local_x = x - room->x;
int local_y = y - room->y;
int carved = 1;
if ((room->type == ROOM_SHRINE || room->type == ROOM_VAULT) &&
((local_x == 0 && local_y == 0) || (local_x == room->w - 1 && local_y == 0) ||
(local_x == 0 && local_y == room->h - 1) || (local_x == room->w - 1 && local_y == room->h - 1))) {
carved = 0;
}
if (room->type == ROOM_CRYPT &&
((local_x == 0 || local_x == room->w - 1) && (local_y == 0 || local_y == room->h - 1)))
carved = 0;
if (carved)
if (in_bounds(x, y, MAP_WIDTH, MAP_HEIGHT)) {
map->tiles[y][x] = TILE_FLOOR;
}
}
}
}
@ -88,125 +71,39 @@ static int room_overlaps(Room *rooms, int count, Room *new_room) {
// Add padding to prevent rooms from touching
for (int i = 0; i < count; i++) {
Room *r = &rooms[i];
if (!(new_room->x > r->x + r->w + 2 || new_room->x + new_room->w + 2 < r->x || new_room->y > r->y + r->h + 2 ||
new_room->y + new_room->h + 2 < r->y)) {
if (!(new_room->x > r->x + r->w || new_room->x + new_room->w < r->x || new_room->y > r->y + r->h ||
new_room->y + new_room->h < r->y)) {
return 1;
}
}
return 0;
}
static RoomType pick_room_type(int floor, int room_index) {
if (room_index == 0)
return ROOM_START;
int roll = rng_int(0, 99);
if (floor <= 1) {
if (roll < 35)
return ROOM_GUARD;
if (roll < 55)
return ROOM_ARMORY;
if (roll < 75)
return ROOM_SHRINE;
return ROOM_CRYPT;
}
if (floor == 2) {
if (roll < 40)
return ROOM_CISTERN;
if (roll < 60)
return ROOM_GUARD;
if (roll < 80)
return ROOM_SHRINE;
return ROOM_CRYPT;
}
if (floor == 3) {
if (roll < 35)
return ROOM_CRYPT;
if (roll < 60)
return ROOM_LIBRARY;
if (roll < 80)
return ROOM_CISTERN;
return ROOM_VAULT;
}
if (floor == 4) {
if (roll < 35)
return ROOM_FORGE;
if (roll < 55)
return ROOM_ARMORY;
if (roll < 75)
return ROOM_CRYPT;
return ROOM_VAULT;
}
if (roll < 30)
return ROOM_VAULT;
if (roll < 55)
return ROOM_CRYPT;
if (roll < 75)
return ROOM_FORGE;
return ROOM_SHRINE;
}
static void room_size_for_type(RoomType type, int *w, int *h) {
switch (type) {
case ROOM_START:
*w = rng_int(7, 10);
*h = rng_int(6, 8);
break;
case ROOM_GUARD:
case ROOM_ARMORY:
*w = rng_int(6, 11);
*h = rng_int(5, 8);
break;
case ROOM_SHRINE:
case ROOM_VAULT:
*w = rng_int(7, 11);
*h = rng_int(7, 11);
break;
case ROOM_CISTERN:
*w = rng_int(8, 14);
*h = rng_int(6, 10);
break;
case ROOM_CRYPT:
case ROOM_LIBRARY:
*w = rng_int(8, 13);
*h = rng_int(5, 9);
break;
case ROOM_FORGE:
*w = rng_int(7, 12);
*h = rng_int(6, 9);
break;
}
}
// Generate rooms for this floor
static int generate_rooms(Map *map, Room *rooms, int floor) {
int room_count = 0;
int attempts = 0;
int max_attempts = 250;
int max_attempts = 100;
// Room count varies by floor, but capped at max_rooms
int target_rooms = 8 + (floor % 3) + rng_int(0, 3);
int target_rooms = 5 + (floor % 3) + rng_int(0, 3);
if (target_rooms > MAX_ROOMS)
target_rooms = MAX_ROOMS;
while (room_count < target_rooms && attempts < max_attempts) {
attempts++;
RoomType type = pick_room_type(floor, room_count);
int w, h;
room_size_for_type(type, &w, &h);
// Random room dimensions
int w = rng_int(5, 12);
int h = rng_int(5, 10);
int x, y;
if (room_count == 0) {
x = rng_int(3, 8);
y = rng_int(3, MAP_HEIGHT - h - 4);
} else {
x = rng_int(2, MAP_WIDTH - w - 3);
y = rng_int(2, MAP_HEIGHT - h - 3);
}
// Random position (within map bounds with 1-tile border)
int x = rng_int(2, MAP_WIDTH - w - 2);
int y = rng_int(2, MAP_HEIGHT - h - 2);
Room new_room = {x, y, w, h, type};
Room new_room = {x, y, w, h};
// Check for overlap
if (!room_overlaps(rooms, room_count, &new_room)) {
rooms[room_count] = new_room;
carve_room(map, &new_room);
@ -258,103 +155,6 @@ static int tile_in_any_room(int x, int y, Room *rooms, int room_count) {
return 0;
}
static int tile_in_room(const Room *room, int x, int y) {
return x >= room->x && x < room->x + room->w && y >= room->y && y < room->y + room->h;
}
Room *map_room_at(Map *map, int x, int y) {
for (int i = 0; i < map->room_count; i++) {
if (tile_in_room(&map->rooms[i], x, y))
return &map->rooms[i];
}
return NULL;
}
static int protected_room_tile(const Room *room, int x, int y) {
int cx, cy;
get_room_center((Room *)room, &cx, &cy);
return (abs(x - cx) <= 1 && abs(y - cy) <= 1) || x == room->x || y == room->y || x == room->x + room->w - 1 ||
y == room->y + room->h - 1;
}
static void set_room_tile(Map *map, const Room *room, int x, int y, TileType tile) {
if (!in_bounds(x, y, MAP_WIDTH, MAP_HEIGHT) || !tile_in_room(room, x, y) || protected_room_tile(room, x, y))
return;
if (map->tiles[y][x] == TILE_FLOOR)
map->tiles[y][x] = tile;
}
static void decorate_room(Map *map, const Room *room, int floor) {
int cx, cy;
get_room_center((Room *)room, &cx, &cy);
switch (room->type) {
case ROOM_START:
if (room->w >= 8 && room->h >= 6) {
set_room_tile(map, room, room->x + 2, room->y + 2, TILE_RUBBLE);
set_room_tile(map, room, room->x + room->w - 3, room->y + room->h - 3, TILE_RUBBLE);
}
break;
case ROOM_GUARD:
for (int y = room->y + 2; y < room->y + room->h - 2; y += 3) {
set_room_tile(map, room, room->x + 2, y, TILE_STATUE);
set_room_tile(map, room, room->x + room->w - 3, y, TILE_STATUE);
}
break;
case ROOM_SHRINE:
set_room_tile(map, room, cx - 2, cy, TILE_STATUE);
set_room_tile(map, room, cx + 2, cy, TILE_STATUE);
set_room_tile(map, room, cx, cy - 2, TILE_RUBBLE);
set_room_tile(map, room, cx, cy + 2, TILE_RUBBLE);
break;
case ROOM_CISTERN:
for (int y = cy - 1; y <= cy + 1; y++) {
for (int x = cx - 2; x <= cx + 2; x++) {
if ((x + y + floor) % 5 != 0)
set_room_tile(map, room, x, y, TILE_SHALLOW_WATER);
}
}
break;
case ROOM_ARMORY:
for (int x = room->x + 2; x < room->x + room->w - 2; x += 3) {
set_room_tile(map, room, x, room->y + 2, TILE_RUBBLE);
set_room_tile(map, room, x, room->y + room->h - 3, TILE_RUBBLE);
}
break;
case ROOM_CRYPT:
for (int x = room->x + 2; x < room->x + room->w - 2; x += 3) {
set_room_tile(map, room, x, cy - 1, TILE_STATUE);
set_room_tile(map, room, x, cy + 1, TILE_RUBBLE);
}
break;
case ROOM_LIBRARY:
for (int y = room->y + 2; y < room->y + room->h - 2; y += 2) {
set_room_tile(map, room, room->x + 2, y, TILE_STATUE);
set_room_tile(map, room, room->x + room->w - 3, y, TILE_RUBBLE);
}
break;
case ROOM_FORGE:
for (int y = cy - 1; y <= cy + 1; y++) {
set_room_tile(map, room, cx - 2, y, TILE_RUBBLE);
set_room_tile(map, room, cx + 2, y, TILE_RUBBLE);
}
set_room_tile(map, room, cx, cy - 2, TILE_STATUE);
break;
case ROOM_VAULT:
set_room_tile(map, room, cx - 2, cy - 2, TILE_STATUE);
set_room_tile(map, room, cx + 2, cy - 2, TILE_STATUE);
set_room_tile(map, room, cx - 2, cy + 2, TILE_STATUE);
set_room_tile(map, room, cx + 2, cy + 2, TILE_STATUE);
break;
}
}
static void decorate_rooms(Map *map, Room *rooms, int room_count, int floor) {
for (int i = 0; i < room_count; i++) {
decorate_room(map, &rooms[i], floor);
}
}
// Place doors at corridor-room junctions.
// Doors sit on corridor tiles, not room tiles, so they occupy the actual doorway.
static void place_doors(Map *map, Room *rooms, int room_count) {
@ -387,56 +187,23 @@ static void place_doors(Map *map, Room *rooms, int room_count) {
}
// Connect all rooms with corridors
static int room_distance2(Room *a, Room *b) {
int ax, ay, bx, by;
get_room_center(a, &ax, &ay);
get_room_center(b, &bx, &by);
int dx = ax - bx;
int dy = ay - by;
return dx * dx + dy * dy;
}
static void carve_connection(Map *map, Room *a, Room *b) {
int cx1, cy1, cx2, cy2;
get_room_center(a, &cx1, &cy1);
get_room_center(b, &cx2, &cy2);
if (rng_int(0, 2) == 0) {
int mid_x = (cx1 + cx2) / 2 + rng_int(-3, 3);
carve_h_corridor(map, cx1, mid_x, cy1);
carve_v_corridor(map, mid_x, cy1, cy2);
carve_h_corridor(map, mid_x, cx2, cy2);
} else if (rng_int(0, 1) == 0) {
carve_h_corridor(map, cx1, cx2, cy1);
carve_v_corridor(map, cx2, cy1, cy2);
} else {
carve_v_corridor(map, cx1, cy1, cy2);
carve_h_corridor(map, cx1, cx2, cy2);
}
}
static void connect_rooms(Map *map, Room *rooms, int room_count) {
for (int i = 1; i < room_count; i++) {
int nearest = 0;
int best = room_distance2(&rooms[i], &rooms[0]);
for (int j = 1; j < i; j++) {
int dist = room_distance2(&rooms[i], &rooms[j]);
if (dist < best) {
best = dist;
nearest = j;
}
for (int i = 0; i < room_count - 1; i++) {
int cx1, cy1, cx2, cy2;
get_room_center(&rooms[i], &cx1, &cy1);
get_room_center(&rooms[i + 1], &cx2, &cy2);
// Carve L-shaped corridor between rooms
if (rng_int(0, 1) == 0) {
carve_h_corridor(map, cx1, cx2, cy1);
carve_v_corridor(map, cx2, cy1, cy2);
} else {
carve_v_corridor(map, cx1, cy1, cy2);
carve_h_corridor(map, cx1, cx2, cy2);
}
carve_connection(map, &rooms[i], &rooms[nearest]);
}
for (int i = 0; i < room_count; i++) {
if (rng_int(0, 99) >= 28)
continue;
int j = rng_int(0, room_count - 1);
if (i != j)
carve_connection(map, &rooms[i], &rooms[j]);
}
// Place doors after all corridors are carved
place_doors(map, rooms, room_count);
}
@ -517,121 +284,18 @@ void get_random_floor_tile(Map *map, int *x, int *y, int attempts) {
}
}
int get_room_floor_tile(const Map *map, const Room *room, int *x, int *y) {
*x = -1;
*y = -1;
int cx = room->x + room->w / 2;
int cy = room->y + room->h / 2;
if (in_bounds(cx, cy, MAP_WIDTH, MAP_HEIGHT) && map->tiles[cy][cx] == TILE_FLOOR) {
*x = cx;
*y = cy;
return 1;
}
for (int radius = 1; radius < room->w + room->h; radius++) {
for (int yy = cy - radius; yy <= cy + radius; yy++) {
for (int xx = cx - radius; xx <= cx + radius; xx++) {
if (!tile_in_room(room, xx, yy) || !in_bounds(xx, yy, MAP_WIDTH, MAP_HEIGHT))
continue;
if (map->tiles[yy][xx] == TILE_FLOOR) {
*x = xx;
*y = yy;
return 1;
}
}
}
}
return 0;
}
int get_random_floor_tile_excluding_room(Map *map, const Room *excluded, int *x, int *y, int attempts) {
*x = -1;
*y = -1;
for (int i = 0; i < attempts; i++) {
int tx = rng_int(1, MAP_WIDTH - 2);
int ty = rng_int(1, MAP_HEIGHT - 2);
if (map->tiles[ty][tx] == TILE_FLOOR && (excluded == NULL || !tile_in_room(excluded, tx, ty))) {
*x = tx;
*y = ty;
return 1;
}
}
for (int ty = 1; ty < MAP_HEIGHT - 1; ty++) {
for (int tx = 1; tx < MAP_WIDTH - 1; tx++) {
if (map->tiles[ty][tx] == TILE_FLOOR && (excluded == NULL || !tile_in_room(excluded, tx, ty))) {
*x = tx;
*y = ty;
return 1;
}
}
}
return 0;
}
int map_validate_layout(const Map *map) {
if (map->room_count <= 0)
return 0;
int sx, sy;
if (!get_room_floor_tile(map, &map->rooms[0], &sx, &sy))
return 0;
unsigned char visited[MAP_HEIGHT][MAP_WIDTH];
Vec2 queue[MAP_HEIGHT * MAP_WIDTH];
memset(visited, 0, sizeof(visited));
int head = 0;
int tail = 0;
queue[tail++] = (Vec2){sx, sy};
visited[sy][sx] = 1;
while (head < tail) {
Vec2 p = queue[head++];
const int dx[4] = {0, 0, 1, -1};
const int dy[4] = {1, -1, 0, 0};
for (int i = 0; i < 4; i++) {
int nx = p.x + dx[i];
int ny = p.y + dy[i];
if (!in_bounds(nx, ny, MAP_WIDTH, MAP_HEIGHT) || visited[ny][nx] || !is_floor(map, nx, ny))
continue;
visited[ny][nx] = 1;
queue[tail++] = (Vec2){nx, ny};
}
}
int stairs = 0;
for (int y = 0; y < MAP_HEIGHT; y++) {
for (int x = 0; x < MAP_WIDTH; x++) {
if (is_floor(map, x, y) && !visited[y][x])
return 0;
if (map->tiles[y][x] == TILE_STAIRS) {
if (!visited[y][x])
return 0;
stairs++;
}
}
}
return stairs == 1;
}
void dungeon_generate(Dungeon *d, Map *map, int floor_num) {
for (int attempt = 0; attempt < 4; attempt++) {
map_init(map);
map->room_count = generate_rooms(map, map->rooms, floor_num);
connect_rooms(map, map->rooms, map->room_count);
decorate_rooms(map, map->rooms, map->room_count, floor_num);
place_stairs(map, map->rooms, map->room_count);
// Initialize map to all walls
map_init(map);
if (map_validate_layout(map))
break;
}
// Generate rooms
map->room_count = generate_rooms(map, map->rooms, floor_num);
// Connect rooms with corridors
connect_rooms(map, map->rooms, map->room_count);
// Place stairs in last room
place_stairs(map, map->rooms, map->room_count);
// Store dungeon state
d->current_floor = floor_num;
@ -662,7 +326,7 @@ static int trace_line_of_sight(const Map *map, int x1, int y1, int x2, int y2) {
return 1;
TileType t = map->tiles[y][x];
if ((t == TILE_WALL || t == TILE_DOOR_CLOSED || t == TILE_STATUE) && !(x == x1 && y == y1))
if ((t == TILE_WALL || t == TILE_DOOR_CLOSED) && !(x == x1 && y == y1))
return 0;
int e2 = 2 * err;
@ -699,7 +363,7 @@ static int is_solid(const Map *map, int sub_x, int sub_y) {
if (tx < 0 || tx >= map_w || ty < 0 || ty >= map_h)
return 1;
TileType t = map->tiles[ty][tx];
return t == TILE_WALL || t == TILE_DOOR_CLOSED || t == TILE_STATUE;
return t == TILE_WALL || t == TILE_DOOR_CLOSED;
}
static float smoothstep_light(float edge0, float edge1, float x) {

View file

@ -17,10 +17,6 @@ void map_init(Map *map);
// Get a random floor tile position
void get_random_floor_tile(Map *map, int *x, int *y, int attempts);
int get_room_floor_tile(const Map *map, const Room *room, int *x, int *y);
int get_random_floor_tile_excluding_room(Map *map, const Room *excluded, int *x, int *y, int attempts);
Room *map_room_at(Map *map, int x, int y);
int map_validate_layout(const Map *map);
// Visibility / Fog of War
int is_in_view_range(int x, int y, int view_x, int view_y, int range);

View file

@ -42,67 +42,44 @@ void paint_wall_tile(Tileset *ts, int id, int variant) {
BeginTextureMode(ts->render_target);
int ox = (int)off.x;
int oy = (int)off.y;
Color base = variant == 0 ? (Color){38, 37, 39, 255} : (Color){33, 32, 35, 255};
Color edge = variant == 0 ? (Color){48, 46, 46, 255} : (Color){42, 40, 41, 255};
Color shadow = variant == 0 ? (Color){24, 23, 25, 255} : (Color){20, 19, 21, 255};
Color highlight = variant == 0 ? (Color){56, 54, 52, 255} : (Color){49, 47, 46, 255};
Color moss = variant == 0 ? (Color){38, 58, 42, 255} : (Color){28, 45, 34, 255};
// Base color is much darker for better contrast against floor
Color base = variant == 0 ? (Color){45, 42, 38, 255} : (Color){35, 32, 28, 255};
DrawRectangle((int)off.x, (int)off.y, w, h, base);
DrawRectangle(ox, oy, w, h, base);
lcg_srand((unsigned int)(variant * 4099 + id * 9176));
// Brick pattern
int brick_h = h / 3;
int brick_w = w / 2;
Color mortar = (Color){25, 22, 18, 255};
Color brick_light = variant == 0 ? (Color){70, 65, 60, 255} : (Color){50, 47, 43, 255};
Color brick_dark = variant == 0 ? (Color){40, 37, 33, 255} : (Color){30, 27, 24, 255};
int row_y = 0;
for (int row = 0; row < 3; row++) {
int block_h = row == 2 ? h - row_y : lcg_rand_range(5, 6);
int x = 0;
int stagger = (row % 2) ? lcg_rand_range(-4, 0) : lcg_rand_range(-1, 2);
while (x < w) {
int block_w = lcg_rand_range(7, 12);
int bx = ox + x + stagger;
int by = oy + row_y;
int bw = block_w;
if (bx < ox) {
bw -= ox - bx;
bx = ox;
}
if (bx + bw > ox + w)
bw = ox + w - bx;
if (bw > 0) {
Color face = ((x + row + variant) % 3 == 0) ? edge : base;
DrawRectangle(bx, by, bw, block_h, face);
if (row == 0)
DrawLine(bx, by, bx + bw - 1, by, highlight);
DrawLine(bx, by + block_h - 1, bx + bw - 1, by + block_h - 1, shadow);
if (bw > 5 && block_h > 4 && lcg_rand_range(0, 99) < 18)
DrawPixel(bx + lcg_rand_range(1, bw - 2), by + lcg_rand_range(1, block_h - 2), shadow);
}
x += block_w;
int y = (int)off.y + row * brick_h;
int offset_x = (row % 2 == 0) ? 0 : brick_w / 2;
for (int col = -1; col < 3; col++) {
int x = (int)off.x + offset_x + col * brick_w;
if (x >= (int)off.x + w)
break;
if (x + brick_w <= (int)off.x)
continue;
// Clip to tile bounds
int draw_x = x < (int)off.x ? (int)off.x : x;
int draw_w = brick_w;
if (draw_x + draw_w > (int)off.x + w)
draw_w = (int)off.x + w - draw_x;
Color c = ((col + row) % 2 == 0) ? brick_light : brick_dark;
DrawRectangle(draw_x, y, draw_w, brick_h - 1, c);
}
row_y += block_h;
}
for (int i = 0; i < 3 + variant; i++) {
int px = ox + lcg_rand_range(1, w - 2);
int py = oy + lcg_rand_range(1, h - 2);
DrawPixel(px, py, lcg_rand_range(0, 1) ? highlight : shadow);
// Mortar lines
for (int row = 1; row < 3; row++) {
int y = (int)off.y + row * brick_h;
DrawLine((int)off.x, y, (int)off.x + w - 1, y, mortar);
}
if (variant == 1) {
DrawLine(ox + 3, oy + 1, ox + 5, oy + 5, shadow);
DrawLine(ox + 5, oy + 5, ox + 4, oy + 8, shadow);
DrawLine(ox + 11, oy + 4, ox + 8, oy + 7, shadow);
} else {
DrawLine(ox + 9, oy + 2, ox + 12, oy + 5, shadow);
DrawLine(ox + 12, oy + 5, ox + 10, oy + 9, shadow);
DrawPixel(ox + 3, oy + 13, moss);
DrawPixel(ox + 4, oy + 13, moss);
DrawPixel(ox + 4, oy + 14, moss);
}
DrawRectangle(ox, oy + h - 2, w, 2, (Color){16, 15, 17, 255});
EndTextureMode();
}
@ -118,44 +95,42 @@ void paint_floor_tile(Tileset *ts, int id, int variant) {
BeginTextureMode(ts->render_target);
int ox = (int)off.x;
int oy = (int)off.y;
Color base = variant == 0 ? (Color){61, 58, 55, 255}
: variant == 1 ? (Color){67, 63, 58, 255}
: variant == 2 ? (Color){56, 55, 58, 255}
: (Color){64, 59, 53, 255};
Color bevel = (Color){86, 82, 75, 255};
Color seam = (Color){34, 32, 31, 255};
Color chip = (Color){43, 41, 40, 255};
// Base stone color - lighter than walls for contrast
Color base = (Color){75, 72, 68, 255};
DrawRectangle((int)off.x, (int)off.y, w, h, base);
DrawRectangle(ox, oy, w, h, base);
// Seeded noise based on variant
lcg_srand((unsigned int)(variant * 7919 + id * 104729));
int split_x = lcg_rand_range(6, 10);
int split_y = lcg_rand_range(6, 10);
DrawLine(ox + split_x, oy + 1, ox + split_x, oy + h - 2, seam);
DrawLine(ox + 1, oy + split_y, ox + w - 2, oy + split_y, seam);
DrawLine(ox + 1, oy + 1, ox + w - 2, oy + 1, bevel);
DrawLine(ox + 1, oy + 1, ox + 1, oy + h - 2, bevel);
DrawLine(ox + 1, oy + h - 2, ox + w - 2, oy + h - 2, seam);
DrawLine(ox + w - 2, oy + 1, ox + w - 2, oy + h - 2, seam);
int num_dots = 14 + variant * 5;
// Dithered noise dots - lighter shades
int num_dots = 8 + variant * 4;
for (int i = 0; i < num_dots; i++) {
int px = ox + lcg_rand_range(1, w - 2);
int py = oy + lcg_rand_range(1, h - 2);
Color c = lcg_rand_range(0, 2) == 0 ? bevel : chip;
int px = (int)off.x + lcg_rand_range(1, w - 2);
int py = (int)off.y + lcg_rand_range(1, h - 2);
int shade = lcg_rand_range(0, 3);
Color c;
if (shade == 0)
c = (Color){85, 82, 78, 255};
else if (shade == 1)
c = (Color){65, 62, 58, 255};
else
c = (Color){90, 87, 83, 255};
DrawPixel(px, py, c);
}
if (variant >= 1) {
int crack_x = ox + lcg_rand_range(3, w - 4);
int crack_y = oy + lcg_rand_range(3, h - 4);
DrawPixel(crack_x, crack_y, seam);
DrawPixel(crack_x + 1, crack_y, seam);
DrawPixel(crack_x + 1, crack_y + 1, seam);
if (variant >= 2)
DrawPixel(crack_x + 2, crack_y + 2, seam);
// Occasional crack line
if (variant >= 2) {
int crack_x = (int)off.x + lcg_rand_range(2, w - 3);
int crack_y = (int)off.y + lcg_rand_range(2, h - 3);
int crack_len = lcg_rand_range(2, 4);
int crack_dir = lcg_rand_range(0, 1); // 0 = horizontal, 1 = vertical
Color crack_color = (Color){55, 52, 48, 255};
for (int i = 0; i < crack_len; i++) {
if (crack_dir == 0)
DrawPixel(crack_x + i, crack_y, crack_color);
else
DrawPixel(crack_x, crack_y + i, crack_color);
}
}
EndTextureMode();
@ -173,25 +148,32 @@ void paint_stairs_tile(Tileset *ts, int id) {
BeginTextureMode(ts->render_target);
int ox = (int)off.x;
int oy = (int)off.y;
DrawRectangle(ox, oy, w, h, (Color){31, 29, 27, 255});
// Dark stone background
DrawRectangle((int)off.x, (int)off.y, w, h, (Color){40, 38, 35, 255});
Color face = (Color){82, 76, 68, 255};
Color lip = (Color){118, 106, 84, 255};
Color drop = (Color){28, 25, 23, 255};
for (int i = 0; i < 4; i++) {
int y = oy + 3 + i * 3;
int inset = i + 1;
DrawRectangle(ox + inset, y, w - inset * 2, 2, face);
DrawLine(ox + inset, y, ox + w - inset - 1, y, lip);
DrawLine(ox + inset, y + 2, ox + w - inset - 1, y + 2, drop);
// Stair steps (3 steps)
int step_h = h / 4;
Color step_light = (Color){100, 95, 90, 255};
Color step_dark = (Color){60, 57, 53, 255};
for (int i = 0; i < 3; i++) {
int y = (int)off.y + h - (i + 1) * step_h;
int inset = i * 2;
int x = (int)off.x + inset;
DrawRectangle(x, y, w - inset * 2, step_h - 1, step_light);
DrawLine(x, y + step_h - 1, x + w - inset * 2 - 1, y + step_h - 1, step_dark);
}
DrawLine(ox + 4, oy + 2, ox + 11, oy + 2, (Color){120, 111, 93, 255});
DrawPixel(ox + 7, oy + 1, (Color){142, 134, 114, 255});
DrawPixel(ox + 8, oy + 1, (Color){142, 134, 114, 255});
DrawRectangle(ox + 6, oy + 13, 4, 1, (Color){12, 10, 9, 255});
// ">" symbol on top step
int top_y = (int)off.y + 2;
int cx = (int)off.x + w / 2;
Color arrow = (Color){180, 175, 170, 255};
DrawPixel(cx, top_y, arrow);
DrawPixel(cx - 1, top_y + 1, arrow);
DrawPixel(cx + 1, top_y + 1, arrow);
DrawPixel(cx - 2, top_y + 2, arrow);
DrawPixel(cx + 2, top_y + 2, arrow);
DrawPixel(cx, top_y + 3, arrow);
EndTextureMode();
}
@ -652,22 +634,23 @@ void paint_door_closed_tile(Tileset *ts, int id) {
BeginTextureMode(ts->render_target);
Color iron = (Color){35, 32, 30, 255};
Color wood_dark = (Color){72, 47, 30, 255};
Color wood_mid = (Color){111, 73, 42, 255};
Color wood_light = (Color){151, 103, 58, 255};
// Wooden door frame
Color wood_dark = (Color){100, 70, 40, 255};
Color wood_light = (Color){140, 100, 60, 255};
Color wood_mid = (Color){120, 85, 50, 255};
DrawRectangle(ox, oy, w, h, iron);
DrawRectangle(ox + 2, oy + 1, w - 4, h - 2, wood_dark);
DrawRectangle(ox + 3, oy + 2, 3, h - 4, wood_mid);
DrawRectangle(ox + 7, oy + 2, 3, h - 4, wood_light);
DrawRectangle(ox + 11, oy + 2, 2, h - 4, wood_mid);
DrawRectangle(ox + 2, oy + 5, w - 4, 2, iron);
DrawRectangle(ox + 2, oy + 11, w - 4, 2, iron);
DrawPixel(ox + 4, oy + 3, (Color){180, 129, 72, 255});
DrawPixel(ox + 8, oy + 9, (Color){84, 53, 32, 255});
DrawPixel(ox + 12, oy + 8, (Color){208, 169, 69, 255});
DrawPixel(ox + 12, oy + 9, (Color){166, 124, 51, 255});
// Frame
DrawRectangle(ox, oy, w, h, wood_dark);
// Panels
DrawRectangle(ox + 2, oy + 2, w - 4, h - 4, wood_mid);
// Inner panel
DrawRectangle(ox + 4, oy + 4, w - 8, h - 8, wood_light);
// Cross pattern
DrawRectangle(ox + 7, oy + 2, 2, h - 4, wood_dark);
DrawRectangle(ox + 2, oy + 7, w - 4, 2, wood_dark);
// Handle
DrawPixel(ox + 12, oy + 8, (Color){200, 180, 50, 255});
DrawPixel(ox + 12, oy + 9, (Color){200, 180, 50, 255});
EndTextureMode();
}
@ -686,19 +669,17 @@ void paint_door_open_tile(Tileset *ts, int id) {
BeginTextureMode(ts->render_target);
Color floor = (Color){58, 55, 52, 255};
Color seam = (Color){32, 30, 29, 255};
Color wood_dark = (Color){72, 47, 30, 255};
Color wood_light = (Color){131, 88, 49, 255};
// Open door - shows floor with door frame on sides
Color floor = (Color){75, 72, 68, 255};
Color wood_dark = (Color){100, 70, 40, 255};
// Floor
DrawRectangle(ox, oy, w, h, floor);
DrawLine(ox + 7, oy + 1, ox + 7, oy + h - 2, seam);
DrawLine(ox + 1, oy + 8, ox + w - 2, oy + 8, seam);
// Door frame on left side (open)
DrawRectangle(ox, oy, 3, h, wood_dark);
DrawRectangle(ox + 2, oy + 2, 2, h - 4, wood_light);
DrawPixel(ox + 1, oy + 4, (Color){72, 72, 72, 255});
DrawPixel(ox + 1, oy + 11, (Color){72, 72, 72, 255});
DrawRectangle(ox + 4, oy + 13, 6, 1, (Color){25, 20, 17, 255});
// Hinges
DrawPixel(ox + 1, oy + 4, (Color){80, 80, 80, 255});
DrawPixel(ox + 1, oy + 12, (Color){80, 80, 80, 255});
EndTextureMode();
}

View file

@ -20,7 +20,6 @@ stdenv.mkDerivation {
(s + /libs)
(s + /src)
(s + /build.zig)
(s + /build.zig.zon)
];
};

View file

@ -9,17 +9,7 @@ typedef struct {
} Vec2;
// Tile types
typedef enum {
TILE_WALL,
TILE_FLOOR,
TILE_STAIRS,
TILE_DOOR_CLOSED,
TILE_DOOR_OPEN,
TILE_DOOR_RUINED,
TILE_RUBBLE,
TILE_SHALLOW_WATER,
TILE_STATUE,
} TileType;
typedef enum { TILE_WALL, TILE_FLOOR, TILE_STAIRS, TILE_DOOR_CLOSED, TILE_DOOR_OPEN, TILE_DOOR_RUINED } TileType;
// Status effect types
typedef enum { EFFECT_NONE, EFFECT_POISON, EFFECT_STUN, EFFECT_BLEED, EFFECT_WEAKEN, EFFECT_BURN } StatusEffectType;
@ -35,21 +25,8 @@ typedef struct {
} StatusEffect;
// Room
typedef enum {
ROOM_START,
ROOM_GUARD,
ROOM_SHRINE,
ROOM_CISTERN,
ROOM_ARMORY,
ROOM_CRYPT,
ROOM_LIBRARY,
ROOM_FORGE,
ROOM_VAULT,
} RoomType;
typedef struct {
int x, y, w, h;
RoomType type;
} Room;
// Light source for sub-tile lighting system

View file

@ -26,14 +26,16 @@ void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) {
if (floor >= 4)
max_type = 3;
Room *start_room = map_room_at(map, p->position.x, p->position.y);
// Get the player's starting room (first room) to exclude from enemy spawn
Room *start_room = NULL;
if (map->room_count > 0) {
start_room = &map->rooms[0];
}
int attempts = 0;
while (*count < num_enemies && attempts < num_enemies * 40) {
attempts++;
for (int i = 0; i < num_enemies; i++) {
// Find random floor position
int ex, ey;
if (!get_random_floor_tile_excluding_room(map, start_room, &ex, &ey, 80))
break;
get_random_floor_tile(map, &ex, &ey, 50);
// Don't spawn on player position
if (ex == p->position.x && ey == p->position.y) {
@ -159,7 +161,7 @@ void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) {
break;
}
enemies[*count] = e;
enemies[i] = e;
(*count)++;
}
}

View file

@ -118,9 +118,7 @@ static void init_floor(GameState *gs, int floor_num) {
// Find spawn position
int start_x, start_y;
if (gs->map.room_count <= 0 || !get_room_floor_tile(&gs->map, &gs->map.rooms[0], &start_x, &start_y)) {
get_random_floor_tile(&gs->map, &start_x, &start_y, 100);
}
get_random_floor_tile(&gs->map, &start_x, &start_y, 100);
// Initialize player position if first floor
if (floor_num == 1) {
@ -150,151 +148,6 @@ static void init_floor(GameState *gs, int floor_num) {
gs->turn_count = 0;
}
#ifdef ROGGED_ADMIN_CONTROLS
static void admin_recompute_lighting(GameState *gs) {
LightSource player_light = {gs->player.position.x, gs->player.position.y, PLAYER_LIGHT_INTENSITY, PLAYER_LIGHT_RANGE};
LightSource sources[1 + 32];
sources[0] = player_light;
memcpy(sources + 1, gs->static_lights, gs->static_light_count * sizeof(LightSource));
compute_lighting(&gs->map, sources, 1 + gs->static_light_count);
}
static void admin_reveal_map(GameState *gs) {
for (int y = 0; y < MAP_HEIGHT; y++) {
for (int x = 0; x < MAP_WIDTH; x++) {
gs->map.remembered[y][x] = 1;
}
}
}
static void admin_apply_fullbright(GameState *gs) {
memset(gs->map.light_map, 255, sizeof(gs->map.light_map));
admin_reveal_map(gs);
}
static void admin_kill_enemies(GameState *gs) {
for (int i = 0; i < gs->enemy_count; i++) {
if (gs->enemies[i].alive) {
gs->enemies[i].alive = 0;
gs->enemies[i].hp = 0;
}
}
}
static void admin_teleport_to_stairs(GameState *gs) {
for (int y = 0; y < MAP_HEIGHT; y++) {
for (int x = 0; x < MAP_WIDTH; x++) {
if (gs->map.tiles[y][x] == TILE_STAIRS) {
gs->player.position.x = x;
gs->player.position.y = y;
gs->awaiting_descend = 1;
gs->last_message = "Admin: teleported to stairs";
gs->message_timer = 90;
admin_recompute_lighting(gs);
return;
}
}
}
}
static int admin_button(Rectangle rect, const char *label) {
Vector2 mouse = GetMousePosition();
int hovered = CheckCollisionPointRec(mouse, rect);
DrawRectangleRec(rect, hovered ? (Color){72, 62, 48, 235} : (Color){44, 42, 46, 235});
DrawRectangleLines((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height,
hovered ? (Color){210, 180, 110, 255} : (Color){112, 104, 92, 255});
DrawText(label, (int)rect.x + 8, (int)rect.y + 5, SMALL_FONT, (Color){235, 228, 210, 255});
return hovered && IsMouseButtonPressed(MOUSE_LEFT_BUTTON);
}
static void admin_controls(GameState *gs, int *visible, int *fullbright) {
if (IsKeyPressed(KEY_F1))
*visible = !*visible;
DrawRectangle(8, 8, 142, 22, (Color){10, 10, 12, 190});
DrawText("F1 Admin", 16, 13, SMALL_FONT, (Color){220, 190, 110, 255});
if (!*visible)
return;
const int x = 20;
const int start_y = 84;
const int w = 166;
const int h = 24;
const int gap = 8;
const int button_count = 10;
const int panel_padding_bottom = 16;
Rectangle panel = {8, 34, 190,
(float)(start_y - 34 + button_count * h + (button_count - 1) * gap + panel_padding_bottom)};
DrawRectangleRec(panel, (Color){12, 12, 15, 225});
DrawRectangleLines((int)panel.x, (int)panel.y, (int)panel.width, (int)panel.height, (Color){170, 145, 90, 255});
DrawText("Admin Controls", 20, 46, NORM_FONT, (Color){230, 205, 130, 255});
DrawText("development build only", 20, 62, TINY_FONT, (Color){170, 164, 150, 255});
int y = start_y;
if (admin_button((Rectangle){x, y, w, h}, "Heal full")) {
gs->player.hp = gs->player.max_hp;
gs->game_over = 0;
gs->last_message = "Admin: healed";
gs->message_timer = 60;
}
y += h + gap;
if (admin_button((Rectangle){x, y, w, h}, "+10 max HP")) {
gs->player.max_hp += 10;
gs->player.hp = gs->player.max_hp;
}
y += h + gap;
if (admin_button((Rectangle){x, y, w, h}, "+2 attack"))
gs->player.attack += 2;
y += h + gap;
if (admin_button((Rectangle){x, y, w, h}, "+2 defense"))
gs->player.defense += 2;
y += h + gap;
if (admin_button((Rectangle){x, y, w, h}, "Reveal map"))
admin_reveal_map(gs);
y += h + gap;
char fullbright_label[32];
snprintf(fullbright_label, sizeof(fullbright_label), "Fullbright: %s", *fullbright ? "on" : "off");
if (admin_button((Rectangle){x, y, w, h}, fullbright_label)) {
*fullbright = !*fullbright;
if (*fullbright) {
admin_apply_fullbright(gs);
} else {
admin_recompute_lighting(gs);
}
}
y += h + gap;
if (admin_button((Rectangle){x, y, w, h}, "Kill enemies"))
admin_kill_enemies(gs);
y += h + gap;
if (admin_button((Rectangle){x, y, w, h}, "Teleport stairs"))
admin_teleport_to_stairs(gs);
y += h + gap;
if (admin_button((Rectangle){x, y, w, h}, "Reroll floor")) {
init_floor(gs, gs->player.floor);
gs->last_message = "Admin: rerolled floor";
gs->message_timer = 60;
}
y += h + gap;
if (admin_button((Rectangle){x, y, w, h}, "Next floor")) {
int next_floor = gs->player.floor < NUM_FLOORS ? gs->player.floor + 1 : gs->player.floor;
init_floor(gs, next_floor);
gs->last_message = "Admin: advanced floor";
gs->message_timer = 60;
}
}
#endif
// Tick all status effects at the start of a turn
static void tick_all_effects(GameState *gs) {
// Player effects
@ -764,10 +617,6 @@ static void game_loop(unsigned int run_seed, FontManager *fm) {
SetExitKey(0);
int frame_counter = 0;
#ifdef ROGGED_ADMIN_CONTROLS
int admin_visible = 1;
int admin_fullbright = 0;
#endif
while (!WindowShouldClose()) {
frame_counter++;
@ -843,11 +692,6 @@ static void game_loop(unsigned int run_seed, FontManager *fm) {
if (gs.player.flash_timer > 0)
gs.player.flash_timer--;
#ifdef ROGGED_ADMIN_CONTROLS
if (admin_fullbright)
admin_apply_fullbright(&gs);
#endif
// Render
BeginDrawing();
ClearBackground(BLACK);
@ -900,10 +744,6 @@ static void game_loop(unsigned int run_seed, FontManager *fm) {
gs.final_score, gs.run_seed, fm);
}
#ifdef ROGGED_ADMIN_CONTROLS
admin_controls(&gs, &admin_visible, &admin_fullbright);
#endif
EndDrawing();
// small delay for key repeat control

View file

@ -103,57 +103,6 @@ static Color color_lerp(Color a, Color b, float t) {
(unsigned char)(a.b + (int)((b.b - a.b) * t)), (unsigned char)(a.a + (int)((b.a - a.a) * t))};
}
static float light_factor_from_brightness(int brightness) {
float base_light = brightness > 0 ? AMBIENT_LIGHT_FACTOR : REMEMBERED_LIGHT_FACTOR;
return base_light + (1.0f - base_light) * powf((float)brightness / 255.0f, LIGHT_EXPONENT);
}
static int sample_light(const Map *map, int tx, int ty, int sx, int sy) {
int base_x = tx * SUB_TILE_RES + sx;
int base_y = ty * SUB_TILE_RES + sy;
int sum = 0;
int count = 0;
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
int x = base_x + dx;
int y = base_y + dy;
if (x < 0 || y < 0 || x >= MAP_WIDTH * SUB_TILE_RES || y >= MAP_HEIGHT * SUB_TILE_RES)
continue;
sum += map->light_map[y][x];
count++;
}
}
return count > 0 ? sum / count : 0;
}
static Color tint_for_light(const Map *map, int tx, int ty, int sx, int sy, int is_opaque, float dim) {
float factor = light_factor_from_brightness(sample_light(map, tx, ty, sx, sy)) * dim;
if (is_opaque)
factor = AMBIENT_LIGHT_FACTOR + 0.92f * factor;
int value = (int)(255.0f * factor);
if (value > 255)
value = 255;
return (Color){(unsigned char)value, (unsigned char)value, (unsigned char)value, 255};
}
static void draw_lit_texture_tile(const Map *map, const Tileset *tileset, Rectangle src, Rectangle dst, int tx, int ty,
int is_opaque, float dim) {
const int parts = 4;
float src_w = src.width / (float)parts;
float src_h = src.height / (float)parts;
float dst_w = dst.width / (float)parts;
float dst_h = dst.height / (float)parts;
for (int py = 0; py < parts; py++) {
for (int px = 0; px < parts; px++) {
int sx = (px * SUB_TILE_RES) / parts + SUB_TILE_RES / (parts * 2);
int sy = (py * SUB_TILE_RES) / parts + SUB_TILE_RES / (parts * 2);
Rectangle s = {src.x + src_w * px, src.y + src_h * py, src_w, src_h};
Rectangle d = {dst.x + dst_w * px, dst.y + dst_h * py, dst_w + 0.5f, dst_h + 0.5f};
DrawTexturePro(tileset->atlas, s, d, (Vector2){0, 0}, 0.0f, tint_for_light(map, tx, ty, sx, sy, is_opaque, dim));
}
}
}
void render_map(const Map *map, const Tileset *tileset) {
for (int y = 0; y < MAP_HEIGHT; y++) {
for (int x = 0; x < MAP_WIDTH; x++) {
@ -166,7 +115,8 @@ void render_map(const Map *map, const Tileset *tileset) {
continue;
}
float light_factor = light_factor_from_brightness(brightness);
float base_light = brightness > 0 ? AMBIENT_LIGHT_FACTOR : REMEMBERED_LIGHT_FACTOR;
float light_factor = base_light + (1.0f - base_light) * powf((float)brightness / 255.0f, LIGHT_EXPONENT);
if (is_deep_corridor(map, x, y))
light_factor *= 0.82f;
@ -190,18 +140,10 @@ void render_map(const Map *map, const Tileset *tileset) {
case TILE_DOOR_RUINED:
tile_id = TILE_DOOR_OPEN_SPRITE;
break;
case TILE_RUBBLE:
case TILE_SHALLOW_WATER:
tile_id = TILE_FLOOR_0 + ((x * 7 + y * 13) % 4);
break;
case TILE_STATUE:
tile_id = TILE_WALL_0 + ((x * 7 + y * 13) % 2);
break;
}
int is_door = (map->tiles[y][x] == TILE_DOOR_CLOSED || map->tiles[y][x] == TILE_DOOR_OPEN);
int tile_drawn = 0;
int decor_base_drawn = 0;
// Draw floor underneath doors using tileset if available, so the
// floor matches adjacent tiles
@ -209,42 +151,35 @@ void render_map(const Map *map, const Tileset *tileset) {
int floor_id = TILE_FLOOR_0 + ((x * 7 + y * 13) % 4);
Rectangle floor_src = tileset_get_region(tileset, floor_id);
if (floor_src.width > 0) {
draw_lit_texture_tile(map, tileset, floor_src, dst, x, y, 0, 1.0f);
int fv = (int)(255.0f * light_factor);
if (fv > 255)
fv = 255;
Color ftint = (Color){(unsigned char)fv, (unsigned char)fv, (unsigned char)fv, 255};
DrawTexturePro(tileset->atlas, floor_src, dst, (Vector2){0, 0}, 0.0f, ftint);
tile_drawn = 1;
}
}
int is_decor =
map->tiles[y][x] == TILE_RUBBLE || map->tiles[y][x] == TILE_SHALLOW_WATER || map->tiles[y][x] == TILE_STATUE;
if (tile_id >= 0 && tileset != NULL && tileset->finalized && !is_door && !is_decor) {
if (tile_id >= 0 && tileset != NULL && tileset->finalized && !is_door) {
Rectangle src = tileset_get_region(tileset, tile_id);
if (src.width > 0) {
int is_opaque = (map->tiles[y][x] == TILE_WALL || map->tiles[y][x] == TILE_STATUE);
draw_lit_texture_tile(map, tileset, src, dst, x, y, is_opaque, is_deep_corridor(map, x, y) ? 0.82f : 1.0f);
int is_opaque = (map->tiles[y][x] == TILE_WALL);
float wall_dim = is_opaque ? (AMBIENT_LIGHT_FACTOR + 0.92f * light_factor) : light_factor;
int tv = (int)(255.0f * wall_dim);
if (tv > 255)
tv = 255;
Color tint = (Color){(unsigned char)tv, (unsigned char)tv, (unsigned char)tv, 255};
DrawTexturePro(tileset->atlas, src, dst, (Vector2){0, 0}, 0.0f, tint);
tile_drawn = 1;
}
}
if (is_decor && tileset != NULL && tileset->finalized) {
int base_id = TILE_FLOOR_0 + ((x * 7 + y * 13) % 4);
Rectangle src = tileset_get_region(tileset, base_id);
if (src.width > 0) {
draw_lit_texture_tile(map, tileset, src, dst, x, y, 0, 1.0f);
decor_base_drawn = 1;
}
}
if (!tile_drawn || is_door) {
Color wall_color = color_lerp((Color){42, 42, 52, 255}, DARKGRAY, light_factor);
Color floor_color = color_lerp((Color){32, 32, 42, 255}, BLACK, light_factor);
Color stairs_color = color_lerp((Color){85, 80, 70, 255}, (Color){180, 160, 100, 255}, light_factor);
Color door_color = color_lerp((Color){38, 34, 30, 255}, (Color){120, 92, 58, 255}, light_factor);
Color door_handle_color = color_lerp((Color){42, 38, 32, 255}, (Color){145, 122, 82, 255}, light_factor);
int is_currently_lit = brightness > 0;
if (is_door && !is_currently_lit) {
door_color = (Color){30, 27, 24, 255};
door_handle_color = (Color){34, 31, 27, 255};
}
switch (map->tiles[y][x]) {
case TILE_WALL:
@ -252,6 +187,23 @@ void render_map(const Map *map, const Tileset *tileset) {
break;
case TILE_FLOOR:
DrawRectangleRec(dst, floor_color);
// Torch flicker: warm tint on floor tiles adjacent to stairs
{
int is_adjacent_to_stairs = 0;
for (int dy = -1; dy <= 1 && !is_adjacent_to_stairs; dy++) {
for (int dx = -1; dx <= 1 && !is_adjacent_to_stairs; dx++) {
int nx = x + dx;
int ny = y + dy;
if (in_bounds(nx, ny, MAP_WIDTH, MAP_HEIGHT) && map->tiles[ny][nx] == TILE_STAIRS) {
is_adjacent_to_stairs = 1;
}
}
}
if (is_adjacent_to_stairs && light_factor > 0.05f) {
int flicker = (int)(sinf(GetTime() * 5.0f) * 15.0f);
DrawRectangleRec(dst, (Color){40 + flicker, 25, 10, 60});
}
}
// Grid lines
if (DRAW_GRID_LINES && light_factor > 0.05f) {
DrawRectangleLines((int)dst.x, (int)dst.y, (int)dst.width, (int)dst.height, (Color){20, 20, 20, 80});
@ -304,13 +256,11 @@ void render_map(const Map *map, const Tileset *tileset) {
int open_x = swing_to_left ? x * TILE_SIZE + 1 : x * TILE_SIZE + 13;
int px = (int)(closed_x + (open_x - closed_x) * t);
int alpha = (int)(255 * (1.0f - t * 0.45f));
if (!is_currently_lit)
alpha = (int)(alpha * 0.55f);
int width = (int)(2 + (1 - t)); // 2px closed, 1px open
Color panel_color = door_color;
panel_color.a = alpha;
DrawRectangle(px, y * TILE_SIZE + 1, width, TILE_SIZE - 2, panel_color);
if (t < 0.5f && is_currently_lit) {
if (t < 0.5f && light_factor > 0.05f) {
int hx = px + (swing_to_left ? -1 : width + 1);
Color handle_color = door_handle_color;
handle_color.a = alpha;
@ -331,13 +281,11 @@ void render_map(const Map *map, const Tileset *tileset) {
int open_y = swing_up ? y * TILE_SIZE + 1 : y * TILE_SIZE + 13;
int py = (int)(closed_y + (open_y - closed_y) * t);
int alpha = (int)(255 * (1.0f - t * 0.45f));
if (!is_currently_lit)
alpha = (int)(alpha * 0.55f);
int height = (int)(2 + (1 - t)); // 2px closed, 1px open
Color panel_color = door_color;
panel_color.a = alpha;
DrawRectangle(x * TILE_SIZE + 1, py, TILE_SIZE - 2, height, panel_color);
if (t < 0.5f && is_currently_lit) {
if (t < 0.5f && light_factor > 0.05f) {
int hy = py + (swing_up ? -1 : height + 1);
Color handle_color = door_handle_color;
handle_color.a = alpha;
@ -356,72 +304,6 @@ void render_map(const Map *map, const Tileset *tileset) {
(Color){120, 90, 60, 255});
}
break;
case TILE_RUBBLE:
if (!decor_base_drawn)
DrawRectangleRec(dst, floor_color);
if (light_factor > 0.05f) {
Color stone = color_lerp((Color){54, 50, 46, 255}, (Color){126, 118, 102, 255}, light_factor);
Color light = color_lerp((Color){72, 68, 60, 255}, (Color){166, 156, 132, 255}, light_factor);
Color dark = color_lerp((Color){22, 20, 19, 255}, (Color){67, 60, 52, 255}, light_factor);
int ox = x * TILE_SIZE;
int oy = y * TILE_SIZE;
DrawRectangle(ox + 3, oy + 12, 10, 2, (Color){0, 0, 0, 55});
DrawRectangle(ox + 5, oy + 7, 5, 5, dark);
DrawRectangle(ox + 6, oy + 6, 5, 5, stone);
DrawLine(ox + 6, oy + 6, ox + 10, oy + 6, light);
DrawRectangle(ox + 2, oy + 10, 5, 3, dark);
DrawRectangle(ox + 3, oy + 9, 5, 3, stone);
DrawPixel(ox + 4, oy + 9, light);
DrawRectangle(ox + 10, oy + 10, 4, 3, dark);
DrawRectangle(ox + 10, oy + 9, 4, 3, stone);
DrawPixel(ox + 12, oy + 9, light);
DrawPixel(ox + 5, oy + 13, stone);
DrawPixel(ox + 12, oy + 13, stone);
}
break;
case TILE_SHALLOW_WATER:
if (!decor_base_drawn)
DrawRectangleRec(dst, floor_color);
if (light_factor > 0.05f) {
Color water = color_lerp((Color){12, 30, 39, 165}, (Color){36, 94, 116, 175}, light_factor);
Color edge = color_lerp((Color){7, 18, 24, 170}, (Color){23, 62, 76, 180}, light_factor);
Color glint = color_lerp((Color){48, 91, 105, 130}, (Color){128, 184, 196, 155}, light_factor);
int shimmer = (int)(sinf(GetTime() * 2.0f + (float)(x + y)) * 10.0f);
int ox = x * TILE_SIZE;
int oy = y * TILE_SIZE;
DrawRectangle(ox + 3, oy + 5, 10, 7, water);
DrawRectangle(ox + 4, oy + 4, 8, 1, water);
DrawRectangle(ox + 4, oy + 12, 8, 1, edge);
DrawPixel(ox + 2, oy + 7, edge);
DrawPixel(ox + 13, oy + 9, edge);
glint.a = (unsigned char)(glint.a + shimmer);
DrawLine(ox + 5, oy + 7, ox + 10, oy + 7, glint);
DrawLine(ox + 7, oy + 10, ox + 12, oy + 10, glint);
}
break;
case TILE_STATUE:
if (!decor_base_drawn)
DrawRectangleRec(dst, floor_color);
if (light_factor > 0.05f) {
Color stone = color_lerp((Color){52, 52, 56, 255}, (Color){142, 138, 128, 255}, light_factor);
Color shade = color_lerp((Color){22, 22, 26, 255}, (Color){68, 66, 64, 255}, light_factor);
Color light = color_lerp((Color){78, 78, 82, 255}, (Color){176, 170, 150, 255}, light_factor);
int ox = x * TILE_SIZE;
int oy = y * TILE_SIZE;
DrawRectangle(ox + 3, oy + 12, 10, 2, (Color){0, 0, 0, 70});
DrawRectangle(ox + 4, oy + 11, 8, 3, shade);
DrawRectangle(ox + 5, oy + 10, 6, 2, stone);
DrawRectangle(ox + 5, oy + 5, 6, 6, shade);
DrawRectangle(ox + 6, oy + 4, 5, 6, stone);
DrawRectangle(ox + 5, oy + 7, 1, 3, shade);
DrawRectangle(ox + 11, oy + 7, 1, 3, shade);
DrawRectangle(ox + 6, oy + 2, 4, 3, stone);
DrawRectangle(ox + 7, oy + 1, 2, 1, light);
DrawPixel(ox + 7, oy + 6, light);
DrawPixel(ox + 9, oy + 6, shade);
DrawLine(ox + 6, oy + 10, ox + 10, oy + 10, light);
}
break;
}
}
@ -438,9 +320,12 @@ void render_player(const Player *p, const Tileset *tileset, int frame_counter) {
Rectangle dst = {(float)(p->position.x * TILE_SIZE), (float)(p->position.y * TILE_SIZE), (float)TILE_SIZE,
(float)TILE_SIZE};
// Soft radial glow under the player
int cx = p->position.x * TILE_SIZE + TILE_SIZE / 2;
int cy = p->position.y * TILE_SIZE + TILE_SIZE / 2;
DrawEllipse(cx, cy + 5, 6.0f, 3.0f, (Color){0, 0, 0, 60});
DrawCircle(cx, cy, 14.0f, (Color){255, 220, 100, 20});
DrawCircle(cx, cy, 10.0f, (Color){255, 230, 150, 35});
DrawCircle(cx, cy, 6.0f, (Color){255, 240, 180, 55});
if (tileset != NULL && tileset->finalized) {
int tile_id = p->sprite_tile_id;