forked from NotAShelf/rogged
core: fix enemy movement collision with the player
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I8bfcc0f8816fbc02dbd7ad462b5c0a4e6a6a6964
This commit is contained in:
parent
0894466532
commit
5fbe7c8c60
3 changed files with 66 additions and 57 deletions
19
src/common.h
19
src/common.h
|
|
@ -56,9 +56,26 @@ typedef enum { ENEMY_GOBLIN, ENEMY_SKELETON, ENEMY_ORC } EnemyType;
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int x, y;
|
int x, y;
|
||||||
int hp;
|
int hp;
|
||||||
|
int max_hp;
|
||||||
int attack;
|
int attack;
|
||||||
int alive;
|
int alive;
|
||||||
EnemyType type;
|
EnemyType type;
|
||||||
} Enemy;
|
} Enemy;
|
||||||
|
|
||||||
#endif // COMMON_H
|
// GameState - encapsulates all game state for testability and save/load
|
||||||
|
typedef struct {
|
||||||
|
Player player;
|
||||||
|
Map map;
|
||||||
|
Dungeon dungeon;
|
||||||
|
Enemy enemies[MAX_ENEMIES];
|
||||||
|
int enemy_count;
|
||||||
|
Item items[MAX_ITEMS];
|
||||||
|
int item_count;
|
||||||
|
int game_over;
|
||||||
|
int game_won;
|
||||||
|
const char *last_message;
|
||||||
|
int message_timer;
|
||||||
|
int turn_count;
|
||||||
|
} GameState;
|
||||||
|
|
||||||
|
#endif // COMMON_H
|
||||||
|
|
|
||||||
32
src/enemy.c
32
src/enemy.c
|
|
@ -5,7 +5,7 @@
|
||||||
#include "rng.h"
|
#include "rng.h"
|
||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
int is_enemy_at(Enemy *enemies, int count, int x, int y);
|
int is_enemy_at(const Enemy *enemies, int count, int x, int y);
|
||||||
|
|
||||||
void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) {
|
void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) {
|
||||||
*count = 0;
|
*count = 0;
|
||||||
|
|
@ -47,19 +47,23 @@ void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) {
|
||||||
// Stats based on type and floor
|
// Stats based on type and floor
|
||||||
switch (e.type) {
|
switch (e.type) {
|
||||||
case ENEMY_GOBLIN:
|
case ENEMY_GOBLIN:
|
||||||
e.hp = ENEMY_BASE_HP + floor;
|
e.max_hp = ENEMY_BASE_HP + floor;
|
||||||
|
e.hp = e.max_hp;
|
||||||
e.attack = ENEMY_BASE_ATTACK;
|
e.attack = ENEMY_BASE_ATTACK;
|
||||||
break;
|
break;
|
||||||
case ENEMY_SKELETON:
|
case ENEMY_SKELETON:
|
||||||
e.hp = ENEMY_BASE_HP + floor + 2;
|
e.max_hp = ENEMY_BASE_HP + floor + 2;
|
||||||
|
e.hp = e.max_hp;
|
||||||
e.attack = ENEMY_BASE_ATTACK + 1;
|
e.attack = ENEMY_BASE_ATTACK + 1;
|
||||||
break;
|
break;
|
||||||
case ENEMY_ORC:
|
case ENEMY_ORC:
|
||||||
e.hp = ENEMY_BASE_HP + floor + 4;
|
e.max_hp = ENEMY_BASE_HP + floor + 4;
|
||||||
|
e.hp = e.max_hp;
|
||||||
e.attack = ENEMY_BASE_ATTACK + 2;
|
e.attack = ENEMY_BASE_ATTACK + 2;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
e.hp = ENEMY_BASE_HP;
|
e.max_hp = ENEMY_BASE_HP;
|
||||||
|
e.hp = e.max_hp;
|
||||||
e.attack = ENEMY_BASE_ATTACK;
|
e.attack = ENEMY_BASE_ATTACK;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +74,7 @@ void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if position has an enemy
|
// Check if position has an enemy
|
||||||
int is_enemy_at(Enemy *enemies, int count, int x, int y) {
|
int is_enemy_at(const Enemy *enemies, int count, int x, int y) {
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
if (enemies[i].alive && enemies[i].x == x && enemies[i].y == y) {
|
if (enemies[i].alive && enemies[i].x == x && enemies[i].y == y) {
|
||||||
return 1;
|
return 1;
|
||||||
|
|
@ -86,9 +90,13 @@ static int can_see_player(Enemy *e, Player *p) {
|
||||||
return (dx >= -1 && dx <= 1 && dy >= -1 && dy <= 1);
|
return (dx >= -1 && dx <= 1 && dy >= -1 && dy <= 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if position is occupied by player
|
||||||
|
static int is_player_at(Player *p, int x, int y) {
|
||||||
|
return (p->x == x && p->y == y);
|
||||||
|
}
|
||||||
|
|
||||||
// Move enemy toward player
|
// Move enemy toward player
|
||||||
static void enemy_move_toward_player(Enemy *e, Player *p, Map *map,
|
static void enemy_move_toward_player(Enemy *e, Player *p, Map *map, Enemy *all_enemies, int enemy_count) {
|
||||||
Enemy *all_enemies, int enemy_count) {
|
|
||||||
int dx = 0, dy = 0;
|
int dx = 0, dy = 0;
|
||||||
|
|
||||||
if (p->x > e->x)
|
if (p->x > e->x)
|
||||||
|
|
@ -105,14 +113,14 @@ static void enemy_move_toward_player(Enemy *e, Player *p, Map *map,
|
||||||
int new_x = e->x + dx;
|
int new_x = e->x + dx;
|
||||||
int new_y = e->y;
|
int new_y = e->y;
|
||||||
|
|
||||||
if (dx != 0 && is_floor(map, new_x, new_y) &&
|
if (dx != 0 && is_floor(map, new_x, new_y) && !is_enemy_at(all_enemies, enemy_count, new_x, new_y) &&
|
||||||
!is_enemy_at(all_enemies, enemy_count, new_x, new_y)) {
|
!is_player_at(p, new_x, new_y)) {
|
||||||
e->x = new_x;
|
e->x = new_x;
|
||||||
} else if (dy != 0) {
|
} else if (dy != 0) {
|
||||||
new_x = e->x;
|
new_x = e->x;
|
||||||
new_y = e->y + dy;
|
new_y = e->y + dy;
|
||||||
if (is_floor(map, new_x, new_y) &&
|
if (is_floor(map, new_x, new_y) && !is_enemy_at(all_enemies, enemy_count, new_x, new_y) &&
|
||||||
!is_enemy_at(all_enemies, enemy_count, new_x, new_y)) {
|
!is_player_at(p, new_x, new_y)) {
|
||||||
e->x = new_x;
|
e->x = new_x;
|
||||||
e->y = new_y;
|
e->y = new_y;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
72
src/render.c
72
src/render.c
|
|
@ -5,11 +5,10 @@
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
void render_map(Map *map) {
|
void render_map(const Map *map) {
|
||||||
for (int y = 0; y < MAP_HEIGHT; y++) {
|
for (int y = 0; y < MAP_HEIGHT; y++) {
|
||||||
for (int x = 0; x < MAP_WIDTH; x++) {
|
for (int x = 0; x < MAP_WIDTH; x++) {
|
||||||
Rectangle rect = {(float)(x * TILE_SIZE), (float)(y * TILE_SIZE),
|
Rectangle rect = {(float)(x * TILE_SIZE), (float)(y * TILE_SIZE), (float)TILE_SIZE, (float)TILE_SIZE};
|
||||||
(float)TILE_SIZE, (float)TILE_SIZE};
|
|
||||||
|
|
||||||
switch (map->tiles[y][x]) {
|
switch (map->tiles[y][x]) {
|
||||||
case TILE_WALL:
|
case TILE_WALL:
|
||||||
|
|
@ -28,32 +27,30 @@ void render_map(Map *map) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void render_player(Player *p) {
|
void render_player(const Player *p) {
|
||||||
Rectangle rect = {(float)(p->x * TILE_SIZE), (float)(p->y * TILE_SIZE),
|
Rectangle rect = {(float)(p->x * TILE_SIZE), (float)(p->y * TILE_SIZE), (float)TILE_SIZE, (float)TILE_SIZE};
|
||||||
(float)TILE_SIZE, (float)TILE_SIZE};
|
|
||||||
DrawRectangleRec(rect, BLUE);
|
DrawRectangleRec(rect, BLUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void render_enemies(Enemy *enemies, int count) {
|
void render_enemies(const Enemy *enemies, int count) {
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
if (!enemies[i].alive)
|
if (!enemies[i].alive)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Rectangle rect = {(float)(enemies[i].x * TILE_SIZE),
|
Rectangle rect = {(float)(enemies[i].x * TILE_SIZE), (float)(enemies[i].y * TILE_SIZE), (float)TILE_SIZE,
|
||||||
(float)(enemies[i].y * TILE_SIZE), (float)TILE_SIZE,
|
|
||||||
(float)TILE_SIZE};
|
(float)TILE_SIZE};
|
||||||
|
|
||||||
// Different colors based on enemy type
|
// Different colors based on enemy type
|
||||||
Color enemy_color;
|
Color enemy_color;
|
||||||
switch (enemies[i].type) {
|
switch (enemies[i].type) {
|
||||||
case ENEMY_GOBLIN:
|
case ENEMY_GOBLIN:
|
||||||
enemy_color = (Color){150, 50, 50, 255}; // dark red
|
enemy_color = (Color){150, 50, 50, 255}; // dark red
|
||||||
break;
|
break;
|
||||||
case ENEMY_SKELETON:
|
case ENEMY_SKELETON:
|
||||||
enemy_color = (Color){200, 200, 200, 255}; // light gray
|
enemy_color = (Color){200, 200, 200, 255}; // light gray
|
||||||
break;
|
break;
|
||||||
case ENEMY_ORC:
|
case ENEMY_ORC:
|
||||||
enemy_color = (Color){50, 150, 50, 255}; // dark green
|
enemy_color = (Color){50, 150, 50, 255}; // dark green
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
enemy_color = RED;
|
enemy_color = RED;
|
||||||
|
|
@ -63,37 +60,34 @@ void render_enemies(Enemy *enemies, int count) {
|
||||||
DrawRectangleRec(rect, enemy_color);
|
DrawRectangleRec(rect, enemy_color);
|
||||||
|
|
||||||
// Draw hp bar above enemy
|
// Draw hp bar above enemy
|
||||||
int hp_percent =
|
int hp_percent = (enemies[i].hp * TILE_SIZE) / enemies[i].max_hp;
|
||||||
(enemies[i].hp * TILE_SIZE) / 10; // FIXME: assuming max 10 hp, for now
|
|
||||||
if (hp_percent > 0) {
|
if (hp_percent > 0) {
|
||||||
Rectangle hp_bar = {(float)(enemies[i].x * TILE_SIZE),
|
Rectangle hp_bar = {(float)(enemies[i].x * TILE_SIZE), (float)(enemies[i].y * TILE_SIZE - 4), (float)hp_percent,
|
||||||
(float)(enemies[i].y * TILE_SIZE - 4),
|
3};
|
||||||
(float)hp_percent, 3};
|
|
||||||
DrawRectangleRec(hp_bar, GREEN);
|
DrawRectangleRec(hp_bar, GREEN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void render_items(Item *items, int count) {
|
void render_items(const Item *items, int count) {
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
if (items[i].picked_up)
|
if (items[i].picked_up)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Rectangle rect = {(float)(items[i].x * TILE_SIZE),
|
Rectangle rect = {(float)(items[i].x * TILE_SIZE), (float)(items[i].y * TILE_SIZE), (float)TILE_SIZE,
|
||||||
(float)(items[i].y * TILE_SIZE), (float)TILE_SIZE,
|
|
||||||
(float)TILE_SIZE};
|
(float)TILE_SIZE};
|
||||||
|
|
||||||
// Different colors based on item type
|
// Different colors based on item type
|
||||||
Color item_color;
|
Color item_color;
|
||||||
switch (items[i].type) {
|
switch (items[i].type) {
|
||||||
case ITEM_POTION:
|
case ITEM_POTION:
|
||||||
item_color = (Color){255, 100, 100, 255}; // red/pink
|
item_color = (Color){255, 100, 100, 255}; // red/pink
|
||||||
break;
|
break;
|
||||||
case ITEM_WEAPON:
|
case ITEM_WEAPON:
|
||||||
item_color = (Color){255, 255, 100, 255}; // yellow
|
item_color = (Color){255, 255, 100, 255}; // yellow
|
||||||
break;
|
break;
|
||||||
case ITEM_ARMOR:
|
case ITEM_ARMOR:
|
||||||
item_color = (Color){100, 100, 255, 255}; // blue
|
item_color = (Color){100, 100, 255, 255}; // blue
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
item_color = GREEN;
|
item_color = GREEN;
|
||||||
|
|
@ -104,15 +98,13 @@ void render_items(Item *items, int count) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void render_ui(Player *p) {
|
void render_ui(const Player *p) {
|
||||||
// UI background bar at bottom of screen
|
// UI background bar at bottom of screen
|
||||||
Rectangle ui_bg = {0, (float)(MAP_HEIGHT * TILE_SIZE), (float)SCREEN_WIDTH,
|
Rectangle ui_bg = {0, (float)(MAP_HEIGHT * TILE_SIZE), (float)SCREEN_WIDTH, 60};
|
||||||
60};
|
|
||||||
DrawRectangleRec(ui_bg, (Color){30, 30, 30, 255});
|
DrawRectangleRec(ui_bg, (Color){30, 30, 30, 255});
|
||||||
|
|
||||||
// Draw dividing line
|
// Draw dividing line
|
||||||
DrawLine(0, MAP_HEIGHT * TILE_SIZE, SCREEN_WIDTH, MAP_HEIGHT * TILE_SIZE,
|
DrawLine(0, MAP_HEIGHT * TILE_SIZE, SCREEN_WIDTH, MAP_HEIGHT * TILE_SIZE, GRAY);
|
||||||
GRAY);
|
|
||||||
|
|
||||||
// Player hp
|
// Player hp
|
||||||
char hp_text[32];
|
char hp_text[32];
|
||||||
|
|
@ -136,8 +128,7 @@ void render_ui(Player *p) {
|
||||||
|
|
||||||
// Inventory count
|
// Inventory count
|
||||||
char inv_text[32];
|
char inv_text[32];
|
||||||
snprintf(inv_text, sizeof(inv_text), "Inv: %d/%d", p->inventory_count,
|
snprintf(inv_text, sizeof(inv_text), "Inv: %d/%d", p->inventory_count, MAX_INVENTORY);
|
||||||
MAX_INVENTORY);
|
|
||||||
DrawText(inv_text, 530, MAP_HEIGHT * TILE_SIZE + 10, 16, GREEN);
|
DrawText(inv_text, 530, MAP_HEIGHT * TILE_SIZE + 10, 16, GREEN);
|
||||||
|
|
||||||
// Show first item in inventory if any
|
// Show first item in inventory if any
|
||||||
|
|
@ -158,14 +149,12 @@ void render_ui(Player *p) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
char first_item[48];
|
char first_item[48];
|
||||||
snprintf(first_item, sizeof(first_item), "[%s +%d]", item_name,
|
snprintf(first_item, sizeof(first_item), "[%s +%d]", item_name, p->inventory[0].power);
|
||||||
p->inventory[0].power);
|
|
||||||
DrawText(first_item, 10, MAP_HEIGHT * TILE_SIZE + 35, 14, LIGHTGRAY);
|
DrawText(first_item, 10, MAP_HEIGHT * TILE_SIZE + 35, 14, LIGHTGRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Controls hint
|
// Controls hint
|
||||||
DrawText("WASD: Move | U: Use Item | Q: Quit", 280,
|
DrawText("WASD: Move | U: Use Item | Q: Quit", 280, MAP_HEIGHT * TILE_SIZE + 35, 14, GRAY);
|
||||||
MAP_HEIGHT * TILE_SIZE + 35, 14, GRAY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void render_game_over(void) {
|
void render_game_over(void) {
|
||||||
|
|
@ -176,13 +165,11 @@ void render_game_over(void) {
|
||||||
// Game over text
|
// Game over text
|
||||||
const char *title = "GAME OVER";
|
const char *title = "GAME OVER";
|
||||||
int title_width = MeasureText(title, 60);
|
int title_width = MeasureText(title, 60);
|
||||||
DrawText(title, (SCREEN_WIDTH - title_width) / 2, SCREEN_HEIGHT / 2 - 30, 60,
|
DrawText(title, (SCREEN_WIDTH - title_width) / 2, SCREEN_HEIGHT / 2 - 30, 60, RED);
|
||||||
RED);
|
|
||||||
|
|
||||||
const char *subtitle = "Press R to restart or Q to quit";
|
const char *subtitle = "Press R to restart or Q to quit";
|
||||||
int sub_width = MeasureText(subtitle, 20);
|
int sub_width = MeasureText(subtitle, 20);
|
||||||
DrawText(subtitle, (SCREEN_WIDTH - sub_width) / 2, SCREEN_HEIGHT / 2 + 40, 20,
|
DrawText(subtitle, (SCREEN_WIDTH - sub_width) / 2, SCREEN_HEIGHT / 2 + 40, 20, WHITE);
|
||||||
WHITE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void render_message(const char *message) {
|
void render_message(const char *message) {
|
||||||
|
|
@ -190,13 +177,10 @@ void render_message(const char *message) {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Draw message box
|
// Draw message box
|
||||||
Rectangle msg_bg = {(float)(SCREEN_WIDTH / 2 - 150),
|
Rectangle msg_bg = {(float)(SCREEN_WIDTH / 2 - 150), (float)(SCREEN_HEIGHT / 2 - 30), 300, 60};
|
||||||
(float)(SCREEN_HEIGHT / 2 - 30), 300, 60};
|
|
||||||
DrawRectangleRec(msg_bg, (Color){50, 50, 50, 230});
|
DrawRectangleRec(msg_bg, (Color){50, 50, 50, 230});
|
||||||
DrawRectangleLines((int)msg_bg.x, (int)msg_bg.y, (int)msg_bg.width,
|
DrawRectangleLines((int)msg_bg.x, (int)msg_bg.y, (int)msg_bg.width, (int)msg_bg.height, WHITE);
|
||||||
(int)msg_bg.height, WHITE);
|
|
||||||
|
|
||||||
int msg_width = MeasureText(message, 20);
|
int msg_width = MeasureText(message, 20);
|
||||||
DrawText(message, (SCREEN_WIDTH - msg_width) / 2, SCREEN_HEIGHT / 2 - 10, 20,
|
DrawText(message, (SCREEN_WIDTH - msg_width) / 2, SCREEN_HEIGHT / 2 - 10, 20, WHITE);
|
||||||
WHITE);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue