diff --git a/libs/map/map.c b/libs/map/map.c index 74f1c80..518b89c 100644 --- a/libs/map/map.c +++ b/libs/map/map.c @@ -15,6 +15,9 @@ void map_init(Map *map) { } memset(map->light_map, 0, sizeof(map->light_map)); memset(map->remembered, 0, sizeof(map->remembered)); + memset(map->door_open_from, 255, sizeof(map->door_open_from)); + memset(map->door_anim_timer, 0, sizeof(map->door_anim_timer)); + memset(map->door_anim_target, 0, sizeof(map->door_anim_target)); map->room_count = 0; } @@ -22,7 +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_RUINED || map->tiles[y][x] == TILE_DOOR_CLOSED; } void get_room_center(Room *room, int *cx, int *cy) { @@ -128,13 +131,59 @@ static int is_room_boundary(Map *map, int x, int y) { return 0; } -// Place doors at corridor-room junctions -// DISABLED: Door placement removed per user request +// A corridor tile is a narrow floor passage with walls on at least 2 sides. +static int is_corridor_tile(const Map *map, int x, int y) { + if (map->tiles[y][x] != TILE_FLOOR) + return 0; + int walls = 0; + if (y > 0 && map->tiles[y - 1][x] == TILE_WALL) + walls++; + if (y < MAP_HEIGHT - 1 && map->tiles[y + 1][x] == TILE_WALL) + walls++; + if (x > 0 && map->tiles[y][x - 1] == TILE_WALL) + walls++; + if (x < MAP_WIDTH - 1 && map->tiles[y][x + 1] == TILE_WALL) + walls++; + return walls >= 2; +} + +static int tile_in_any_room(int x, int y, Room *rooms, int room_count) { + for (int i = 0; i < room_count; i++) { + if (x >= rooms[i].x && x < rooms[i].x + rooms[i].w && y >= rooms[i].y && y < rooms[i].y + rooms[i].h) + return 1; + } + return 0; +} + +// 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) { - (void)map; - (void)rooms; - (void)room_count; - // No-op: doors disabled + for (int y = 0; y < MAP_HEIGHT; y++) { + for (int x = 0; x < MAP_WIDTH; x++) { + if (map->tiles[y][x] != TILE_FLOOR) + continue; + if (!is_corridor_tile(map, x, y)) + continue; + // Don't place doors inside rooms — corridors are between rooms + if (tile_in_any_room(x, y, rooms, room_count)) + continue; + // Corridor must be adjacent to a room floor tile + 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 = x + dx[i]; + int ny = y + dy[i]; + if (!in_bounds(nx, ny, MAP_WIDTH, MAP_HEIGHT)) + continue; + if (map->tiles[ny][nx] != TILE_FLOOR) + continue; + if (tile_in_any_room(nx, ny, rooms, room_count)) { + map->tiles[y][x] = TILE_DOOR_CLOSED; + break; + } + } + } + } } // Connect all rooms with corridors @@ -276,7 +325,8 @@ static int trace_line_of_sight(const Map *map, int x1, int y1, int x2, int y2) { if (x == x2 && y == y2) return 1; - if (map->tiles[y][x] == TILE_WALL && !(x == x1 && y == y1)) + TileType t = map->tiles[y][x]; + if ((t == TILE_WALL || t == TILE_DOOR_CLOSED) && !(x == x1 && y == y1)) return 0; int e2 = 2 * err; @@ -421,7 +471,17 @@ void compute_lighting(Map *map, const LightSource *sources, int num_sources) { for (int ty = 0; ty < MAP_HEIGHT; ty++) { for (int tx = 0; tx < MAP_WIDTH; tx++) { - if (tile_brightness(map, tx, ty) > LIGHT_SIGHT_THRESHOLD) + int max_bright = 0; + int bx = tx * SUB_TILE_RES; + int by = ty * SUB_TILE_RES; + for (int dy = 0; dy < SUB_TILE_RES; dy++) { + for (int dx = 0; dx < SUB_TILE_RES; dx++) { + int v = map->light_map[by + dy][bx + dx]; + if (v > max_bright) + max_bright = v; + } + } + if (max_bright > LIGHT_SIGHT_THRESHOLD) map->remembered[ty][tx] = 1; } } diff --git a/src/common.h b/src/common.h index 2db9859..8df2f72 100644 --- a/src/common.h +++ b/src/common.h @@ -43,6 +43,9 @@ typedef struct { int room_count; unsigned char light_map[MAP_HEIGHT * SUB_TILE_RES][MAP_WIDTH * SUB_TILE_RES]; unsigned char remembered[MAP_HEIGHT][MAP_WIDTH]; + unsigned char door_open_from[MAP_HEIGHT][MAP_WIDTH]; // 0=N,1=S,2=E,3=W,255=closed + unsigned char door_anim_timer[MAP_HEIGHT][MAP_WIDTH]; + unsigned char door_anim_target[MAP_HEIGHT][MAP_WIDTH]; // 0=closed, 1=open } Map; // Dungeon diff --git a/src/main.c b/src/main.c index 83007cd..1b8383c 100644 --- a/src/main.c +++ b/src/main.c @@ -231,6 +231,19 @@ static void post_action(GameState *gs, Enemy *attacked_enemy) { } } + // Close doors that the player moved away from before recomputing lighting. + for (int dy = 0; dy < MAP_HEIGHT; dy++) { + for (int dx = 0; dx < MAP_WIDTH; dx++) { + if (gs->map.tiles[dy][dx] == TILE_DOOR_OPEN) { + if (gs->player.position.x != dx || gs->player.position.y != dy) { + gs->map.tiles[dy][dx] = TILE_DOOR_CLOSED; + gs->map.door_anim_target[dy][dx] = 0; + gs->map.door_anim_timer[dy][dx] = DOOR_ANIM_FRAMES; + } + } + } + } + // Update visibility based on player's new position LightSource p_light = {gs->player.position.x, gs->player.position.y, PLAYER_LIGHT_INTENSITY, PLAYER_LIGHT_RANGE}; LightSource srcs[1 + 32]; @@ -277,6 +290,18 @@ static int handle_stun_turn(GameState *gs) { if (gs->game_over) return 1; enemy_update_all(gs->enemies, gs->enemy_count, &gs->player, &gs->map); + // Close doors that the player moved away from before recomputing lighting. + for (int dy = 0; dy < MAP_HEIGHT; dy++) { + for (int dx = 0; dx < MAP_WIDTH; dx++) { + if (gs->map.tiles[dy][dx] == TILE_DOOR_OPEN) { + if (gs->player.position.x != dx || gs->player.position.y != dy) { + gs->map.tiles[dy][dx] = TILE_DOOR_CLOSED; + gs->map.door_anim_target[dy][dx] = 0; + gs->map.door_anim_timer[dy][dx] = DOOR_ANIM_FRAMES; + } + } + } + } { LightSource l = {gs->player.position.x, gs->player.position.y, PLAYER_LIGHT_INTENSITY, PLAYER_LIGHT_RANGE}; LightSource s[1 + 32]; @@ -639,6 +664,17 @@ static void game_loop(unsigned int run_seed, FontManager *fm) { if (gs.slash_timer > 0) gs.slash_timer--; + // Door animations are visual, so they tick every rendered frame. + for (int y = 0; y < MAP_HEIGHT; y++) { + for (int x = 0; x < MAP_WIDTH; x++) { + if (gs.map.door_anim_timer[y][x] > 0) { + gs.map.door_anim_timer[y][x]--; + if (gs.map.door_anim_timer[y][x] == 0 && gs.map.door_anim_target[y][x] == 0) + gs.map.door_open_from[y][x] = 255; + } + } + } + // Update player animation if (gs.player.anim_timer > 0) { gs.player.anim_timer--; @@ -787,4 +823,4 @@ int main(int argc, char **argv) { audio_close(); return 0; -} \ No newline at end of file +} diff --git a/src/movement.c b/src/movement.c index 89e74e1..23afd5c 100644 --- a/src/movement.c +++ b/src/movement.c @@ -1,6 +1,7 @@ #include "movement.h" #include "enemy.h" #include "map/map.h" +#include "map/utils.h" #include // Check if position is occupied by player @@ -8,11 +9,33 @@ static int is_player_at(Player *p, int x, int y) { return (p->position.x == x && p->position.y == y); } +static int direction_to_door_open_from(Vec2 dir) { + if (dir.y == -1) + return 0; // N + if (dir.y == 1) + return 1; // S + if (dir.x == 1) + return 2; // E + if (dir.x == -1) + return 3; // W + return 255; +} + MoveResult try_move_entity(Vec2 *p, Vec2 direction, Map *map, Player *player, Enemy *enemies, int enemy_count, bool moving_is_player) { int new_x = p->x + direction.x; int new_y = p->y + direction.y; + if (!in_bounds(new_x, new_y, MAP_WIDTH, MAP_HEIGHT)) + return MOVE_RESULT_BLOCKED_WALL; + + if (map->tiles[new_y][new_x] == TILE_DOOR_CLOSED) { + map->tiles[new_y][new_x] = TILE_DOOR_OPEN; + map->door_open_from[new_y][new_x] = direction_to_door_open_from(direction); + map->door_anim_target[new_y][new_x] = 1; + map->door_anim_timer[new_y][new_x] = DOOR_ANIM_FRAMES; + } + if (!is_floor(map, new_x, new_y)) return MOVE_RESULT_BLOCKED_WALL; diff --git a/src/render.c b/src/render.c index f18254b..c88584e 100644 --- a/src/render.c +++ b/src/render.c @@ -66,6 +66,43 @@ static void draw_text_body(Font f, const char *text, float x, float y, int size, DrawTextEx(f, text, (Vector2){x, y}, (float)size, spacing, c); } +static int is_deep_corridor(const Map *map, int x, int y) { + if (map->tiles[y][x] != TILE_FLOOR) + return 0; + int walls_ns = (y > 0 && map->tiles[y - 1][x] == TILE_WALL ? 1 : 0) + + (y < MAP_HEIGHT - 1 && map->tiles[y + 1][x] == TILE_WALL ? 1 : 0); + int walls_ew = (x > 0 && map->tiles[y][x - 1] == TILE_WALL ? 1 : 0) + + (x < MAP_WIDTH - 1 && map->tiles[y][x + 1] == TILE_WALL ? 1 : 0); + int is_corridor = (walls_ns >= 2 && walls_ew == 0) || (walls_ew >= 2 && walls_ns == 0); + if (!is_corridor) + return 0; + // A "deep" corridor is surrounded by corridor on all floor sides. + // If any floor neighbor is NOT a corridor, this is an entrance/exit. + const int dx4[4] = {0, 0, 1, -1}; + const int dy4[4] = {1, -1, 0, 0}; + for (int i = 0; i < 4; i++) { + int nx = x + dx4[i]; + int ny = y + dy4[i]; + if (nx < 0 || nx >= MAP_WIDTH || ny < 0 || ny >= MAP_HEIGHT) + continue; + if (map->tiles[ny][nx] != TILE_FLOOR) + continue; + int nw_ns = (ny > 0 && map->tiles[ny - 1][nx] == TILE_WALL ? 1 : 0) + + (ny < MAP_HEIGHT - 1 && map->tiles[ny + 1][nx] == TILE_WALL ? 1 : 0); + int nw_ew = (nx > 0 && map->tiles[ny][nx - 1] == TILE_WALL ? 1 : 0) + + (nx < MAP_WIDTH - 1 && map->tiles[ny][nx + 1] == TILE_WALL ? 1 : 0); + int neighbor_is_corridor = (nw_ns >= 2 && nw_ew == 0) || (nw_ew >= 2 && nw_ns == 0); + if (!neighbor_is_corridor) + return 0; + } + return 1; +} + +static Color color_lerp(Color a, Color b, float t) { + return (Color){(unsigned char)(a.r + (int)((b.r - a.r) * t)), (unsigned char)(a.g + (int)((b.g - a.g) * t)), + (unsigned char)(a.b + (int)((b.b - a.b) * t)), (unsigned char)(a.a + (int)((b.a - a.a) * t))}; +} + 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++) { @@ -78,6 +115,11 @@ void render_map(const Map *map, const Tileset *tileset) { continue; } + 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; + int tile_id = -1; switch (map->tiles[y][x]) { case TILE_WALL: @@ -100,94 +142,175 @@ void render_map(const Map *map, const Tileset *tileset) { break; } - if (tile_id >= 0 && tileset != NULL && tileset->finalized) { - Rectangle src = tileset_get_region(tileset, tile_id); - if (src.width > 0) { - Color tint; - if (brightness > 0) { - float lit = (float)brightness / 255.0f; - tint = (Color){(unsigned char)(128 + (int)(127.0f * lit)), (unsigned char)(128 + (int)(127.0f * lit)), - (unsigned char)(128 + (int)(127.0f * lit)), 255}; - } else { - tint = (Color){128, 128, 128, 255}; - } - DrawTexturePro(tileset->atlas, src, dst, (Vector2){0, 0}, 0.0f, tint); - continue; + int is_door = (map->tiles[y][x] == TILE_DOOR_CLOSED || map->tiles[y][x] == TILE_DOOR_OPEN); + int tile_drawn = 0; + + // Draw floor underneath doors using tileset if available, so the + // floor matches adjacent tiles + if (is_door && tileset != NULL && tileset->finalized) { + 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) { + 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; } } - Color wall_color = brightness > 0 ? DARKGRAY : (Color){25, 25, 30, 255}; - Color floor_color = brightness > 0 ? BLACK : (Color){15, 15, 20, 255}; - Color stairs_color = brightness > 0 ? (Color){180, 160, 100, 255} : (Color){60, 55, 50, 255}; - Color door_color = brightness > 0 ? (Color){139, 119, 89, 255} : (Color){60, 55, 50, 255}; + 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); + 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; + } + } - switch (map->tiles[y][x]) { - case TILE_WALL: - DrawRectangleRec(dst, wall_color); - 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 (!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); + + switch (map->tiles[y][x]) { + case TILE_WALL: + DrawRectangleRec(dst, wall_color); + 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}); + } } - if (is_adjacent_to_stairs && brightness > 0) { - 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}); } + break; + case TILE_STAIRS: + DrawRectangleRec(dst, stairs_color); + // Make stairs very visible with bright symbol and bounce + { + int bounce = (int)(sinf(GetTime() * 3.0f) * 1.5f); + if (light_factor > 0.05f) + DrawText(">", x * TILE_SIZE + 4, y * TILE_SIZE + 1 + bounce, NORM_FONT + 2, (Color){255, 255, 200, 255}); + else + DrawText(">", x * TILE_SIZE + 4, y * TILE_SIZE + 1 + bounce, NORM_FONT + 2, (Color){100, 90, 70, 255}); + } + break; + case TILE_DOOR_CLOSED: + case TILE_DOOR_OPEN: { + // Determine door orientation by adjacent walls + int wall_n = (y > 0 && map->tiles[y - 1][x] == TILE_WALL); + int wall_s = (y < MAP_HEIGHT - 1 && map->tiles[y + 1][x] == TILE_WALL); + int wall_e = (x < MAP_WIDTH - 1 && map->tiles[y][x + 1] == TILE_WALL); + int wall_w = (x > 0 && map->tiles[y][x - 1] == TILE_WALL); + int is_vertical = (wall_n && wall_s) || (!(wall_e && wall_w) && (wall_e || wall_w)); + + // Animation progress: 0=fully closed, 1=fully open + float t = 0.0f; + if (map->door_anim_timer[y][x] > 0) { + float progress = 1.0f - (float)map->door_anim_timer[y][x] / DOOR_ANIM_FRAMES; + t = map->door_anim_target[y][x] ? progress : (1.0f - progress); + } else { + t = (map->tiles[y][x] == TILE_DOOR_OPEN) ? 1.0f : 0.0f; + } + + if (!tile_drawn) + DrawRectangleRec(dst, floor_color); + + if (is_vertical) { + // Vertical door: swings left/right toward approaching wall + int open_dir = map->door_open_from[y][x]; + int swing_to_left = 0; + if (open_dir == 2) + swing_to_left = 1; // approached from E, swing W (left) + else if (open_dir == 3) + swing_to_left = 0; // approached from W, swing E (right) + else + swing_to_left = (x % 2 == 0); // deterministic fallback + + int closed_x = x * TILE_SIZE + 7; // center + 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)); + 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 && light_factor > 0.05f) { + int hx = px + (swing_to_left ? -1 : width + 1); + Color handle_color = door_handle_color; + handle_color.a = alpha; + DrawRectangle(hx, y * TILE_SIZE + 6, 1, 2, handle_color); + } + } else { + // Horizontal door: swings up/down toward approaching wall + int open_dir = map->door_open_from[y][x]; + int swing_up = 0; + if (open_dir == 0) + swing_up = 1; // approached from N, swing S (down) + else if (open_dir == 1) + swing_up = 0; // approached from S, swing N (up) + else + swing_up = (y % 2 == 0); // deterministic fallback + + int closed_y = y * TILE_SIZE + 7; // center + 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)); + 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 && light_factor > 0.05f) { + int hy = py + (swing_up ? -1 : height + 1); + Color handle_color = door_handle_color; + handle_color.a = alpha; + DrawRectangle(x * TILE_SIZE + 6, hy, 2, 1, handle_color); + } + } + break; } - // Grid lines - if (DRAW_GRID_LINES && brightness > 0) { - DrawRectangleLines((int)dst.x, (int)dst.y, (int)dst.width, (int)dst.height, (Color){20, 20, 20, 80}); + case TILE_DOOR_RUINED: + DrawRectangleRec(dst, (Color){60, 45, 30, 255}); + if (light_factor > 0.05f) { + DrawRectangle(x * TILE_SIZE + 1, y * TILE_SIZE + 1, TILE_SIZE - 2, TILE_SIZE - 2, (Color){80, 60, 40, 200}); + DrawLine(x * TILE_SIZE + 2, y * TILE_SIZE + 2, x * TILE_SIZE + TILE_SIZE - 2, y * TILE_SIZE + TILE_SIZE - 2, + (Color){120, 90, 60, 255}); + DrawLine(x * TILE_SIZE + TILE_SIZE - 2, y * TILE_SIZE + 2, x * TILE_SIZE + 2, y * TILE_SIZE + TILE_SIZE - 2, + (Color){120, 90, 60, 255}); + } + break; } - break; - case TILE_STAIRS: - DrawRectangleRec(dst, stairs_color); - // Make stairs very visible with bright symbol and bounce - { - int bounce = (int)(sinf(GetTime() * 3.0f) * 1.5f); - if (brightness > 0) - DrawText(">", x * TILE_SIZE + 4, y * TILE_SIZE + 1 + bounce, NORM_FONT + 2, (Color){255, 255, 200, 255}); - else - DrawText(">", x * TILE_SIZE + 4, y * TILE_SIZE + 1 + bounce, NORM_FONT + 2, (Color){100, 90, 70, 255}); - } - break; - case TILE_DOOR_CLOSED: - DrawRectangleRec(dst, door_color); - if (brightness > 0) { - DrawRectangle(x * TILE_SIZE + 2, y * TILE_SIZE + 2, TILE_SIZE - 4, TILE_SIZE - 4, (Color){100, 80, 60, 255}); - DrawRectangleLines(x * TILE_SIZE + 2, y * TILE_SIZE + 2, TILE_SIZE - 4, TILE_SIZE - 4, - (Color){60, 50, 40, 255}); - DrawText("+", x * TILE_SIZE + 5, y * TILE_SIZE + 1, NORM_FONT, WHITE); - } - break; - case TILE_DOOR_OPEN: - DrawRectangleRec(dst, floor_color); - if (brightness > 0) { - DrawRectangle(x * TILE_SIZE + 2, y * TILE_SIZE + 2, TILE_SIZE - 4, TILE_SIZE - 4, (Color){80, 70, 50, 180}); - DrawRectangleLines(x * TILE_SIZE + 2, y * TILE_SIZE + 2, TILE_SIZE - 4, TILE_SIZE - 4, - (Color){60, 50, 40, 200}); - DrawText("'", x * TILE_SIZE + 6, y * TILE_SIZE + 2, NORM_FONT, (Color){150, 140, 120, 255}); - } - break; - case TILE_DOOR_RUINED: - DrawRectangleRec(dst, (Color){60, 45, 30, 255}); - if (brightness > 0) { - DrawRectangle(x * TILE_SIZE + 1, y * TILE_SIZE + 1, TILE_SIZE - 2, TILE_SIZE - 2, (Color){80, 60, 40, 200}); - DrawLine(x * TILE_SIZE + 2, y * TILE_SIZE + 2, x * TILE_SIZE + TILE_SIZE - 2, y * TILE_SIZE + TILE_SIZE - 2, - (Color){120, 90, 60, 255}); - DrawLine(x * TILE_SIZE + TILE_SIZE - 2, y * TILE_SIZE + 2, x * TILE_SIZE + 2, y * TILE_SIZE + TILE_SIZE - 2, - (Color){120, 90, 60, 255}); - } - break; + } + + // Wall base shadow: dark strip at bottom of wall tiles + if (map->tiles[y][x] == TILE_WALL && brightness > 0) { + int shadow_alpha = (int)(60.0f * ((float)brightness / 255.0f)); + DrawRectangle(dst.x, dst.y + dst.height - 2, dst.width, 2, (Color){0, 0, 0, shadow_alpha}); } } } @@ -197,6 +320,13 @@ 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; + 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; switch (p->anim_state) { @@ -412,7 +542,7 @@ void render_ui(const Player *p, const Tileset *tileset, const FontManager *fm) { // HUD Panel const int hud_y = MAP_HEIGHT * TILE_SIZE; const int hud_height = 60; - const Color hud_bg = {25, 20, 15, 255}; // dark parchment + const Color hud_bg = {20, 18, 22, 255}; // dark bluish to separate from map const Color hud_border = {139, 119, 89, 255}; // bronze/brown border const Color text_dim = {160, 150, 140, 255}; // dimmed text const Color text_bright = {240, 230, 220, 255}; // bright text @@ -420,6 +550,8 @@ void render_ui(const Player *p, const Tileset *tileset, const FontManager *fm) { // Main HUD background with border Rectangle ui_bg = {0, (float)hud_y, (float)SCREEN_WIDTH, (float)hud_height}; DrawRectangleRec(ui_bg, hud_bg); + // Subtle shadow separating HUD from game view + DrawLine(0, hud_y - 1, SCREEN_WIDTH, hud_y - 1, (Color){0, 0, 0, 80}); DrawRectangleLines(0, hud_y, SCREEN_WIDTH, hud_height, hud_border); DrawLine(0, hud_y + 1, SCREEN_WIDTH, hud_y + 1, (Color){60, 55, 50, 255}); DrawLine(0, hud_y + hud_height - 2, SCREEN_WIDTH, hud_y + hud_height - 2, (Color){15, 12, 10, 255}); diff --git a/src/settings.h b/src/settings.h index 19ecf14..246843f 100644 --- a/src/settings.h +++ b/src/settings.h @@ -84,6 +84,9 @@ // Sub-tile lighting #define SUB_TILE_RES 8 #define LIGHT_SIGHT_THRESHOLD 40 +#define AMBIENT_LIGHT_FACTOR 0.08f +#define REMEMBERED_LIGHT_FACTOR 0.18f +#define LIGHT_EXPONENT 1.7f // Player light source parameters #define PLAYER_LIGHT_RANGE 8 @@ -94,5 +97,6 @@ // Visual polish #define DRAW_GRID_LINES 1 +#define DOOR_ANIM_FRAMES 8 #endif // SETTINGS_H