#include "enemy.h" #include "combat.h" #include "common.h" #include "map.h" #include "rng.h" #include "settings.h" #include // Forward declaration 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) { *count = 0; // Number of enemies scales with floor int num_enemies = 3 + floor + rng_int(0, 2); if (num_enemies > MAX_ENEMIES) num_enemies = MAX_ENEMIES; // Enemy types available for this floor int max_type = 1; if (floor >= 2) max_type = 2; if (floor >= 4) max_type = 3; for (int i = 0; i < num_enemies; i++) { // Find random floor position int ex, ey; get_random_floor_tile(map, &ex, &ey, 50); // Don't spawn on player position if (ex == p->x && ey == p->y) { continue; } // Don't spawn on other enemies if (is_enemy_at(enemies, *count, ex, ey)) { continue; } // Create enemy Enemy e; memset(&e, 0, sizeof(Enemy)); e.x = ex; e.y = ey; e.alive = 1; e.type = rng_int(ENEMY_GOBLIN, max_type); e.effect_count = 0; // Stats based on type and floor // Attack scales with floor: +1 per 2 floors so deeper enemies hit harder int floor_atk = floor / 2; switch (e.type) { case ENEMY_GOBLIN: e.max_hp = ENEMY_BASE_HP + floor; e.hp = e.max_hp; e.attack = ENEMY_BASE_ATTACK + floor_atk; e.speed = 55 + rng_int(0, 10); e.dodge = 15; e.block = 0; e.dmg_class = DMG_POISON; e.status_chance = 15; e.crit_chance = 8; e.crit_mult = 150; e.resistance[DMG_SLASH] = 0; e.resistance[DMG_IMPACT] = 0; e.resistance[DMG_PIERCE] = 0; e.resistance[DMG_FIRE] = -25; e.resistance[DMG_POISON] = 50; break; case ENEMY_SKELETON: e.max_hp = ENEMY_BASE_HP + floor + 2; e.hp = e.max_hp; e.attack = ENEMY_BASE_ATTACK + 1 + floor_atk; e.speed = 70 + rng_int(0, 10); e.dodge = 5; e.block = 0; e.dmg_class = DMG_SLASH; e.status_chance = 10; e.crit_chance = 6; e.crit_mult = 150; e.resistance[DMG_SLASH] = -25; e.resistance[DMG_IMPACT] = -50; e.resistance[DMG_PIERCE] = 50; e.resistance[DMG_FIRE] = 25; e.resistance[DMG_POISON] = 75; break; case ENEMY_ORC: e.max_hp = ENEMY_BASE_HP + floor + 4; e.hp = e.max_hp; e.attack = ENEMY_BASE_ATTACK + 2 + floor_atk; e.speed = 85 + rng_int(0, 10); e.dodge = 0; e.block = 3; e.dmg_class = DMG_IMPACT; e.status_chance = 20; e.crit_chance = 5; e.crit_mult = 175; e.resistance[DMG_SLASH] = 0; e.resistance[DMG_IMPACT] = 25; e.resistance[DMG_PIERCE] = -25; e.resistance[DMG_FIRE] = 0; e.resistance[DMG_POISON] = 0; break; default: e.max_hp = ENEMY_BASE_HP; e.hp = e.max_hp; e.attack = ENEMY_BASE_ATTACK; e.speed = 60; e.dodge = 0; e.block = 0; e.dmg_class = DMG_IMPACT; e.status_chance = 0; e.crit_chance = ENEMY_CRIT_CHANCE; e.crit_mult = ENEMY_CRIT_MULT; memset(e.resistance, 0, sizeof(e.resistance)); break; } e.cooldown = e.speed; enemies[i] = e; (*count)++; } } // Check if position has an enemy int is_enemy_at(const Enemy *enemies, int count, int x, int y) { for (int i = 0; i < count; i++) { if (enemies[i].alive && enemies[i].x == x && enemies[i].y == y) { return 1; } } return 0; } // Check if enemy can see player (within view range and line of sight) 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); } // 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 static void enemy_move_toward_player(Enemy *e, Player *p, Map *map, Enemy *all_enemies, int enemy_count) { int dx = 0, dy = 0; if (p->x > e->x) dx = 1; else if (p->x < e->x) dx = -1; if (p->y > e->y) dy = 1; else if (p->y < e->y) dy = -1; // Try horizontal first, then vertical 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) && !is_player_at(p, 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) && !is_player_at(p, new_x, new_y)) { e->x = new_x; e->y = new_y; } } } // Move enemy in a random direction (patrol) static void enemy_patrol(Enemy *e, Map *map, Enemy *all_enemies, int enemy_count) { if (rng_int(0, 100) > ENEMY_PATROL_MOVE_CHANCE) return; int dx = rng_int(-1, 1); int dy = rng_int(-1, 1); if (dx == 0 && dy == 0) return; int new_x = e->x + dx; int 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; } } // Perform a single action for an enemy (attack if visible, otherwise patrol) void enemy_act(Enemy *e, Player *p, Map *map, Enemy *all_enemies, int enemy_count) { if (!e->alive) return; // Stunned enemies skip their action if (combat_has_effect(e->effects, e->effect_count, EFFECT_STUN)) return; int can_see = can_see_player(e, p, map); // Attack if adjacent to player if (can_see && can_see_entity(map, e->x, e->y, p->x, p->y, 1)) { combat_enemy_attack(e, p); return; } // Move toward player if visible if (can_see) { enemy_move_toward_player(e, p, map, all_enemies, enemy_count); return; } // Player not visible - patrol randomly enemy_patrol(e, map, all_enemies, enemy_count); } void enemy_update_all(Enemy enemies[], int count, Player *p, Map *map) { for (int i = 0; i < count; i++) { Enemy *e = &enemies[i]; if (!e->alive) continue; e->cooldown -= e->speed; if (e->cooldown <= 0) { enemy_act(e, p, map, enemies, count); e->cooldown = 100; } } }