combat: rewrite in Zig; add basic damage types and weapon archetypes

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ic8055a1cf6bdad1aca13673ea171b4b46a6a6964
This commit is contained in:
raf 2026-04-05 20:11:06 +03:00
commit 22ab6fc6eb
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
15 changed files with 802 additions and 158 deletions

View file

@ -25,14 +25,26 @@ static void add_log(GameState *gs, const char *msg) {
// spawn floating damage text
static void spawn_floating_text(GameState *gs, int x, int y, int value, int is_critical) {
// Reuse an expired slot if all slots are taken
int slot = -1;
if (gs->floating_count < 8) {
gs->floating_texts[gs->floating_count].x = x;
gs->floating_texts[gs->floating_count].y = y;
gs->floating_texts[gs->floating_count].value = value;
gs->floating_texts[gs->floating_count].lifetime = 60;
gs->floating_texts[gs->floating_count].is_critical = is_critical;
slot = gs->floating_count;
gs->floating_count++;
} else {
for (int i = 0; i < 8; i++) {
if (gs->floating_texts[i].lifetime <= 0) {
slot = i;
break;
}
}
}
if (slot < 0)
return;
gs->floating_texts[slot].x = x;
gs->floating_texts[slot].y = y;
gs->floating_texts[slot].value = value;
gs->floating_texts[slot].lifetime = 60;
gs->floating_texts[slot].is_critical = is_critical;
}
// update floating texts and screen shake
@ -87,6 +99,37 @@ static void init_floor(GameState *gs, int floor_num) {
gs->turn_count = 0;
}
// Tick all status effects at the start of a turn
static void tick_all_effects(GameState *gs) {
// Player effects
int player_effect_dmg = combat_tick_effects(&gs->player);
if (player_effect_dmg > 0) {
spawn_floating_text(gs, gs->player.x * TILE_SIZE + 8, gs->player.y * TILE_SIZE, player_effect_dmg, 0);
gs->screen_shake = 4;
}
// Check if player died from effects
if (gs->player.hp <= 0) {
gs->player.hp = 0;
gs->game_over = 1;
return;
}
// Enemy effects
for (int i = 0; i < gs->enemy_count; i++) {
Enemy *e = &gs->enemies[i];
if (!e->alive)
continue;
int enemy_effect_dmg = combat_tick_enemy_effects(e);
if (enemy_effect_dmg > 0) {
spawn_floating_text(gs, e->x * TILE_SIZE + 8, e->y * TILE_SIZE, enemy_effect_dmg, 0);
}
if (!e->alive) {
add_log(gs, "Enemy died from effects!");
}
}
}
// Handle player input - returns: 0=continue, 1=acted, -1=quit
static int handle_input(GameState *gs) {
int dx = 0, dy = 0;
@ -103,6 +146,25 @@ static int handle_input(GameState *gs) {
return 0;
}
// If player is stunned, wait for any key then consume the turn
if (combat_has_effect(gs->player.effects, gs->player.effect_count, EFFECT_STUN)) {
if (!(IsKeyDown(KEY_W) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_S) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_A) ||
IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT)))
return 0;
gs->turn_count++;
tick_all_effects(gs);
if (gs->game_over)
return 1;
enemy_update_all(gs->enemies, gs->enemy_count, &gs->player, &gs->map);
if (gs->player.hp <= 0) {
gs->game_over = 1;
}
gs->last_message = "You are stunned!";
gs->message_timer = 60;
add_log(gs, "Stunned! Lost a turn.");
return 1;
}
if (gs->show_inventory) {
if (IsKeyPressed(KEY_I) || IsKeyPressed(KEY_ESCAPE)) {
gs->show_inventory = 0;
@ -298,6 +360,11 @@ static int handle_input(GameState *gs) {
// Increment turn counter
gs->turn_count++;
// Tick status effects at the start of this turn
tick_all_effects(gs);
if (gs->game_over)
return 1;
// Check if stepped on stairs
if (gs->map.tiles[gs->player.y][gs->player.x] == TILE_STAIRS) {
gs->awaiting_descend = 1;
@ -358,6 +425,8 @@ static void game_loop(void) {
while (!WindowShouldClose()) {
// Handle input
if (!gs.game_over) {
// Tick status effects at the start of each frame where input is checked
// (effects tick once per player action via the acted flag below)
int quit = handle_input(&gs);
if (quit == -1)
break;