commit 4d71e647f0f4daa1d931fe03eb6a0d9d32345e56
parent 45aaceae9fef8bb703738666cd40c320363d17d0
Author: beep <beep@wimdupont.com>
Date: Wed, 29 Jul 2026 14:49:33 +0000
Polish pickups, movement, and town tiles
Restrict pickups to the player's tile, calm distance-based walk animation with pixel-snapped rendering, and construct Stonehollow buildings from proper wall, floor, and doorway tiles.
Diffstat:
8 files changed, 89 insertions(+), 41 deletions(-)
diff --git a/config.def.h b/config.def.h
@@ -1,7 +1,7 @@
#ifndef CONFIG_H
#define CONFIG_H
-#define PH_CONFIG_VERSION 8
+#define PH_CONFIG_VERSION 9
#define PH_VIEW_W 320
#define PH_VIEW_H 240
@@ -11,10 +11,10 @@
#define PH_FRAME_DELAY_MS 16
#define PH_ENTITY_BLOCK_RADIUS 10
-#define PH_PICKUP_RADIUS 18.0f
#define PH_INTERACT_RADIUS 20.0f
#define PH_NOTICE_SECONDS 1.25f
#define PH_WORLD_STEP_MILLISECONDS 250
+#define PH_WORLD_ANIMATION_PIXELS_PER_FRAME 8
#define PH_WORLD_ACTION_MILLISECONDS 500
#define PH_MOB_MEMORY_TURNS 4
#define PH_MOB_WANDER_PERCENT 60
diff --git a/data/maps/stonehollow.txt b/data/maps/stonehollow.txt
@@ -2,19 +2,19 @@ name: Stonehollow
################################
#..............................#
-#..BBBBBBBB....BBBBBBBB........#
-#..B======B....B======B........#
-#..B======B....B======B........#
-#..B======B....B======B........#
-#..BBB=BBBB....BBBB=BBB........#
+#..[^^^^^^]....[^^^^^^]........#
+#..{======}....{======}........#
+#..{======}....{======}........#
+#..{======}....{======}........#
+#..(__+___)....(___+__)........#
#.....,............,...........#
#.....,,,,,,,,,,,,,,...........#
#...........,..................#
-#..BBBBBBBB.,..BBBBBBBB........#
-#..B======B.,..B======B........#
-#..B======B.,..B======B........#
-#..B======B.,..B======B........#
-#..BBB=BBBB.,..BBB=BBBB........#
+#..[^^^^^^].,..[^^^^^^]........#
+#..{======}.,..{======}........#
+#..{======}.,..{======}........#
+#..{======}.,..{======}........#
+#..(__+___).,..(__+___)........#
#.....,.....,.....,............#
#.....,,,,,,,,,,,,,............#
#...........,..................#
diff --git a/src/engine/world.c b/src/engine/world.c
@@ -12,7 +12,8 @@
#error "combat formula divisors must be positive"
#endif
-#if PH_WORLD_STEP_MILLISECONDS <= 0 || PH_WORLD_ACTION_MILLISECONDS <= 0 || \
+#if PH_WORLD_STEP_MILLISECONDS <= 0 || PH_WORLD_ANIMATION_PIXELS_PER_FRAME <= 0 || \
+ PH_WORLD_ACTION_MILLISECONDS <= 0 || \
PH_MOB_MEMORY_TURNS < 0 || \
PH_MOB_WANDER_PERCENT < 0 || PH_MOB_WANDER_PERCENT > 100
#error "invalid world movement configuration"
@@ -212,7 +213,10 @@ ph_try_pickup(PhWorld *world, PhEntity *entity)
if (!drop->active || drop->area_id != world->area_id) {
continue;
}
- if (ph_dist2(entity->pos, drop->pos) > PH_PICKUP_RADIUS * PH_PICKUP_RADIUS) {
+ if ((int)floorf(entity->pos.x / PH_TILE_SIZE) !=
+ (int)floorf(drop->pos.x / PH_TILE_SIZE) ||
+ (int)floorf(entity->pos.y / PH_TILE_SIZE) !=
+ (int)floorf(drop->pos.y / PH_TILE_SIZE)) {
continue;
}
@@ -383,6 +387,9 @@ ph_world_advance_movers(PhWorld *world, float dt)
float dy;
float distance;
float travel;
+ float moved;
+ float animation_cycle;
+ int animation_frames;
if (!entity->active || !entity->moving ||
entity->area_id != world->area_id) continue;
@@ -392,6 +399,16 @@ ph_world_advance_movers(PhWorld *world, float dt)
dy = entity->move_target.y - entity->pos.y;
distance = sqrtf(dx * dx + dy * dy);
travel = (float)(PH_TILE_SIZE * def->move_speed) * dt / step_seconds;
+ moved = distance < travel ? distance : travel;
+ entity->animation_distance += moved;
+ animation_frames = def->animation_frames > 0 &&
+ def->animation_frames <= PH_ANIMATION_FRAMES ?
+ def->animation_frames : 1;
+ animation_cycle = (float)(animation_frames *
+ PH_WORLD_ANIMATION_PIXELS_PER_FRAME);
+ if (entity->animation_distance >= animation_cycle)
+ entity->animation_distance = fmodf(entity->animation_distance,
+ animation_cycle);
if (distance <= travel || distance <= 0.001f) {
entity->pos = entity->move_target;
entity->moving = 0;
@@ -855,6 +872,7 @@ ph_world_tick(PhWorld *world, PhInput input, float dt)
{
PhEntity *player;
const PhEntityDef *def;
+ int was_moving;
if (world->player_index < 0 || world->player_index >= world->entity_count) {
return;
@@ -875,10 +893,15 @@ ph_world_tick(PhWorld *world, PhInput input, float dt)
input = ph_orthogonal_input(input);
world->interaction_id = 0;
+ was_moving = player->moving;
if (!player->moving && def->move_speed > 0)
ph_entity_begin_step(world, world->player_index,
input.move_x, input.move_y, 0);
ph_world_advance_movers(world, dt);
+ if (was_moving && !player->moving && world->encounter_index < 0 &&
+ def->move_speed > 0)
+ ph_entity_begin_step(world, world->player_index,
+ input.move_x, input.move_y, 0);
if (input.interact) {
ph_try_pickup(world, player);
@@ -892,22 +915,13 @@ ph_world_tick(PhWorld *world, PhInput input, float dt)
int
ph_entity_animation_frame(const PhEntityDef *def, const PhEntity *entity)
{
- float dx;
- float dy;
- float remaining;
- float progress;
int frames;
- int frame;
if (!def || !entity || !entity->moving) return 0;
frames = def->animation_frames > 0 &&
def->animation_frames <= PH_ANIMATION_FRAMES ? def->animation_frames : 1;
- dx = entity->move_target.x - entity->pos.x;
- dy = entity->move_target.y - entity->pos.y;
- remaining = sqrtf(dx * dx + dy * dy);
- progress = 1.0f - ph_clampf(remaining / (float)PH_TILE_SIZE, 0.0f, 1.0f);
- frame = (int)(progress * (float)frames);
- return frame < frames ? frame : frames - 1;
+ return (int)(entity->animation_distance /
+ PH_WORLD_ANIMATION_PIXELS_PER_FRAME) % frames;
}
const PhEntity *
diff --git a/src/engine/world.h b/src/engine/world.h
@@ -3,7 +3,7 @@
#include <stddef.h>
-#define PH_CONFIG_API_VERSION 8
+#define PH_CONFIG_API_VERSION 9
enum {
PH_MAP_MAGIC = 0x50484d50,
@@ -126,6 +126,7 @@ typedef struct {
int chase_turns;
int moving;
float move_credit;
+ float animation_distance;
int equipment[PH_EQUIP_COUNT];
int active;
} PhEntity;
diff --git a/src/game/content.c b/src/game/content.c
@@ -16,8 +16,16 @@ const PhTileDef ph_tile_defs[PH_TILE_DEF_COUNT] = {
{ '.', 0, PH_ASSET_TILESET, 1, 1 },
{ '#', 1, PH_ASSET_TILESET, 1, 5 },
{ ',', 0, PH_ASSET_TILESET, 5, 0 },
- { 'B', 1, PH_ASSET_TILESET, 1, 16 },
- { '=', 0, PH_ASSET_TILESET, 3, 14 },
+ { '[', 1, PH_ASSET_TILESET, 9, 1 },
+ { '^', 1, PH_ASSET_TILESET, 10, 1 },
+ { ']', 1, PH_ASSET_TILESET, 11, 1 },
+ { '{', 1, PH_ASSET_TILESET, 9, 2 },
+ { '=', 0, PH_ASSET_TILESET, 10, 2 },
+ { '}', 1, PH_ASSET_TILESET, 11, 2 },
+ { '(', 1, PH_ASSET_TILESET, 9, 3 },
+ { '_', 1, PH_ASSET_TILESET, 10, 3 },
+ { ')', 1, PH_ASSET_TILESET, 11, 3 },
+ { '+', 0, PH_ASSET_TILESET, 10, 3 },
{ 'T', 0, PH_ASSET_TILESET, 6, 11 },
};
diff --git a/src/game/content.h b/src/game/content.h
@@ -114,7 +114,7 @@ enum {
};
enum {
- PH_TILE_DEF_COUNT = 6,
+ PH_TILE_DEF_COUNT = 14,
PH_ENTITY_SPAWN_COUNT = 3,
PH_ITEM_DROP_COUNT = 2,
PH_PORTAL_COUNT = 2,
diff --git a/src/game/main.c b/src/game/main.c
@@ -3,6 +3,7 @@
#include "game/battle.h"
#include "game/content.h"
+#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
@@ -106,6 +107,12 @@ ph_ui_center_text(const char *text)
}
static float
+ph_world_pixel(float position, float camera)
+{
+ return floorf(position + 0.5f) - floorf(camera + 0.5f);
+}
+
+static float
ph_draw_wrapped_text(SDL_Renderer *renderer, float x, float y,
float width, float line_height, const char *text)
{
@@ -231,8 +238,8 @@ ph_draw_area(SDL_Renderer *renderer, const PhAssets *assets, const PhWorld *worl
const PhTileDef *def = ph_area_tile_def(&world->area, tx, ty);
SDL_Texture *texture;
SDL_FRect rect = {
- .x = (float)(tx * PH_TILE_SIZE) - world->camera.pos.x,
- .y = (float)(ty * PH_TILE_SIZE) - world->camera.pos.y,
+ .x = ph_world_pixel((float)(tx * PH_TILE_SIZE), world->camera.pos.x),
+ .y = ph_world_pixel((float)(ty * PH_TILE_SIZE), world->camera.pos.y),
.w = (float)PH_TILE_SIZE,
.h = (float)PH_TILE_SIZE,
};
@@ -269,8 +276,8 @@ ph_draw_items(SDL_Renderer *renderer, const PhAssets *assets, const PhWorld *wor
continue;
}
- rect.x = drop->pos.x - world->camera.pos.x - 3.0f;
- rect.y = drop->pos.y - world->camera.pos.y - 3.0f;
+ rect.x = ph_world_pixel(drop->pos.x, world->camera.pos.x) - 3.0f;
+ rect.y = ph_world_pixel(drop->pos.y, world->camera.pos.y) - 3.0f;
rect.w = 6.0f;
rect.h = 6.0f;
@@ -278,8 +285,10 @@ ph_draw_items(SDL_Renderer *renderer, const PhAssets *assets, const PhWorld *wor
(size_t)def->asset_id < LEN(assets->textures))
texture = assets->textures[def->asset_id];
if (texture && def->sprite_tile_x >= 0 && def->sprite_tile_y >= 0) {
- rect.x = drop->pos.x - world->camera.pos.x - PH_TILE_SIZE * 0.5f;
- rect.y = drop->pos.y - world->camera.pos.y - PH_TILE_SIZE * 0.5f;
+ rect.x = ph_world_pixel(drop->pos.x, world->camera.pos.x) -
+ PH_TILE_SIZE * 0.5f;
+ rect.y = ph_world_pixel(drop->pos.y, world->camera.pos.y) -
+ PH_TILE_SIZE * 0.5f;
rect.w = PH_TILE_SIZE;
rect.h = PH_TILE_SIZE;
ph_draw_sprite(renderer, texture,
@@ -315,15 +324,17 @@ ph_draw_entities(SDL_Renderer *renderer, const PhAssets *assets, const PhWorld *
ph_entity_sprite_frame(def, entity, &sprite_tile_x, &sprite_tile_y, &flip);
if (texture && sprite_tile_x >= 0 && sprite_tile_y >= 0) {
- rect.x = entity->pos.x - world->camera.pos.x - PH_TILE_SIZE * 0.5f;
- rect.y = entity->pos.y - world->camera.pos.y - PH_TILE_SIZE * 0.75f;
+ rect.x = ph_world_pixel(entity->pos.x, world->camera.pos.x) -
+ PH_TILE_SIZE * 0.5f;
+ rect.y = ph_world_pixel(entity->pos.y, world->camera.pos.y) -
+ PH_TILE_SIZE * 0.75f;
rect.w = PH_TILE_SIZE;
rect.h = PH_TILE_SIZE;
ph_draw_sprite(renderer, texture, sprite_tile_x, sprite_tile_y, rect, flip);
} else {
- rect.x = entity->pos.x - world->camera.pos.x - 7.0f;
- rect.y = entity->pos.y - world->camera.pos.y - 12.0f;
+ rect.x = ph_world_pixel(entity->pos.x, world->camera.pos.x) - 7.0f;
+ rect.y = ph_world_pixel(entity->pos.y, world->camera.pos.y) - 12.0f;
rect.w = 14.0f;
rect.h = 18.0f;
@@ -1203,6 +1214,11 @@ ph_run_render_smoke_test(void)
return 1;
}
+ ph_world_tick(&world, (PhInput){ .interact = 1 }, 0.0f);
+ if (ph_world_item_amount(&world, ph_item_drops[0].item_id) != 0) {
+ fprintf(stderr, "render smoke test failed: adjacent item pickup\n");
+ result = 1;
+ }
ph_world_tick(&world, (PhInput){ .move_x = 1 },
(float)PH_WORLD_STEP_MILLISECONDS / 1000.0f);
ph_world_tick(&world, (PhInput){ .interact = 1 }, 0.0f);
diff --git a/src/tests/smoke.c b/src/tests/smoke.c
@@ -65,7 +65,9 @@ ph_movement_test(int open, int blocked)
ph_world_tick(&world, (PhInput){ .move_x = 1 }, PH_MAX_FRAME_TIME);
if (!world.entities[player_index].moving ||
world.entities[player_index].move_target.x !=
- PH_TILE_CENTER(PLAYER_COL + 1)) return 1;
+ PH_TILE_CENTER(PLAYER_COL + 1) ||
+ ph_entity_animation_frame(&world.entity_defs[0],
+ &world.entities[player_index]) != 0) return 1;
ph_world_tick(&world, (PhInput){ 0 }, PH_MAX_FRAME_TIME);
if (ph_entity_animation_frame(&world.entity_defs[0],
&world.entities[player_index]) <= 0) return 1;
@@ -153,10 +155,14 @@ ph_logic_test(int open, int blocked)
(player_index = ph_world_spawn_entity(&world, 1, player_start, 0, 1)) < 0 ||
(enemy_index = ph_world_spawn_entity(&world, 4, enemy_start, 0, 1)) < 0 ||
ph_world_drop_item(&world, 1, player_start, 1, 1) < 0 ||
- ph_world_drop_item(&world, 2, player_start, 2, 1) < 0) return 1;
+ ph_world_drop_item(&world, 2, player_start, 2, 1) < 0 ||
+ ph_world_drop_item(&world, 1,
+ (PhVec2){ PH_TILE_CENTER(RIGHT_COL), PH_TILE_CENTER(ROW) },
+ 1, 1) < 0) return 1;
ph_world_set_player(&world, player_index);
if (ph_world_equip_item(&world, player_index, 3) < 0) return 1;
ph_world_tick(&world, (PhInput){ .interact = 1 }, 0.0f);
+ if (!world.ground_items[2].active) return 1;
if (ph_world_equip_inventory_item(&world, player_index, 1) < 0) return 1;
world.entities[player_index].hp = 1;
if (ph_world_use_inventory_item(&world, player_index, 2) < 0 ||
@@ -237,6 +243,9 @@ ph_area_shop_test(void)
player->pos = (PhVec2){ PH_TILE_CENTER(29), PH_TILE_CENTER(2) };
if (ph_game_update_portal(&world) != 1 ||
world.area_id != PH_AREA_STONEHOLLOW) goto fail;
+ if (!ph_area_tile_blocked(&world.area, 3, 2) ||
+ ph_area_tile_blocked(&world.area, 4, 3) ||
+ ph_area_tile_blocked(&world.area, 6, 6)) goto fail;
for (i = 0; i < world.entity_count; ++i) {
const PhEntityDef *def = ph_world_entity_def(&world,
world.entities[i].type_id);