Fix laser beam blocked by own tile, consolidate enemy-type checks
Beam raycast now skips the tile the turret is mounted on, so wall-embedded laser turrets can shoot outward. Extract entity_is_enemy() into engine/entity.c as single source of truth for enemy-type checks. Replaces triplicated type lists in level.c, drone.c, and projectile.c that caused the collision bug when new enemy types were added.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) &&
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user