enemy: add alert memory; vision variance based on type

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I2f5c7cac72c8772e5871b99026d106b46a6a6964
This commit is contained in:
raf 2026-04-09 12:31:52 +03:00
commit dbf8d4886c
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
3 changed files with 90 additions and 3 deletions

View file

@ -100,6 +100,11 @@ typedef struct {
int status_chance; int status_chance;
int crit_chance; // crit chance percentage (0-100) int crit_chance; // crit chance percentage (0-100)
int crit_mult; // crit damage multiplier percentage (e.g. 150 = 1.5x) int crit_mult; // crit damage multiplier percentage (e.g. 150 = 1.5x)
// vision
int vision_range;
int alert; // 1 = aware of player, searching
int last_known_x; // last position where enemy saw player
int last_known_y;
// status effects // status effects
StatusEffect effects[MAX_EFFECTS]; StatusEffect effects[MAX_EFFECTS];
int effect_count; int effect_count;

View file

@ -69,6 +69,7 @@ void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) {
e.resistance[DMG_PIERCE] = 0; e.resistance[DMG_PIERCE] = 0;
e.resistance[DMG_FIRE] = -25; e.resistance[DMG_FIRE] = -25;
e.resistance[DMG_POISON] = 50; e.resistance[DMG_POISON] = 50;
e.vision_range = 7;
break; break;
case ENEMY_SKELETON: case ENEMY_SKELETON:
e.max_hp = ENEMY_BASE_HP + floor + 2; e.max_hp = ENEMY_BASE_HP + floor + 2;
@ -86,6 +87,7 @@ void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) {
e.resistance[DMG_PIERCE] = 50; e.resistance[DMG_PIERCE] = 50;
e.resistance[DMG_FIRE] = 25; e.resistance[DMG_FIRE] = 25;
e.resistance[DMG_POISON] = 75; e.resistance[DMG_POISON] = 75;
e.vision_range = 6;
break; break;
case ENEMY_ORC: case ENEMY_ORC:
e.max_hp = ENEMY_BASE_HP + floor + 4; e.max_hp = ENEMY_BASE_HP + floor + 4;
@ -103,6 +105,7 @@ void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) {
e.resistance[DMG_PIERCE] = -25; e.resistance[DMG_PIERCE] = -25;
e.resistance[DMG_FIRE] = 0; e.resistance[DMG_FIRE] = 0;
e.resistance[DMG_POISON] = 0; e.resistance[DMG_POISON] = 0;
e.vision_range = 5;
break; break;
default: default:
e.max_hp = ENEMY_BASE_HP; e.max_hp = ENEMY_BASE_HP;
@ -116,6 +119,7 @@ void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) {
e.crit_chance = ENEMY_CRIT_CHANCE; e.crit_chance = ENEMY_CRIT_CHANCE;
e.crit_mult = ENEMY_CRIT_MULT; e.crit_mult = ENEMY_CRIT_MULT;
memset(e.resistance, 0, sizeof(e.resistance)); memset(e.resistance, 0, sizeof(e.resistance));
e.vision_range = ENEMY_VIEW_RANGE;
break; break;
} }
e.cooldown = e.speed; e.cooldown = e.speed;
@ -137,7 +141,7 @@ int is_enemy_at(const Enemy *enemies, int count, int x, int y) {
// Check if enemy can see player (within view range and line of sight) // Check if enemy can see player (within view range and line of sight)
static int can_see_player(Enemy *e, Player *p, Map *map) { static int can_see_player(Enemy *e, Player *p, Map *map) {
return can_see_entity(map, e->x, e->y, p->x, p->y, ENEMY_VIEW_RANGE); return can_see_entity(map, e->x, e->y, p->x, p->y, e->vision_range);
} }
// Check if position is occupied by player // Check if position is occupied by player
@ -197,7 +201,68 @@ static void enemy_patrol(Enemy *e, Map *map, Enemy *all_enemies, int enemy_count
} }
} }
// Perform a single action for an enemy (attack if visible, otherwise patrol) // Move enemy toward last known player position
static void enemy_move_to_last_known(Enemy *e, Map *map, Enemy *all_enemies, int enemy_count) {
int dx = 0, dy = 0;
if (e->last_known_x > e->x)
dx = 1;
else if (e->last_known_x < e->x)
dx = -1;
if (e->last_known_y > e->y)
dy = 1;
else if (e->last_known_y < e->y)
dy = -1;
int new_x = e->x + dx;
int new_y = e->y;
if (dx != 0 && is_floor(map, new_x, new_y) && !is_enemy_at(all_enemies, enemy_count, new_x, new_y)) {
e->x = new_x;
} else if (dy != 0) {
new_x = e->x;
new_y = e->y + dy;
if (is_floor(map, new_x, new_y) && !is_enemy_at(all_enemies, enemy_count, new_x, new_y)) {
e->x = new_x;
e->y = new_y;
}
}
if (e->x == e->last_known_x && e->y == e->last_known_y)
e->alert = 0;
}
// Check if position is within alert radius of another enemy
static int is_nearby_enemy(const Enemy *enemies, int count, int x, int y, int radius) {
for (int i = 0; i < count; i++) {
if (!enemies[i].alive)
continue;
int dx = enemies[i].x - x;
int dy = enemies[i].y - y;
if (dx * dx + dy * dy <= radius * radius)
return 1;
}
return 0;
}
// Propagate alert to nearby enemies
static void propagate_alert(Enemy *trigger_enemy, Enemy *all_enemies, int enemy_count) {
for (int i = 0; i < enemy_count; i++) {
Enemy *e = &all_enemies[i];
if (!e->alive || e == trigger_enemy)
continue;
if (e->alert)
continue;
if (is_nearby_enemy(all_enemies, enemy_count, e->x, e->y, 5)) {
e->alert = 1;
e->last_known_x = trigger_enemy->last_known_x;
e->last_known_y = trigger_enemy->last_known_y;
}
}
}
// Perform a single action for an enemy (attack if visible, otherwise patrol or search)
void enemy_act(Enemy *e, Player *p, Map *map, Enemy *all_enemies, int enemy_count) { void enemy_act(Enemy *e, Player *p, Map *map, Enemy *all_enemies, int enemy_count) {
if (!e->alive) if (!e->alive)
return; return;
@ -208,19 +273,34 @@ void enemy_act(Enemy *e, Player *p, Map *map, Enemy *all_enemies, int enemy_coun
int can_see = can_see_player(e, p, map); int can_see = can_see_player(e, p, map);
// If we can see the player, update alert state and last known position
if (can_see) {
e->alert = 1;
e->last_known_x = p->x;
e->last_known_y = p->y;
}
// Attack if adjacent to player // Attack if adjacent to player
if (can_see && can_see_entity(map, e->x, e->y, p->x, p->y, 1)) { if (can_see && can_see_entity(map, e->x, e->y, p->x, p->y, 1)) {
combat_enemy_attack(e, p); combat_enemy_attack(e, p);
propagate_alert(e, all_enemies, enemy_count);
return; return;
} }
// Move toward player if visible // Move toward player if visible
if (can_see) { if (can_see) {
enemy_move_toward_player(e, p, map, all_enemies, enemy_count); enemy_move_toward_player(e, p, map, all_enemies, enemy_count);
propagate_alert(e, all_enemies, enemy_count);
return; return;
} }
// Player not visible - patrol randomly // If alert but can't see player, move toward last known position
if (e->alert) {
enemy_move_to_last_known(e, map, all_enemies, enemy_count);
return;
}
// Not alert - patrol randomly
enemy_patrol(e, map, all_enemies, enemy_count); enemy_patrol(e, map, all_enemies, enemy_count);
} }

View file

@ -12,6 +12,8 @@ void map_init(Map *map) {
map->tiles[y][x] = TILE_WALL; map->tiles[y][x] = TILE_WALL;
} }
} }
memset(map->visible, 0, sizeof(map->visible));
memset(map->remembered, 0, sizeof(map->remembered));
map->room_count = 0; map->room_count = 0;
} }