movement: generalize; use vectors #16
9 changed files with 134 additions and 85 deletions
refactor(movement): generalize movement and use vectors
commit
d01a54161d
|
|
@ -25,6 +25,7 @@ pub fn build(b: *std.Build) void {
|
||||||
"src/main.c",
|
"src/main.c",
|
||||||
"src/map.c",
|
"src/map.c",
|
||||||
"src/player.c",
|
"src/player.c",
|
||||||
|
"src/movement.c",
|
||||||
"src/render.c",
|
"src/render.c",
|
||||||
"src/rng.c",
|
"src/rng.c",
|
||||||
"src/settings.c",
|
"src/settings.c",
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int x, y;
|
||||||
|
} Vec2;
|
||||||
|
|
||||||
// Tile types
|
// Tile types
|
||||||
typedef enum { TILE_WALL, TILE_FLOOR, TILE_STAIRS } TileType;
|
typedef enum { TILE_WALL, TILE_FLOOR, TILE_STAIRS } TileType;
|
||||||
|
|
||||||
|
|
@ -57,7 +61,7 @@ typedef struct {
|
||||||
|
|
||||||
// Player
|
// Player
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int x, y;
|
Vec2 position;
|
||||||
int hp, max_hp;
|
int hp, max_hp;
|
||||||
int attack;
|
int attack;
|
||||||
int defense;
|
int defense;
|
||||||
|
|
@ -83,7 +87,7 @@ typedef enum { ENEMY_GOBLIN, ENEMY_SKELETON, ENEMY_ORC } EnemyType;
|
||||||
|
|
||||||
// Enemy
|
// Enemy
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int x, y;
|
Vec2 position;
|
||||||
int hp;
|
int hp;
|
||||||
int max_hp;
|
int max_hp;
|
||||||
int attack;
|
int attack;
|
||||||
|
|
|
||||||
49
src/enemy.c
49
src/enemy.c
|
|
@ -2,6 +2,7 @@
|
||||||
#include "combat.h"
|
#include "combat.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "map.h"
|
#include "map.h"
|
||||||
|
#include "movement.h"
|
||||||
#include "rng.h"
|
#include "rng.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|
@ -29,7 +30,7 @@ void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) {
|
||||||
get_random_floor_tile(map, &ex, &ey, 50);
|
get_random_floor_tile(map, &ex, &ey, 50);
|
||||||
|
|
||||||
// Don't spawn on player position
|
// Don't spawn on player position
|
||||||
if (ex == p->x && ey == p->y) {
|
if (ex == p->position.x && ey == p->position.y) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,8 +42,8 @@ void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) {
|
||||||
// Create enemy
|
// Create enemy
|
||||||
Enemy e;
|
Enemy e;
|
||||||
memset(&e, 0, sizeof(Enemy));
|
memset(&e, 0, sizeof(Enemy));
|
||||||
e.x = ex;
|
e.position.x = ex;
|
||||||
e.y = ey;
|
e.position.y = ey;
|
||||||
e.alive = 1;
|
e.alive = 1;
|
||||||
e.type = rng_int(ENEMY_GOBLIN, max_type);
|
e.type = rng_int(ENEMY_GOBLIN, max_type);
|
||||||
e.effect_count = 0;
|
e.effect_count = 0;
|
||||||
|
|
@ -127,7 +128,7 @@ void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) {
|
||||||
// Check if position has an enemy
|
// Check if position has an enemy
|
||||||
int is_enemy_at(const Enemy *enemies, int count, int x, int y) {
|
int is_enemy_at(const Enemy *enemies, int count, int x, int y) {
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
if (enemies[i].alive && enemies[i].x == x && enemies[i].y == y) {
|
if (enemies[i].alive && enemies[i].position.x == x && enemies[i].position.y == y) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -136,45 +137,37 @@ int is_enemy_at(const Enemy *enemies, int count, int x, int y) {
|
||||||
|
|
||||||
// Check if enemy can see player (adjacent)
|
// Check if enemy can see player (adjacent)
|
||||||
static int can_see_player(Enemy *e, Player *p) {
|
static int can_see_player(Enemy *e, Player *p) {
|
||||||
int dx = p->x - e->x;
|
int dx = p->position.x - e->position.x;
|
||||||
int dy = p->y - e->y;
|
int dy = p->position.y - e->position.y;
|
||||||
return (dx >= -1 && dx <= 1 && dy >= -1 && dy <= 1);
|
return (dx >= -1 && dx <= 1 && dy >= -1 && dy <= 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// Move enemy toward player
|
||||||
static void enemy_move_toward_player(Enemy *e, Player *p, Map *map, Enemy *all_enemies, int enemy_count) {
|
static void enemy_move_toward_player(Enemy *e, Player *p, Map *map, Enemy *all_enemies, int enemy_count) {
|
||||||
int dx = 0, dy = 0;
|
int dx = 0, dy = 0;
|
||||||
|
|
||||||
if (p->x > e->x)
|
if (p->position.x > e->position.x)
|
||||||
dx = 1;
|
dx = 1;
|
||||||
else if (p->x < e->x)
|
else if (p->position.x < e->position.x)
|
||||||
dx = -1;
|
dx = -1;
|
||||||
|
|
||||||
if (p->y > e->y)
|
if (p->position.y > e->position.y)
|
||||||
dy = 1;
|
dy = 1;
|
||||||
else if (p->y < e->y)
|
else if (p->position.y < e->position.y)
|
||||||
dy = -1;
|
dy = -1;
|
||||||
|
|
||||||
// Try horizontal first, then vertical
|
Vec2 dir = {dx, 0};
|
||||||
int new_x = e->x + dx;
|
if (dx != 0) {
|
||||||
int new_y = e->y;
|
MoveResult r = try_move_entity(&e->position, dir, map, p, all_enemies, enemy_count, false);
|
||||||
|
if (r == MOVE_RESULT_MOVED)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (dx != 0 && is_floor(map, new_x, new_y) && !is_enemy_at(all_enemies, enemy_count, new_x, new_y) &&
|
dir.x = 0;
|
||||||
!is_player_at(p, new_x, new_y)) {
|
dir.y = dy;
|
||||||
e->x = new_x;
|
if (dy != 0) {
|
||||||
} else if (dy != 0) {
|
try_move_entity(&e->position, dir, map, p, all_enemies, enemy_count, false);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
65
src/main.c
65
src/main.c
|
|
@ -4,6 +4,7 @@
|
||||||
#include "enemy.h"
|
#include "enemy.h"
|
||||||
#include "items.h"
|
#include "items.h"
|
||||||
#include "map.h"
|
#include "map.h"
|
||||||
|
#include "movement.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "raylib.h"
|
#include "raylib.h"
|
||||||
#include "render.h"
|
#include "render.h"
|
||||||
|
|
@ -117,8 +118,8 @@ static void init_floor(GameState *gs, int floor_num) {
|
||||||
gs->floors_reached = 1;
|
gs->floors_reached = 1;
|
||||||
} else {
|
} else {
|
||||||
// Move player to new floor position
|
// Move player to new floor position
|
||||||
gs->player.x = start_x;
|
gs->player.position.x = start_x;
|
||||||
gs->player.y = start_y;
|
gs->player.position.y = start_y;
|
||||||
}
|
}
|
||||||
gs->player.floor = floor_num;
|
gs->player.floor = floor_num;
|
||||||
|
|
||||||
|
|
@ -137,7 +138,8 @@ static void tick_all_effects(GameState *gs) {
|
||||||
// Player effects
|
// Player effects
|
||||||
int player_effect_dmg = combat_tick_effects(&gs->player);
|
int player_effect_dmg = combat_tick_effects(&gs->player);
|
||||||
if (player_effect_dmg > 0) {
|
if (player_effect_dmg > 0) {
|
||||||
spawn_floating_text(gs, gs->player.x * TILE_SIZE + 8, gs->player.y * TILE_SIZE, player_effect_dmg, 0);
|
spawn_floating_text(gs, gs->player.position.x * TILE_SIZE + 8, gs->player.position.y * TILE_SIZE, player_effect_dmg,
|
||||||
|
0);
|
||||||
gs->screen_shake = SHAKE_EFFECT_DURATION;
|
gs->screen_shake = SHAKE_EFFECT_DURATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,7 +157,7 @@ static void tick_all_effects(GameState *gs) {
|
||||||
continue;
|
continue;
|
||||||
int enemy_effect_dmg = combat_tick_enemy_effects(e);
|
int enemy_effect_dmg = combat_tick_enemy_effects(e);
|
||||||
if (enemy_effect_dmg > 0) {
|
if (enemy_effect_dmg > 0) {
|
||||||
spawn_floating_text(gs, e->x * TILE_SIZE + 8, e->y * TILE_SIZE, enemy_effect_dmg, 0);
|
spawn_floating_text(gs, e->position.x * TILE_SIZE + 8, e->position.y * TILE_SIZE, enemy_effect_dmg, 0);
|
||||||
}
|
}
|
||||||
if (!e->alive) {
|
if (!e->alive) {
|
||||||
add_log(gs, "Enemy died from effects!");
|
add_log(gs, "Enemy died from effects!");
|
||||||
|
|
@ -173,7 +175,7 @@ static void post_action(GameState *gs, Enemy *attacked_enemy) {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Check if stepped on stairs
|
// Check if stepped on stairs
|
||||||
if (gs->map.tiles[gs->player.y][gs->player.x] == TILE_STAIRS) {
|
if (gs->map.tiles[gs->player.position.y][gs->player.position.x] == TILE_STAIRS) {
|
||||||
gs->awaiting_descend = 1;
|
gs->awaiting_descend = 1;
|
||||||
gs->last_message = "Descend to next floor? (Y/N)";
|
gs->last_message = "Descend to next floor? (Y/N)";
|
||||||
gs->message_timer = 120;
|
gs->message_timer = 120;
|
||||||
|
|
@ -182,8 +184,8 @@ static void post_action(GameState *gs, Enemy *attacked_enemy) {
|
||||||
|
|
||||||
// combat feedback - player attacked an enemy this turn
|
// combat feedback - player attacked an enemy this turn
|
||||||
if (attacked_enemy != NULL) {
|
if (attacked_enemy != NULL) {
|
||||||
int ex = attacked_enemy->x * TILE_SIZE + 8;
|
int ex = attacked_enemy->position.x * TILE_SIZE + 8;
|
||||||
int ey = attacked_enemy->y * TILE_SIZE;
|
int ey = attacked_enemy->position.y * TILE_SIZE;
|
||||||
|
|
||||||
if (combat_was_dodged()) {
|
if (combat_was_dodged()) {
|
||||||
spawn_floating_label(gs, ex, ey, LABEL_DODGE, EFFECT_NONE);
|
spawn_floating_label(gs, ex, ey, LABEL_DODGE, EFFECT_NONE);
|
||||||
|
|
@ -224,8 +226,8 @@ static void post_action(GameState *gs, Enemy *attacked_enemy) {
|
||||||
gs->screen_shake = SHAKE_PLAYER_DAMAGE_DURATION;
|
gs->screen_shake = SHAKE_PLAYER_DAMAGE_DURATION;
|
||||||
gs->damage_taken += combat_get_last_damage();
|
gs->damage_taken += combat_get_last_damage();
|
||||||
gs->times_hit++;
|
gs->times_hit++;
|
||||||
spawn_floating_text(gs, gs->player.x * TILE_SIZE + 8, gs->player.y * TILE_SIZE, combat_get_last_damage(),
|
spawn_floating_text(gs, gs->player.position.x * TILE_SIZE + 8, gs->player.position.y * TILE_SIZE,
|
||||||
combat_was_critical());
|
combat_get_last_damage(), combat_was_critical());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set message and check game over
|
// Set message and check game over
|
||||||
|
|
@ -388,7 +390,7 @@ static int handle_movement_input(GameState *gs) {
|
||||||
|
|
||||||
// Check for manual item pickup (G key)
|
// Check for manual item pickup (G key)
|
||||||
if (IsKeyPressed(KEY_G)) {
|
if (IsKeyPressed(KEY_G)) {
|
||||||
Item *item = get_item_at_floor(gs->items, gs->item_count, gs->player.x, gs->player.y);
|
Item *item = get_item_at_floor(gs->items, gs->item_count, gs->player.position.x, gs->player.position.y);
|
||||||
if (item != NULL) {
|
if (item != NULL) {
|
||||||
if (player_pickup(&gs->player, item)) {
|
if (player_pickup(&gs->player, item)) {
|
||||||
gs->items_collected++;
|
gs->items_collected++;
|
||||||
|
|
@ -417,38 +419,43 @@ static int handle_movement_input(GameState *gs) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Movement: use IsKeyDown for held-key repeat
|
|
||||||
int dx = 0, dy = 0;
|
|
||||||
if (IsKeyDown(KEY_W) || IsKeyDown(KEY_UP))
|
|
||||||
dy = -1;
|
|
||||||
else if (IsKeyDown(KEY_S) || IsKeyDown(KEY_DOWN))
|
|
||||||
dy = 1;
|
|
||||||
else if (IsKeyDown(KEY_A) || IsKeyDown(KEY_LEFT))
|
|
||||||
dx = -1;
|
|
||||||
else if (IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT))
|
|
||||||
dx = 1;
|
|
||||||
|
|
||||||
if (dx == 0 && dy == 0)
|
Vec2 direction = {0, 0};
|
||||||
|
if (IsKeyDown(KEY_W) || IsKeyDown(KEY_UP))
|
||||||
|
direction.y = -1;
|
||||||
|
else if (IsKeyDown(KEY_S) || IsKeyDown(KEY_DOWN))
|
||||||
|
direction.y = 1;
|
||||||
|
else if (IsKeyDown(KEY_A) || IsKeyDown(KEY_LEFT))
|
||||||
|
direction.x = -1;
|
||||||
|
else if (IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT))
|
||||||
|
direction.x = 1;
|
||||||
|
|
||||||
|
if (direction.x == 0 && direction.y == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Reset combat event before player acts
|
// Reset combat event before player acts
|
||||||
combat_reset_event();
|
combat_reset_event();
|
||||||
|
|
||||||
int new_x = gs->player.x + dx;
|
|
||||||
int new_y = gs->player.y + dy;
|
int new_x = gs->player.position.x + direction.x;
|
||||||
|
int new_y = gs->player.position.y + direction.y;
|
||||||
|
|
||||||
|
Enemy *target = NULL;
|
||||||
int action = 0;
|
int action = 0;
|
||||||
|
|
||||||
// Attack enemy at target tile, or move into it
|
MoveResult result =
|
||||||
Enemy *target = player_find_enemy_at(gs->enemies, gs->enemy_count, new_x, new_y);
|
try_move_entity(&gs->player.position, direction, &gs->map, &gs->player, gs->enemies, gs->enemy_count, true);
|
||||||
if (target != NULL) {
|
if (result == MOVE_RESULT_MOVED) {
|
||||||
|
player_on_move(&gs->player);
|
||||||
|
action = 1;
|
||||||
|
} else if (result == MOVE_RESULT_BLOCKED_ENEMY) {
|
||||||
|
SquirrelModeler marked this conversation as resolved
Outdated
|
|||||||
|
target = player_find_enemy_at(gs->enemies, gs->enemy_count, new_x, new_y);
|
||||||
player_attack(&gs->player, target);
|
player_attack(&gs->player, target);
|
||||||
action = 1;
|
action = 1;
|
||||||
} else {
|
|
||||||
action = player_move(&gs->player, dx, dy, &gs->map);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action)
|
if (action)
|
||||||
post_action(gs, target); // target is NULL on move, enemy ptr on attack
|
post_action(gs, target);
|
||||||
|
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
src/movement.c
Normal file
29
src/movement.c
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#include "movement.h"
|
||||||
|
#include "enemy.h"
|
||||||
|
#include "map.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// Check if position is occupied by player
|
||||||
|
static int is_player_at(Player *p, int x, int y) {
|
||||||
|
return (p->position.x == x && p->position.y == y);
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveResult try_move_entity(Vec2 *p, Vec2 direction, Map *map, Player *player, Enemy *enemies, int enemy_count,
|
||||||
|
bool moving_is_player) {
|
||||||
|
int new_x = p->x + direction.x;
|
||||||
|
int new_y = p->y + direction.y;
|
||||||
|
|
||||||
|
if (!is_floor(map, new_x, new_y))
|
||||||
|
return MOVE_RESULT_BLOCKED_WALL;
|
||||||
|
|
||||||
|
if (is_enemy_at(enemies, enemy_count, new_x, new_y))
|
||||||
|
return MOVE_RESULT_BLOCKED_ENEMY;
|
||||||
|
if (!moving_is_player) {
|
||||||
|
if (is_player_at(player, new_x, new_y))
|
||||||
|
return MOVE_RESULT_BLOCKED_PLAYER;
|
||||||
|
}
|
||||||
|
|
||||||
|
p->x = new_x;
|
||||||
|
p->y = new_y;
|
||||||
|
return MOVE_RESULT_MOVED;
|
||||||
|
}
|
||||||
17
src/movement.h
Normal file
17
src/movement.h
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#ifndef MOVEMENT_H
|
||||||
|
#define MOVEMENT_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
MOVE_RESULT_MOVED,
|
||||||
|
MOVE_RESULT_BLOCKED_WALL,
|
||||||
|
MOVE_RESULT_BLOCKED_PLAYER,
|
||||||
|
MOVE_RESULT_BLOCKED_ENEMY
|
||||||
|
} MoveResult;
|
||||||
|
|
||||||
|
// Attempts to move entity in a given direction. Returns outcome of action.
|
||||||
|
MoveResult try_move_entity(Vec2 *p, Vec2 direction, Map *map, Player *player, Enemy *enemies, int enemy_count,
|
||||||
|
bool moving_is_player);
|
||||||
|
|
||||||
|
#endif // MOVEMENT_H
|
||||||
41
src/player.c
41
src/player.c
|
|
@ -2,15 +2,13 @@
|
||||||
#include "combat.h"
|
#include "combat.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "items.h"
|
#include "items.h"
|
||||||
#include "map.h"
|
#include "movement.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "utils.h"
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
void player_init(Player *p, int x, int y) {
|
void player_init(Player *p, int x, int y) {
|
||||||
p->x = x;
|
p->position.x = x;
|
||||||
p->y = y;
|
p->position.y = y;
|
||||||
p->hp = PLAYER_BASE_HP;
|
p->hp = PLAYER_BASE_HP;
|
||||||
p->max_hp = PLAYER_BASE_HP;
|
p->max_hp = PLAYER_BASE_HP;
|
||||||
p->attack = PLAYER_BASE_ATTACK;
|
p->attack = PLAYER_BASE_ATTACK;
|
||||||
|
|
@ -43,29 +41,18 @@ Enemy *player_find_enemy_at(Enemy *enemies, int count, int x, int y) {
|
||||||
if (count > MAX_ENEMIES)
|
if (count > MAX_ENEMIES)
|
||||||
count = MAX_ENEMIES;
|
count = MAX_ENEMIES;
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
if (enemies[i].alive && enemies[i].x == x && enemies[i].y == y)
|
if (enemies[i].alive && enemies[i].position.x == x && enemies[i].position.y == y)
|
||||||
return &enemies[i];
|
return &enemies[i];
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int player_move(Player *p, int dx, int dy, Map *map) {
|
int player_move(Player *p, Vec2 direction, Map *map, Enemy *enemies, int enemy_count) {
|
||||||
int new_x = p->x + dx;
|
MoveResult result = try_move_entity(&p->position, direction, map, p, enemies, enemy_count, true);
|
||||||
int new_y = p->y + dy;
|
if (result != MOVE_RESULT_MOVED)
|
||||||
|
|
||||||
// Check bounds
|
|
||||||
if (!in_bounds(new_x, new_y, MAP_WIDTH, MAP_HEIGHT))
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Check if walkable
|
|
||||||
if (!is_floor(map, new_x, new_y))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Move player
|
|
||||||
p->x = new_x;
|
|
||||||
p->y = new_y;
|
|
||||||
p->step_count += 1;
|
p->step_count += 1;
|
||||||
// Regen suppressed while poisoned, bleeding, or burning
|
|
||||||
if (p->step_count % REGEN_STEP_INTERVAL == 0 && p->hp < p->max_hp &&
|
if (p->step_count % REGEN_STEP_INTERVAL == 0 && p->hp < p->max_hp &&
|
||||||
!combat_has_effect(p->effects, p->effect_count, EFFECT_POISON) &&
|
!combat_has_effect(p->effects, p->effect_count, EFFECT_POISON) &&
|
||||||
!combat_has_effect(p->effects, p->effect_count, EFFECT_BLEED) &&
|
!combat_has_effect(p->effects, p->effect_count, EFFECT_BLEED) &&
|
||||||
|
|
@ -75,6 +62,16 @@ int player_move(Player *p, int dx, int dy, Map *map) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void player_on_move(Player *p) {
|
||||||
|
p->step_count += 1;
|
||||||
|
if (p->step_count % REGEN_STEP_INTERVAL == 0 && p->hp < p->max_hp &&
|
||||||
|
!combat_has_effect(p->effects, p->effect_count, EFFECT_POISON) &&
|
||||||
|
!combat_has_effect(p->effects, p->effect_count, EFFECT_BLEED) &&
|
||||||
|
!combat_has_effect(p->effects, p->effect_count, EFFECT_BURN)) {
|
||||||
|
p->hp += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void player_attack(Player *p, Enemy *e) {
|
void player_attack(Player *p, Enemy *e) {
|
||||||
// Use combat system
|
// Use combat system
|
||||||
combat_player_attack(p, e);
|
combat_player_attack(p, e);
|
||||||
|
|
@ -228,8 +225,8 @@ int player_drop_item(Player *p, int inv_index, Item *items, int item_count) {
|
||||||
if (items[i].picked_up) {
|
if (items[i].picked_up) {
|
||||||
// Place dropped item at this position
|
// Place dropped item at this position
|
||||||
items[i] = *item;
|
items[i] = *item;
|
||||||
items[i].x = p->x;
|
items[i].x = p->position.x;
|
||||||
items[i].y = p->y;
|
items[i].y = p->position.y;
|
||||||
items[i].picked_up = 0;
|
items[i].picked_up = 0;
|
||||||
// Remove from inventory
|
// Remove from inventory
|
||||||
player_remove_inventory_item(p, inv_index);
|
player_remove_inventory_item(p, inv_index);
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
// Initialize player at position
|
// Initialize player at position
|
||||||
void player_init(Player *p, int x, int y);
|
void player_init(Player *p, int x, int y);
|
||||||
|
|
||||||
// Move player to (x+dx, y+dy); returns 1 if moved, 0 if blocked
|
// Apply status effect, healing ect
|
||||||
|
SquirrelModeler marked this conversation as resolved
Outdated
NotAShelf
commented
Typo, should be "healing, etc." Typo, should be "healing, etc."
|
|||||||
int player_move(Player *p, int dx, int dy, Map *map);
|
void player_on_move(Player *p);
|
||||||
|
|
||||||
// Find a living enemy at tile (x, y); returns NULL if none
|
// Find a living enemy at tile (x, y); returns NULL if none
|
||||||
Enemy *player_find_enemy_at(Enemy *enemies, int count, int x, int y);
|
Enemy *player_find_enemy_at(Enemy *enemies, int count, int x, int y);
|
||||||
|
|
|
||||||
11
src/render.c
11
src/render.c
|
|
@ -30,7 +30,8 @@ void render_map(const Map *map) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void render_player(const Player *p) {
|
void render_player(const Player *p) {
|
||||||
Rectangle rect = {(float)(p->x * TILE_SIZE), (float)(p->y * TILE_SIZE), (float)TILE_SIZE, (float)TILE_SIZE};
|
Rectangle rect = {(float)(p->position.x * TILE_SIZE), (float)(p->position.y * TILE_SIZE), (float)TILE_SIZE,
|
||||||
|
(float)TILE_SIZE};
|
||||||
DrawRectangleRec(rect, BLUE);
|
DrawRectangleRec(rect, BLUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,8 +40,8 @@ void render_enemies(const Enemy *enemies, int count) {
|
||||||
if (!enemies[i].alive)
|
if (!enemies[i].alive)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Rectangle rect = {(float)(enemies[i].x * TILE_SIZE), (float)(enemies[i].y * TILE_SIZE), (float)TILE_SIZE,
|
Rectangle rect = {(float)(enemies[i].position.x * TILE_SIZE), (float)(enemies[i].position.y * TILE_SIZE),
|
||||||
(float)TILE_SIZE};
|
(float)TILE_SIZE, (float)TILE_SIZE};
|
||||||
|
|
||||||
// Different colors based on enemy type
|
// Different colors based on enemy type
|
||||||
Color enemy_color;
|
Color enemy_color;
|
||||||
|
|
@ -72,8 +73,8 @@ void render_enemies(const Enemy *enemies, int count) {
|
||||||
bar_color = (Color){200, 180, 40, 255}; // yellow
|
bar_color = (Color){200, 180, 40, 255}; // yellow
|
||||||
else
|
else
|
||||||
bar_color = (Color){200, 60, 60, 255}; // red
|
bar_color = (Color){200, 60, 60, 255}; // red
|
||||||
Rectangle hp_bar = {(float)(enemies[i].x * TILE_SIZE), (float)(enemies[i].y * TILE_SIZE - 4), (float)hp_pixels,
|
Rectangle hp_bar = {(float)(enemies[i].position.x * TILE_SIZE), (float)(enemies[i].position.y * TILE_SIZE - 4),
|
||||||
3};
|
(float)hp_pixels, 3};
|
||||||
DrawRectangleRec(hp_bar, bar_color);
|
DrawRectangleRec(hp_bar, bar_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue
You should probably guard against potential null target here before attacking.
Because if
try_move_entityreturnsMOVE_RESULT_BLOCKED_ENEMYbutplayer_find_enemy_atfails to find the enemy (be it due to differing lookup logic, or if the enemy was killed by effects earlier in the turn), target would beNULL, and thus lead to an ub inplayer_attack. Something like:If we move the action = 1 into the null guarded if statement, then enemy AI breaks. We can do:Which fixes that bug.Ignore this. I added an incorrect guard.