commit ae5a1592ddbf20295d9034c62d7a1be9bf2ef2bc
parent 76ff3492aea3b0d0ef01f68be4b6eec5ab1e0d1a
Author: beep <beep@wimdupont.com>
Date: Thu, 30 Jul 2026 09:39:30 +0000
Add semantic autotiling and shop interior
Diffstat:
9 files changed, 155 insertions(+), 76 deletions(-)
diff --git a/data/maps/ashen-meadow.txt b/data/maps/ashen-meadow.txt
@@ -1,26 +1,26 @@
name: Ashen Meadow
-ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBC
-D:..;...:.....;..:.....;.......F
-D..:....;..............:....e>.F
-D.....ABC....;......:..........F
-D....@1EF........:.......;.....F
-D.....2HI...:........ABC.......F
-D..;.......a.........DEF..:....F
-D........:...........GHI.......F
-D....;.........:..........;....F
-D.........ABC................:.F
-D..:......DEF...;..............F
-D.........GHI..........:.......F
-D....;.............ABC.........F
-D........:.........DEF.....;...F
-D..ABC.............GHI.........F
-D..DEF...;....:...............:F
-D..GHI..............;..........F
-D...........ABC..........:.....F
-D...:.......DEF....;...........F
-D...........GHI...........;....F
-D..;.............:.............F
-D.......:..............;.......F
-D..;.........:..............;..F
-GHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHI
+################################
+#:..;...:.....;..:.....;.......#
+#..:....;..............:....e>.#
+#............;......:..........#
+#....@1..........:.......;.....#
+#.....2.....:........###.......#
+#..;.......a.........###..:....#
+#........:...........###.......#
+#....;.........:..........;....#
+#.........###................:.#
+#..:......###...;..............#
+#.........###..........:.......#
+#....;.............###.........#
+#........:.........###.....;...#
+#..###.............###.........#
+#..###...;....:...............:#
+#..###..............;..........#
+#...........###..........:.....#
+#...:.......###....;...........#
+#...........###...........;....#
+#..;.............:.............#
+#.......:..............;.......#
+#..;.........:..............;..#
+################################
diff --git a/data/maps/mira-shop.txt b/data/maps/mira-shop.txt
@@ -0,0 +1,17 @@
+name: Mira's Sundries
+
+[^^^^^^^^^^^^^^^^^^]
+{==================}
+{==================}
+{==================}
+{========m=========}
+{==================}
+{==================}
+{==================}
+{==================}
+{==================}
+{==================}
+{==================}
+{==================}
+{========e=========}
+(_________<________)
diff --git a/data/maps/stonehollow.txt b/data/maps/stonehollow.txt
@@ -1,26 +1,26 @@
name: Stonehollow
-ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBC
-D..............................F
-D..J....K....L....M....N....O..F
-D..m...........................F
-D..,,,,,,,,,,,,,,,,,,,,,,,,,,..F
-D..,....:.........;.........,..F
-D..,........................,..F
-D..,....P.....Q.....R.......,..F
-D..,........................,..F
-D..,.........ABC............,..F
-D..,,,,,,,,,,DEF,,,,,,,,,,,,,..F
-D..,.........GHI............,..F
-D..,....;...............:...,..F
-D..,........................,..F
-D..,......M......K......P...,..F
-D..,,,,,,,,,,,,,,,,,,,,,,,,,,..F
-D.............,................F
-D....;........,......:.........F
-D.............,................F
-D..:..........,..........;.....F
-D.............,................F
-D.............e................F
-D.............<................F
-GHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHI
+################################
+#..............................#
+#..>....K....L....M....N....O..#
+#..i...........................#
+#..,,,,,,,,,,,,,,,,,,,,,,,,,,..#
+#..,....:.........;.........,..#
+#..,........................,..#
+#..,....P.....Q.....R.......,..#
+#..,........................,..#
+#..,.........###............,..#
+#..,,,,,,,,,,###,,,,,,,,,,,,,..#
+#..,.........###............,..#
+#..,....;...............:...,..#
+#..,........................,..#
+#..,......M......K......P...,..#
+#..,,,,,,,,,,,,,,,,,,,,,,,,,,..#
+#.............,................#
+#....;........,......:.........#
+#.............,................#
+#..:..........,..........;.....#
+#.............,................#
+#.............e................#
+#.............<................#
+################################
diff --git a/src/engine/render.c b/src/engine/render.c
@@ -123,6 +123,8 @@ ph_draw_area(SDL_Renderer *renderer, const PhRenderAssets *assets, const PhWorld
.h = (float)PH_TILE_SIZE,
};
SDL_FRect src;
+ int sprite_tile_x;
+ int sprite_tile_y;
if (!def) continue;
if (def->background_asset_id) {
@@ -140,9 +142,24 @@ ph_draw_area(SDL_Renderer *renderer, const PhRenderAssets *assets, const PhWorld
}
texture = ph_render_asset(assets, def->asset_id);
if (!texture) continue;
+ sprite_tile_x = def->sprite_tile_x;
+ sprite_tile_y = def->sprite_tile_y;
+ if (def->autotile == PH_TILE_AUTOTILE_3X3) {
+ const PhTileDef *north = ph_area_tile_def(&world->area, tx, ty - 1);
+ const PhTileDef *east = ph_area_tile_def(&world->area, tx + 1, ty);
+ const PhTileDef *south = ph_area_tile_def(&world->area, tx, ty + 1);
+ const PhTileDef *west = ph_area_tile_def(&world->area, tx - 1, ty);
+ int joins_north = north && north->symbol == def->symbol;
+ int joins_east = east && east->symbol == def->symbol;
+ int joins_south = south && south->symbol == def->symbol;
+ int joins_west = west && west->symbol == def->symbol;
+
+ sprite_tile_x += joins_west == joins_east ? 1 : joins_west ? 2 : 0;
+ sprite_tile_y += joins_north == joins_south ? 1 : joins_north ? 2 : 0;
+ }
src = (SDL_FRect){
- .x = (float)(def->sprite_tile_x * PH_TILE_SIZE),
- .y = (float)(def->sprite_tile_y * PH_TILE_SIZE),
+ .x = (float)(sprite_tile_x * PH_TILE_SIZE),
+ .y = (float)(sprite_tile_y * PH_TILE_SIZE),
.w = (float)PH_TILE_SIZE,
.h = (float)PH_TILE_SIZE,
};
diff --git a/src/engine/world.h b/src/engine/world.h
@@ -112,8 +112,14 @@ typedef struct {
int background_asset_id;
int background_sprite_tile_x;
int background_sprite_tile_y;
+ int autotile;
} PhTileDef;
+enum {
+ PH_TILE_AUTOTILE_NONE,
+ PH_TILE_AUTOTILE_3X3,
+};
+
typedef struct {
int id;
const char *name;
diff --git a/src/game/content.c b/src/game/content.c
@@ -10,23 +10,16 @@ const PhAssetDef ph_asset_defs[PH_ASSET_COUNT] = {
const PhAreaDef ph_area_defs[PH_AREA_COUNT] = {
{ PH_AREA_MEADOW, "obj/maps/ashen-meadow.phmap" },
{ PH_AREA_STONEHOLLOW, "obj/maps/stonehollow.phmap" },
+ { PH_AREA_MIRA_SHOP, "obj/maps/mira-shop.phmap" },
};
const PhTileDef ph_tile_defs[PH_TILE_DEF_COUNT] = {
- { '.', 0, PH_ASSET_TILESET, 1, 1, 0, 0, 0 },
- { ':', 0, PH_ASSET_TILESET, 1, 0, 0, 0, 0 },
- { ';', 0, PH_ASSET_TILESET, 2, 0, 0, 0, 0 },
- { ',', 0, PH_ASSET_TILESET, 6, 2, 0, 0, 0 },
- { 'T', 0, PH_ASSET_TILESET, 6, 2, 0, 0, 0 },
- { 'A', 1, PH_ASSET_TILESET, 9, 4, 0, 0, 0 },
- { 'B', 1, PH_ASSET_TILESET, 10, 4, 0, 0, 0 },
- { 'C', 1, PH_ASSET_TILESET, 11, 4, 0, 0, 0 },
- { 'D', 1, PH_ASSET_TILESET, 9, 5, 0, 0, 0 },
- { 'E', 1, PH_ASSET_TILESET, 10, 5, 0, 0, 0 },
- { 'F', 1, PH_ASSET_TILESET, 11, 5, 0, 0, 0 },
- { 'G', 1, PH_ASSET_TILESET, 9, 6, 0, 0, 0 },
- { 'H', 1, PH_ASSET_TILESET, 10, 6, 0, 0, 0 },
- { 'I', 1, PH_ASSET_TILESET, 11, 6, 0, 0, 0 },
+ { '.', 0, PH_ASSET_TILESET, 1, 1, 0, 0, 0, 0 },
+ { ':', 0, PH_ASSET_TILESET, 1, 0, 0, 0, 0, 0 },
+ { ';', 0, PH_ASSET_TILESET, 2, 0, 0, 0, 0, 0 },
+ { ',', 0, PH_ASSET_TILESET, 6, 2, 0, 0, 0, 0 },
+ { 'T', 0, PH_ASSET_TILESET, 6, 2, 0, 0, 0, 0 },
+ { '#', 1, PH_ASSET_TILESET, 9, 4, 0, 0, 0, PH_TILE_AUTOTILE_3X3 },
{ .symbol = 'J', .blocks_movement = 1, .asset_id = PH_ASSET_TILESET,
.sprite_tile_x = 0, .sprite_tile_y = 16,
.background_asset_id = PH_ASSET_TILESET,
@@ -63,6 +56,20 @@ const PhTileDef ph_tile_defs[PH_TILE_DEF_COUNT] = {
.sprite_tile_x = 3, .sprite_tile_y = 18,
.background_asset_id = PH_ASSET_TILESET,
.background_sprite_tile_x = 1, .background_sprite_tile_y = 1 },
+ { .symbol = 'j', .asset_id = PH_ASSET_TILESET,
+ .sprite_tile_x = 0, .sprite_tile_y = 16,
+ .background_asset_id = PH_ASSET_TILESET,
+ .background_sprite_tile_x = 1, .background_sprite_tile_y = 1 },
+ { '[', 1, PH_ASSET_TILESET, 9, 1, 0, 0, 0, 0 },
+ { '^', 1, PH_ASSET_TILESET, 10, 1, 0, 0, 0, 0 },
+ { ']', 1, PH_ASSET_TILESET, 11, 1, 0, 0, 0, 0 },
+ { '{', 1, PH_ASSET_TILESET, 9, 2, 0, 0, 0, 0 },
+ { '=', 0, PH_ASSET_TILESET, 10, 2, 0, 0, 0, 0 },
+ { '}', 1, PH_ASSET_TILESET, 11, 2, 0, 0, 0, 0 },
+ { '(', 1, PH_ASSET_TILESET, 9, 3, 0, 0, 0, 0 },
+ { '_', 1, PH_ASSET_TILESET, 10, 3, 0, 0, 0, 0 },
+ { ')', 1, PH_ASSET_TILESET, 11, 3, 0, 0, 0, 0 },
+ { '+', 0, PH_ASSET_TILESET, 10, 3, 0, 0, 0, 0 },
};
const PhSpellDef ph_spell_defs[PH_SPELL_DEF_COUNT] = {
@@ -212,11 +219,17 @@ const PhMarkerDef ph_marker_defs[PH_MARKER_DEF_COUNT] = {
{ PH_AREA_MEADOW, '>', 'T', PH_MARKER_PORTAL,
.destination_area_id = PH_AREA_STONEHOLLOW, .destination_symbol = 'e' },
{ PH_AREA_MEADOW, 'e', '.', PH_MARKER_ARRIVAL, .content_id = 0 },
- { PH_AREA_STONEHOLLOW, 'm', ',', PH_MARKER_ENTITY,
- PH_DEF_SHOPKEEPER, .amount = 0 },
+ { PH_AREA_STONEHOLLOW, '>', 'j', PH_MARKER_PORTAL,
+ .destination_area_id = PH_AREA_MIRA_SHOP, .destination_symbol = 'e' },
{ PH_AREA_STONEHOLLOW, '<', 'T', PH_MARKER_PORTAL,
.destination_area_id = PH_AREA_MEADOW, .destination_symbol = 'e' },
{ PH_AREA_STONEHOLLOW, 'e', ',', PH_MARKER_ARRIVAL, .content_id = 0 },
+ { PH_AREA_STONEHOLLOW, 'i', ',', PH_MARKER_ARRIVAL, .content_id = 0 },
+ { PH_AREA_MIRA_SHOP, 'm', '=', PH_MARKER_ENTITY,
+ PH_DEF_SHOPKEEPER, .amount = 0 },
+ { PH_AREA_MIRA_SHOP, 'e', '=', PH_MARKER_ARRIVAL, .content_id = 0 },
+ { PH_AREA_MIRA_SHOP, '<', '+', PH_MARKER_PORTAL,
+ .destination_area_id = PH_AREA_STONEHOLLOW, .destination_symbol = 'i' },
};
static const PhAreaCatalog ph_area_catalog = {
diff --git a/src/game/content.h b/src/game/content.h
@@ -55,7 +55,8 @@ enum {
enum {
PH_AREA_MEADOW = 1,
PH_AREA_STONEHOLLOW,
- PH_AREA_COUNT = 2,
+ PH_AREA_MIRA_SHOP,
+ PH_AREA_COUNT = 3,
PH_START_AREA = PH_AREA_MEADOW,
};
@@ -73,8 +74,8 @@ enum {
};
enum {
- PH_TILE_DEF_COUNT = 23,
- PH_MARKER_DEF_COUNT = 9,
+ PH_TILE_DEF_COUNT = 26,
+ PH_MARKER_DEF_COUNT = 13,
};
extern const PhAssetDef ph_asset_defs[PH_ASSET_COUNT];
diff --git a/src/tests/render.c b/src/tests/render.c
@@ -241,6 +241,23 @@ main(void)
fprintf(stderr, "render smoke test failed: town portal\n");
result = 1;
}
+ for (i = 0; i < world.area.marker_count; ++i) {
+ const PhMarkerDef *def = ph_area_catalog_marker(ph_game_world.areas,
+ world.area_id, world.area.markers[i].symbol);
+
+ if (!def || def->kind != PH_MARKER_PORTAL ||
+ def->destination_area_id != PH_AREA_MIRA_SHOP) continue;
+ world.entities[world.player_index].pos = (PhVec2){
+ PH_TILE_CENTER(world.area.markers[i].tile_x),
+ PH_TILE_CENTER(world.area.markers[i].tile_y) };
+ break;
+ }
+ if (i == world.area.marker_count ||
+ ph_world_update_portal(&world, ph_game_world.areas) != 1 ||
+ world.area_id != PH_AREA_MIRA_SHOP) {
+ fprintf(stderr, "render smoke test failed: shop portal\n");
+ result = 1;
+ }
ph_shop_open(&shop_ui, ph_shop_defs[0].id);
shop_ui.mode = PH_SHOP_TALK;
context.menu = PH_MENU_SHOP;
diff --git a/src/tests/smoke.c b/src/tests/smoke.c
@@ -298,7 +298,7 @@ ph_logic_test(int open, int blocked)
}
static int
-ph_enter_portal(PhWorld *world)
+ph_enter_portal(PhWorld *world, int destination_area_id)
{
PhEntity *player = &world->entities[world->player_index];
int i;
@@ -309,9 +309,10 @@ ph_enter_portal(PhWorld *world)
world->area_id, marker->symbol);
const PhTileDef *tile;
- if (!def || def->kind != PH_MARKER_PORTAL) continue;
+ if (!def || def->kind != PH_MARKER_PORTAL ||
+ def->destination_area_id != destination_area_id) continue;
tile = ph_area_tile_def(&world->area, marker->tile_x, marker->tile_y);
- if (!tile || tile->symbol != 'T') return -1;
+ if (!tile || tile->blocks_movement) return -1;
player->pos = (PhVec2){ PH_TILE_CENTER(marker->tile_x),
PH_TILE_CENTER(marker->tile_y) };
return ph_world_update_portal(world, ph_game_world.areas);
@@ -338,12 +339,16 @@ ph_area_shop_test(void)
if (!potion || !ether || world.area_id != PH_AREA_MEADOW || player->gold != 40 ||
world.initialized_area_count != 1)
goto fail;
- if (ph_enter_portal(&world) != 1 ||
+ if (ph_enter_portal(&world, PH_AREA_STONEHOLLOW) != 1 ||
world.area_id != PH_AREA_STONEHOLLOW ||
world.initialized_area_count != 2) goto fail;
- if (!ph_area_tile_blocked(&world.area, 3, 2) ||
+ if (ph_area_tile_blocked(&world.area, 3, 2) ||
+ !ph_area_tile_blocked(&world.area, 8, 2) ||
ph_area_tile_blocked(&world.area, 4, 4) ||
ph_area_tile_blocked(&world.area, 6, 6)) goto fail;
+ if (ph_enter_portal(&world, PH_AREA_MIRA_SHOP) != 1 ||
+ world.area_id != PH_AREA_MIRA_SHOP ||
+ world.initialized_area_count != 3) goto fail;
for (i = 0; i < world.entity_count; ++i) {
const PhEntityDef *def = ph_world_entity_def(&world,
world.entities[i].type_id);
@@ -378,8 +383,11 @@ ph_area_shop_test(void)
player->vitals.mp = 0;
if (ph_world_use_inventory_item(&world, world.player_index, ether->id) < 0 ||
player->vitals.mp != ether->use_power) goto fail;
- if (ph_enter_portal(&world) != 1 || world.area_id != PH_AREA_MEADOW ||
- world.initialized_area_count != 2)
+ if (ph_enter_portal(&world, PH_AREA_STONEHOLLOW) != 1 ||
+ world.area_id != PH_AREA_STONEHOLLOW ||
+ world.initialized_area_count != 3 ||
+ ph_enter_portal(&world, PH_AREA_MEADOW) != 1 ||
+ world.area_id != PH_AREA_MEADOW || world.initialized_area_count != 3)
goto fail;
for (i = 0; i < world.entity_count; ++i) {
const PhEntityDef *def = ph_world_entity_def(&world,