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->render_fn[type] = render;
|
||||||
em->destroy_fn[type] = destroy;
|
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,
|
EntityUpdateFn update, EntityRenderFn render,
|
||||||
EntityDestroyFn destroy);
|
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 */
|
#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++) {
|
for (int i = 0; i < s_em->count; i++) {
|
||||||
Entity *e = &s_em->entities[i];
|
Entity *e = &s_em->entities[i];
|
||||||
if (!e->active || e->health <= 0) continue;
|
if (!e->active || e->health <= 0) continue;
|
||||||
if (e->type != ENT_ENEMY_GRUNT && e->type != ENT_ENEMY_FLYER &&
|
if (!entity_is_enemy(e)) continue;
|
||||||
e->type != ENT_TURRET && e->type != ENT_ENEMY_CHARGER &&
|
|
||||||
e->type != ENT_SPAWNER && e->type != ENT_LASER_TURRET) continue;
|
|
||||||
Vec2 epos = vec2(e->body.pos.x + e->body.size.x * 0.5f,
|
Vec2 epos = vec2(e->body.pos.x + e->body.size.x * 0.5f,
|
||||||
e->body.pos.y + e->body.size.y * 0.5f);
|
e->body.pos.y + e->body.size.y * 0.5f);
|
||||||
float dx = epos.x - from.x;
|
float dx = epos.x - from.x;
|
||||||
|
|||||||
@@ -38,12 +38,14 @@ static Entity *find_player(EntityManager *em) {
|
|||||||
return NULL;
|
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
|
* Returns the world-space endpoint where the beam hits
|
||||||
* a solid tile or the map boundary. Step size is one
|
* a solid tile or the map boundary. Ignores the turret's
|
||||||
* pixel for accuracy. Max range prevents runaway. */
|
* own tile (skip_tx, skip_ty) so wall-mounted turrets can
|
||||||
|
* shoot outward. Max range prevents runaway. */
|
||||||
static void beam_raycast(const Tilemap *map,
|
static void beam_raycast(const Tilemap *map,
|
||||||
float ox, float oy, float dx, float dy,
|
float ox, float oy, float dx, float dy,
|
||||||
|
int skip_tx, int skip_ty,
|
||||||
float *out_x, float *out_y) {
|
float *out_x, float *out_y) {
|
||||||
float max_range = 800.0f; /* pixels */
|
float max_range = 800.0f; /* pixels */
|
||||||
float step = 1.0f;
|
float step = 1.0f;
|
||||||
@@ -58,6 +60,7 @@ static void beam_raycast(const Tilemap *map,
|
|||||||
int tx = (int)(x / TILE_SIZE);
|
int tx = (int)(x / TILE_SIZE);
|
||||||
int ty = (int)(y / TILE_SIZE);
|
int ty = (int)(y / TILE_SIZE);
|
||||||
if (tx < 0 || ty < 0 || tx >= map->width || ty >= map->height) break;
|
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;
|
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;
|
if (dx < 0) self->flags |= ENTITY_FACING_LEFT;
|
||||||
else 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_x = cosf(ld->aim_angle);
|
||||||
float dir_y = sinf(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 */
|
/* Damage cooldown */
|
||||||
if (ld->damage_cd > 0) ld->damage_cd -= dt;
|
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) {
|
static void handle_collisions(EntityManager *em) {
|
||||||
/* Find the player */
|
/* Find the player */
|
||||||
Entity *player = NULL;
|
Entity *player = NULL;
|
||||||
@@ -267,7 +261,7 @@ static void handle_collisions(EntityManager *em) {
|
|||||||
bool hit = false;
|
bool hit = false;
|
||||||
|
|
||||||
/* Player bullet hits enemies */
|
/* Player bullet hits enemies */
|
||||||
if (from_player && is_enemy(b)) {
|
if (from_player && entity_is_enemy(b)) {
|
||||||
if (physics_overlap(&a->body, &b->body)) {
|
if (physics_overlap(&a->body, &b->body)) {
|
||||||
damage_entity(b, a->damage);
|
damage_entity(b, a->damage);
|
||||||
hit = true;
|
hit = true;
|
||||||
@@ -293,7 +287,7 @@ static void handle_collisions(EntityManager *em) {
|
|||||||
|
|
||||||
/* ── Enemy contact damage to player ──── */
|
/* ── Enemy contact damage to player ──── */
|
||||||
if (player && !(player->flags & ENTITY_INVINCIBLE) &&
|
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)) {
|
if (physics_overlap(&a->body, &player->body)) {
|
||||||
/* Check if player is stomping (falling onto enemy from above) */
|
/* Check if player is stomping (falling onto enemy from above) */
|
||||||
bool stomping = (player->body.vel.y > 0) &&
|
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 */
|
/* Player bullets target enemies, enemy bullets target player */
|
||||||
if (is_player_proj) {
|
if (is_player_proj) {
|
||||||
if (e->type != ENT_ENEMY_GRUNT && e->type != ENT_ENEMY_FLYER &&
|
if (!entity_is_enemy(e)) continue;
|
||||||
e->type != ENT_TURRET && e->type != ENT_ENEMY_CHARGER &&
|
|
||||||
e->type != ENT_SPAWNER && e->type != ENT_LASER_TURRET) continue;
|
|
||||||
} else {
|
} else {
|
||||||
if (e->type != ENT_PLAYER) continue;
|
if (e->type != ENT_PLAYER) continue;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user