diff --git a/src/engine/entity.c b/src/engine/entity.c index 475a030..2d866bd 100644 --- a/src/engine/entity.c +++ b/src/engine/entity.c @@ -126,3 +126,17 @@ void entity_register(EntityManager *em, EntityType type, em->render_fn[type] = render; em->destroy_fn[type] = destroy; } + +bool entity_is_enemy(const Entity *e) { + switch (e->type) { + case ENT_ENEMY_GRUNT: + case ENT_ENEMY_FLYER: + case ENT_TURRET: + case ENT_ENEMY_CHARGER: + case ENT_SPAWNER: + case ENT_LASER_TURRET: + return true; + default: + return false; + } +} diff --git a/src/engine/entity.h b/src/engine/entity.h index b0bb999..0d3c528 100644 --- a/src/engine/entity.h +++ b/src/engine/entity.h @@ -80,4 +80,8 @@ void entity_register(EntityManager *em, EntityType type, EntityUpdateFn update, EntityRenderFn render, EntityDestroyFn destroy); +/* Returns true if the entity is a hostile enemy type. + * Single source of truth — add new enemy types here. */ +bool entity_is_enemy(const Entity *e); + #endif /* JNR_ENTITY_H */ diff --git a/src/game/drone.c b/src/game/drone.c index b35cac8..7519882 100644 --- a/src/game/drone.c +++ b/src/game/drone.c @@ -34,9 +34,7 @@ static Entity *find_nearest_enemy(Vec2 from, float range) { for (int i = 0; i < s_em->count; i++) { Entity *e = &s_em->entities[i]; if (!e->active || e->health <= 0) continue; - if (e->type != ENT_ENEMY_GRUNT && e->type != ENT_ENEMY_FLYER && - e->type != ENT_TURRET && e->type != ENT_ENEMY_CHARGER && - e->type != ENT_SPAWNER && e->type != ENT_LASER_TURRET) continue; + if (!entity_is_enemy(e)) continue; Vec2 epos = vec2(e->body.pos.x + e->body.size.x * 0.5f, e->body.pos.y + e->body.size.y * 0.5f); float dx = epos.x - from.x; diff --git a/src/game/laser_turret.c b/src/game/laser_turret.c index d96ae7e..2509b6a 100644 --- a/src/game/laser_turret.c +++ b/src/game/laser_turret.c @@ -38,12 +38,14 @@ static Entity *find_player(EntityManager *em) { return NULL; } -/* Raycast from (ox, oy) along (dx, dy) in tile steps. +/* Raycast from (ox, oy) along (dx, dy) in pixel steps. * Returns the world-space endpoint where the beam hits - * a solid tile or the map boundary. Step size is one - * pixel for accuracy. Max range prevents runaway. */ + * a solid tile or the map boundary. Ignores the turret's + * own tile (skip_tx, skip_ty) so wall-mounted turrets can + * shoot outward. Max range prevents runaway. */ static void beam_raycast(const Tilemap *map, float ox, float oy, float dx, float dy, + int skip_tx, int skip_ty, float *out_x, float *out_y) { float max_range = 800.0f; /* pixels */ float step = 1.0f; @@ -58,6 +60,7 @@ static void beam_raycast(const Tilemap *map, int tx = (int)(x / TILE_SIZE); int ty = (int)(y / TILE_SIZE); if (tx < 0 || ty < 0 || tx >= map->width || ty >= map->height) break; + if (tx == skip_tx && ty == skip_ty) continue; if (tilemap_is_solid(map, tx, ty)) break; } @@ -111,10 +114,13 @@ static void laser_turret_update(Entity *self, float dt, const Tilemap *map) { if (dx < 0) self->flags |= ENTITY_FACING_LEFT; else self->flags &= ~ENTITY_FACING_LEFT; - /* Compute beam endpoint via raycast */ + /* Compute beam endpoint via raycast (skip turret's own tile) */ float dir_x = cosf(ld->aim_angle); float dir_y = sinf(ld->aim_angle); - beam_raycast(map, cx, cy, dir_x, dir_y, &ld->beam_end_x, &ld->beam_end_y); + int self_tx = (int)(cx / TILE_SIZE); + int self_ty = (int)(cy / TILE_SIZE); + beam_raycast(map, cx, cy, dir_x, dir_y, self_tx, self_ty, + &ld->beam_end_x, &ld->beam_end_y); /* Damage cooldown */ if (ld->damage_cd > 0) ld->damage_cd -= dt; diff --git a/src/game/level.c b/src/game/level.c index 3d11627..e996b97 100644 --- a/src/game/level.c +++ b/src/game/level.c @@ -233,12 +233,6 @@ static void damage_player(Entity *player, int damage, Entity *source) { } } -static bool is_enemy(const Entity *e) { - return e->type == ENT_ENEMY_GRUNT || e->type == ENT_ENEMY_FLYER || - e->type == ENT_TURRET || e->type == ENT_ENEMY_CHARGER || - e->type == ENT_SPAWNER || e->type == ENT_LASER_TURRET; -} - static void handle_collisions(EntityManager *em) { /* Find the player */ Entity *player = NULL; @@ -267,7 +261,7 @@ static void handle_collisions(EntityManager *em) { bool hit = false; /* Player bullet hits enemies */ - if (from_player && is_enemy(b)) { + if (from_player && entity_is_enemy(b)) { if (physics_overlap(&a->body, &b->body)) { damage_entity(b, a->damage); hit = true; @@ -293,7 +287,7 @@ static void handle_collisions(EntityManager *em) { /* ── Enemy contact damage to player ──── */ if (player && !(player->flags & ENTITY_INVINCIBLE) && - is_enemy(a) && !(a->flags & ENTITY_DEAD)) { + entity_is_enemy(a) && !(a->flags & ENTITY_DEAD)) { if (physics_overlap(&a->body, &player->body)) { /* Check if player is stomping (falling onto enemy from above) */ bool stomping = (player->body.vel.y > 0) && diff --git a/src/game/projectile.c b/src/game/projectile.c index 63299cc..76d8ac5 100644 --- a/src/game/projectile.c +++ b/src/game/projectile.c @@ -251,9 +251,7 @@ static void projectile_update(Entity *self, float dt, const Tilemap *map) { /* Player bullets target enemies, enemy bullets target player */ if (is_player_proj) { - if (e->type != ENT_ENEMY_GRUNT && e->type != ENT_ENEMY_FLYER && - e->type != ENT_TURRET && e->type != ENT_ENEMY_CHARGER && - e->type != ENT_SPAWNER && e->type != ENT_LASER_TURRET) continue; + if (!entity_is_enemy(e)) continue; } else { if (e->type != ENT_PLAYER) continue; }