commit cfc0364d7fcc8a45c91d65674d328fe2fee9d5e0
parent 0a1b42c8e5d5306a911ca1c2a6696659550341e3
Author: beep <beep@wimdupont.com>
Date: Thu, 30 Jul 2026 08:03:42 +0000
Move generic area and rendering systems into engine
Diffstat:
15 files changed, 618 insertions(+), 470 deletions(-)
diff --git a/Makefile b/Makefile
@@ -16,7 +16,9 @@ CRITTERS_SRC = assets/vendor/16x16-animated-critters/16x16babies.png
CRITTERS_BMP = $(OBJDIR)/assets/critters.bmp
SRC = \
+ src/engine/area.c \
src/engine/battle.c \
+ src/engine/render.c \
src/engine/world.c \
src/game/area.c \
src/game/content.c \
@@ -29,7 +31,8 @@ CONFIG = config.h
OBJ = $(SRC:src/%.c=$(OBJDIR)/%.o)
SMOKE_OBJ = $(OBJDIR)/tests/smoke.o
RENDER_SMOKE_OBJ = $(OBJDIR)/tests/render.o
-CORE_OBJ = $(filter-out $(OBJDIR)/game/main.o $(OBJDIR)/game/presentation.o,$(OBJ))
+CORE_OBJ = $(filter-out $(OBJDIR)/engine/render.o $(OBJDIR)/game/main.o \
+ $(OBJDIR)/game/presentation.o,$(OBJ))
DEP = $(OBJ:.o=.d) $(SMOKE_OBJ:.o=.d) $(RENDER_SMOKE_OBJ:.o=.d)
SDL3_CFLAGS = $(shell pkg-config --cflags sdl3 2>/dev/null)
@@ -59,10 +62,12 @@ $(SMOKE): $(CORE_OBJ) $(SMOKE_OBJ)
@mkdir -p $(dir $@)
$(CC) $^ -o $@ -lm
-$(RENDER_SMOKE): $(CORE_OBJ) $(OBJDIR)/game/presentation.o \
- $(RENDER_SMOKE_OBJ) $(MAPBIN) $(TILESET_BMP) $(HERO_BMP) $(ITEMS_BMP) $(CRITTERS_BMP)
+$(RENDER_SMOKE): $(CORE_OBJ) $(OBJDIR)/engine/render.o \
+ $(OBJDIR)/game/presentation.o $(RENDER_SMOKE_OBJ) $(MAPBIN) \
+ $(TILESET_BMP) $(HERO_BMP) $(ITEMS_BMP) $(CRITTERS_BMP)
@mkdir -p $(dir $@)
- $(CC) $(CORE_OBJ) $(OBJDIR)/game/presentation.o $(RENDER_SMOKE_OBJ) \
+ $(CC) $(CORE_OBJ) $(OBJDIR)/engine/render.o $(OBJDIR)/game/presentation.o \
+ $(RENDER_SMOKE_OBJ) \
-o $@ $(LDLIBS)
$(MAPC): $(MAPC_SRC) $(CONFIG)
diff --git a/README.adoc b/README.adoc
@@ -18,21 +18,24 @@ Stonehollow. Named IDs belong in the content catalog and content-specific tests;
initialization, world logic, and presentation treat those IDs as opaque data.
The source tree enforces that boundary. `src/engine` contains reusable mechanics
-and must never include `game/*`. `src/game` owns Phantasia content, area
-composition, presentation, and application startup; it may depend on the engine.
-Code belongs in the engine only when it can operate without named Phantasia
-content or presentation knowledge.
-
-Core world and CTB battle logic are independent of SDL. `src/game/main.c`
-composes those systems with input and presentation; `src/engine/battle.c` owns
-the battle state machine and receives the game's spell book as data. Headless
-world and battle coverage lives in `src/tests/smoke.c`; SDL presentation coverage
-lives in `src/tests/render.c` and builds as a separate executable. Production
-`main.c` contains no test fixtures.
-`src/game/area.c` loads content-defined maps and portals while preserving the
-state of entities and ground items belonging to inactive areas. Marker content
-is instantiated once when an area is first entered; only the active tile map is
-retained.
+and must never include `game/*`. `src/game` owns Phantasia's content catalogs,
+starting-world composition, interface overlays, and application startup; it may
+depend on the engine. Code belongs in the engine whenever it operates entirely
+on generic engine types and caller-supplied data.
+
+Core world, area, and CTB battle logic are independent of SDL. `src/engine/area.c`
+loads caller-supplied area catalogs, instantiates generic markers, and handles
+portal travel while preserving inactive-area entity and item state.
+`src/engine/battle.c` owns the battle state machine and receives the game's spell
+book as data. The optional SDL module `src/engine/render.c` owns generic asset,
+sprite, tile, item, and entity rendering. `src/game/area.c` only composes the
+starting world from Phantasia's catalogs, while `src/game/presentation.c` owns
+game-specific menus and overlays. `src/game/main.c` composes those systems with
+input and application lifecycle.
+
+Headless engine coverage lives in `src/tests/smoke.c`; SDL rendering and
+presentation coverage lives in `src/tests/render.c` and builds as a separate
+executable. Production `main.c` contains no test fixtures.
World actors update even while the player stands still. Entity content defines
relative movement speed, directional animation frames, vision, and aggression;
diff --git a/src/engine/area.c b/src/engine/area.c
@@ -0,0 +1,138 @@
+#include "engine/area.h"
+
+#include <math.h>
+
+const PhAreaDef *
+ph_area_catalog_area(const PhAreaCatalog *catalog, int area_id)
+{
+ int i;
+
+ for (i = 0; i < catalog->area_count; ++i)
+ if (catalog->areas[i].id == area_id) return &catalog->areas[i];
+ return NULL;
+}
+
+const PhMarkerDef *
+ph_area_catalog_marker(const PhAreaCatalog *catalog, int area_id,
+ unsigned char symbol)
+{
+ int i;
+
+ for (i = 0; i < catalog->marker_count; ++i)
+ if (catalog->markers[i].area_id == area_id &&
+ catalog->markers[i].symbol == symbol) return &catalog->markers[i];
+ return NULL;
+}
+
+int
+ph_area_catalog_load(PhArea *area, const PhAreaCatalog *catalog, int area_id)
+{
+ const PhAreaDef *def = ph_area_catalog_area(catalog, area_id);
+
+ if (!def || ph_area_load(area, def->path) < 0) return -1;
+ area->tile_defs = catalog->tiles;
+ area->tile_def_count = catalog->tile_count;
+ return 0;
+}
+
+static PhVec2
+ph_marker_pos(const PhAreaMarker *marker)
+{
+ return (PhVec2){ PH_TILE_CENTER(marker->tile_x),
+ PH_TILE_CENTER(marker->tile_y) };
+}
+
+static int
+ph_world_add_area_content(PhWorld *world, const PhArea *area,
+ const PhAreaCatalog *catalog, int area_id, int *player)
+{
+ int i;
+
+ for (i = 0; i < area->marker_count; ++i) {
+ const PhAreaMarker *marker = &area->markers[i];
+ const PhMarkerDef *def = ph_area_catalog_marker(catalog, area_id,
+ marker->symbol);
+ int entity;
+
+ if (!def) return -1;
+ if (def->kind == PH_MARKER_ITEM) {
+ if (ph_world_drop_item(world, def->content_id, ph_marker_pos(marker),
+ def->amount, area_id) < 0) return -1;
+ continue;
+ }
+ if (def->kind != PH_MARKER_ENTITY && def->kind != PH_MARKER_PLAYER)
+ continue;
+ if (def->kind == PH_MARKER_PLAYER && *player >= 0) return -1;
+ entity = ph_world_spawn_entity(world, def->content_id,
+ ph_marker_pos(marker), def->territory_radius, area_id);
+ if (entity < 0) return -1;
+ if (def->kind == PH_MARKER_PLAYER) *player = entity;
+ }
+ return 0;
+}
+
+int
+ph_world_prepare_area(PhWorld *world, const PhArea *area,
+ const PhAreaCatalog *catalog, int area_id, int *player)
+{
+ int entity_count = world->entity_count;
+ int ground_item_count = world->ground_item_count;
+ int old_player = *player;
+ int i;
+
+ for (i = 0; i < world->initialized_area_count; ++i)
+ if (world->initialized_areas[i] == area_id) return 0;
+ if (world->initialized_area_count >= PH_MAX_AREAS ||
+ ph_world_add_area_content(world, area, catalog, area_id, player) < 0) {
+ world->entity_count = entity_count;
+ world->ground_item_count = ground_item_count;
+ *player = old_player;
+ return -1;
+ }
+ world->initialized_areas[world->initialized_area_count++] = area_id;
+ return 0;
+}
+
+int
+ph_world_update_portal(PhWorld *world, const PhAreaCatalog *catalog)
+{
+ const PhEntity *player = ph_world_player(world);
+ int tile_x;
+ int tile_y;
+ int i;
+
+ if (!player) return -1;
+ tile_x = (int)floorf(player->pos.x / PH_TILE_SIZE);
+ tile_y = (int)floorf(player->pos.y / PH_TILE_SIZE);
+ for (i = 0; i < world->area.marker_count; ++i) {
+ const PhAreaMarker *marker = &world->area.markers[i];
+ const PhMarkerDef *portal = ph_area_catalog_marker(catalog, world->area_id,
+ marker->symbol);
+ PhArea area;
+ PhVec2 destination;
+ int player_index = world->player_index;
+ int j;
+
+ if (!portal || portal->kind != PH_MARKER_PORTAL ||
+ marker->tile_x != tile_x || marker->tile_y != tile_y) continue;
+ if (ph_area_catalog_load(&area, catalog, portal->destination_area_id) < 0)
+ return -1;
+ for (j = 0; j < area.marker_count; ++j) {
+ const PhMarkerDef *arrival = ph_area_catalog_marker(catalog,
+ portal->destination_area_id, area.markers[j].symbol);
+
+ if (area.markers[j].symbol == portal->destination_symbol && arrival &&
+ arrival->kind == PH_MARKER_ARRIVAL) break;
+ }
+ if (j == area.marker_count ||
+ ph_world_prepare_area(world, &area, catalog,
+ portal->destination_area_id, &player_index) < 0) {
+ ph_area_free(&area);
+ return -1;
+ }
+ destination = ph_marker_pos(&area.markers[j]);
+ ph_world_enter_area(world, area, portal->destination_area_id, destination);
+ return 1;
+ }
+ return 0;
+}
diff --git a/src/engine/area.h b/src/engine/area.h
@@ -0,0 +1,48 @@
+#ifndef PH_ENGINE_AREA_H
+#define PH_ENGINE_AREA_H
+
+#include "engine/world.h"
+
+typedef struct {
+ int id;
+ const char *path;
+} PhAreaDef;
+
+typedef enum {
+ PH_MARKER_ENTITY,
+ PH_MARKER_PLAYER,
+ PH_MARKER_ITEM,
+ PH_MARKER_PORTAL,
+ PH_MARKER_ARRIVAL,
+} PhMarkerKind;
+
+typedef struct {
+ int area_id;
+ unsigned char symbol;
+ unsigned char tile_symbol;
+ PhMarkerKind kind;
+ int content_id;
+ int amount;
+ int territory_radius;
+ int destination_area_id;
+ unsigned char destination_symbol;
+} PhMarkerDef;
+
+typedef struct {
+ const PhAreaDef *areas;
+ int area_count;
+ const PhMarkerDef *markers;
+ int marker_count;
+ const PhTileDef *tiles;
+ int tile_count;
+} PhAreaCatalog;
+
+const PhAreaDef *ph_area_catalog_area(const PhAreaCatalog *catalog, int area_id);
+const PhMarkerDef *ph_area_catalog_marker(const PhAreaCatalog *catalog,
+ int area_id, unsigned char symbol);
+int ph_area_catalog_load(PhArea *area, const PhAreaCatalog *catalog, int area_id);
+int ph_world_prepare_area(PhWorld *world, const PhArea *area,
+ const PhAreaCatalog *catalog, int area_id, int *player);
+int ph_world_update_portal(PhWorld *world, const PhAreaCatalog *catalog);
+
+#endif
diff --git a/src/engine/render.c b/src/engine/render.c
@@ -0,0 +1,232 @@
+#include "engine/render.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "config.h"
+
+#if PH_USE_SDL
+static float
+ph_world_pixel(float position, float camera)
+{
+ return floorf(position + 0.5f) - floorf(camera + 0.5f);
+}
+
+static SDL_Texture *
+ph_load_sprite_sheet(SDL_Renderer *renderer, const char *path,
+ Uint8 key_r, Uint8 key_g, Uint8 key_b)
+{
+ SDL_Surface *surface;
+ SDL_Texture *texture;
+
+ surface = SDL_LoadBMP(path);
+ if (!surface) {
+ fprintf(stderr, "SDL_LoadBMP failed for %s: %s\n", path, SDL_GetError());
+ return NULL;
+ }
+
+ SDL_SetSurfaceColorKey(surface, true, SDL_MapSurfaceRGB(surface, key_r, key_g, key_b));
+ texture = SDL_CreateTextureFromSurface(renderer, surface);
+ SDL_DestroySurface(surface);
+ if (!texture) {
+ fprintf(stderr, "SDL_CreateTextureFromSurface failed: %s\n", SDL_GetError());
+ }
+ return texture;
+}
+
+int
+ph_render_assets_load(PhRenderAssets *assets, SDL_Renderer *renderer,
+ const PhAssetDef *defs, int def_count)
+{
+ int i;
+
+ memset(assets, 0, sizeof(*assets));
+ if (!renderer || def_count < 0 || def_count > PH_MAX_ASSETS ||
+ (def_count > 0 && !defs)) return -1;
+ assets->texture_count = def_count;
+ for (i = 0; i < def_count; ++i) {
+ if (!defs[i].path) continue;
+ assets->textures[i] = ph_load_sprite_sheet(renderer, defs[i].path,
+ defs[i].key_r, defs[i].key_g, defs[i].key_b);
+ if (!assets->textures[i]) {
+ ph_render_assets_destroy(assets);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+void
+ph_render_assets_destroy(PhRenderAssets *assets)
+{
+ int i;
+
+ for (i = 0; i < assets->texture_count; ++i)
+ SDL_DestroyTexture(assets->textures[i]);
+ memset(assets, 0, sizeof(*assets));
+}
+
+SDL_Texture *
+ph_render_asset(const PhRenderAssets *assets, int asset_id)
+{
+ return asset_id >= 0 && asset_id < assets->texture_count ?
+ assets->textures[asset_id] : NULL;
+}
+
+void
+ph_render_sprite(SDL_Renderer *renderer, SDL_Texture *texture,
+ int sprite_tile_x, int sprite_tile_y, SDL_FRect dst, SDL_FlipMode flip)
+{
+ SDL_FRect src = {
+ .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,
+ };
+
+ SDL_RenderTextureRotated(renderer, texture, &src, &dst, 0.0, NULL, flip);
+}
+
+void
+ph_render_entity_frame(const PhEntityDef *def, const PhEntity *entity,
+ int *sprite_tile_x, int *sprite_tile_y, SDL_FlipMode *flip)
+{
+ int direction = entity->facing_y > 0 ? PH_SPRITE_DOWN :
+ entity->facing_y < 0 ? PH_SPRITE_UP : PH_SPRITE_SIDE;
+ int frame = ph_entity_animation_frame(def, entity);
+
+ *sprite_tile_x = def->sprite_tiles[direction][frame][0];
+ *sprite_tile_y = def->sprite_tiles[direction][frame][1];
+ *flip = direction == PH_SPRITE_SIDE && entity->facing_x > 0 ?
+ SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
+}
+
+static void
+ph_draw_area(SDL_Renderer *renderer, const PhRenderAssets *assets, const PhWorld *world)
+{
+ int tx;
+ int ty;
+
+ for (ty = (int)(world->camera.pos.y / PH_TILE_SIZE);
+ ty < world->area.height && (float)(ty * PH_TILE_SIZE) <
+ world->camera.pos.y + world->camera.viewport_h; ++ty) {
+ for (tx = (int)(world->camera.pos.x / PH_TILE_SIZE);
+ tx < world->area.width && (float)(tx * PH_TILE_SIZE) <
+ world->camera.pos.x + world->camera.viewport_w; ++tx) {
+ const PhTileDef *def = ph_area_tile_def(&world->area, tx, ty);
+ SDL_Texture *texture;
+ SDL_FRect rect = {
+ .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,
+ };
+ SDL_FRect src;
+
+ if (!def) continue;
+ texture = ph_render_asset(assets, def->asset_id);
+ if (!texture) continue;
+ src = (SDL_FRect){
+ .x = (float)(def->sprite_tile_x * PH_TILE_SIZE),
+ .y = (float)(def->sprite_tile_y * PH_TILE_SIZE),
+ .w = (float)PH_TILE_SIZE,
+ .h = (float)PH_TILE_SIZE,
+ };
+
+ SDL_RenderTexture(renderer, texture, &src, &rect);
+ }
+ }
+}
+
+static void
+ph_draw_items(SDL_Renderer *renderer, const PhRenderAssets *assets, const PhWorld *world)
+{
+ int i;
+
+ for (i = 0; i < world->ground_item_count; ++i) {
+ const PhGroundItem *drop = &world->ground_items[i];
+ const PhItemDef *def = ph_world_item_def(world, drop->item_id);
+ SDL_Texture *texture = NULL;
+ SDL_FRect rect;
+
+ if (!drop->active || drop->area_id != world->area_id) {
+ continue;
+ }
+
+ 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;
+
+ if (def) texture = ph_render_asset(assets, def->asset_id);
+ if (texture && def->sprite_tile_x >= 0 && def->sprite_tile_y >= 0) {
+ 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_render_sprite(renderer, texture,
+ def->sprite_tile_x, def->sprite_tile_y, rect, SDL_FLIP_NONE);
+ } else {
+ SDL_SetRenderDrawColor(renderer, PH_ITEM_COLOR);
+ SDL_RenderFillRect(renderer, &rect);
+ }
+ }
+}
+
+static void
+ph_draw_entities(SDL_Renderer *renderer, const PhRenderAssets *assets, const PhWorld *world)
+{
+ int i;
+
+ for (i = 0; i < world->entity_count; ++i) {
+ const PhEntity *entity = &world->entities[i];
+ const PhEntityDef *def = ph_world_entity_def(world, entity->type_id);
+ SDL_Texture *texture = NULL;
+ SDL_FRect rect;
+ int sprite_tile_x;
+ int sprite_tile_y;
+ SDL_FlipMode flip;
+
+ if (!entity->active || entity->area_id != world->area_id || !def) {
+ continue;
+ }
+
+ texture = ph_render_asset(assets, def->asset_id);
+ ph_render_entity_frame(def, entity, &sprite_tile_x, &sprite_tile_y, &flip);
+ if (texture && sprite_tile_x >= 0 && sprite_tile_y >= 0) {
+
+ 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_render_sprite(renderer, texture, sprite_tile_x, sprite_tile_y, rect, flip);
+ } else {
+ 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;
+
+ if (def->kind == PH_ENTITY_MONSTER) {
+ SDL_SetRenderDrawColor(renderer, PH_MONSTER_COLOR);
+ } else {
+ SDL_SetRenderDrawColor(renderer, PH_NPC_COLOR);
+ }
+ SDL_RenderFillRect(renderer, &rect);
+ }
+ }
+}
+void
+ph_render_world(SDL_Renderer *renderer, const PhRenderAssets *assets,
+ const PhWorld *world)
+{
+ ph_draw_area(renderer, assets, world);
+ ph_draw_items(renderer, assets, world);
+ ph_draw_entities(renderer, assets, world);
+}
+#endif
diff --git a/src/engine/render.h b/src/engine/render.h
@@ -0,0 +1,36 @@
+#ifndef PH_ENGINE_RENDER_H
+#define PH_ENGINE_RENDER_H
+
+#include "engine/world.h"
+
+enum { PH_MAX_ASSETS = 32 };
+
+typedef struct {
+ const char *path;
+ unsigned char key_r;
+ unsigned char key_g;
+ unsigned char key_b;
+} PhAssetDef;
+
+#if PH_USE_SDL
+#include <SDL3/SDL.h>
+
+typedef struct {
+ SDL_Texture *textures[PH_MAX_ASSETS];
+ int texture_count;
+} PhRenderAssets;
+
+int ph_render_assets_load(PhRenderAssets *assets, SDL_Renderer *renderer,
+ const PhAssetDef *defs, int def_count);
+void ph_render_assets_destroy(PhRenderAssets *assets);
+SDL_Texture *ph_render_asset(const PhRenderAssets *assets, int asset_id);
+void ph_render_sprite(SDL_Renderer *renderer, SDL_Texture *texture,
+ int sprite_tile_x, int sprite_tile_y, SDL_FRect dst, SDL_FlipMode flip);
+void ph_render_entity_frame(const PhEntityDef *def, const PhEntity *entity,
+ int *sprite_tile_x, int *sprite_tile_y, SDL_FlipMode *flip);
+void ph_render_world(SDL_Renderer *renderer, const PhRenderAssets *assets,
+ const PhWorld *world);
+
+#endif
+
+#endif
diff --git a/src/game/area.c b/src/game/area.c
@@ -1,118 +1,10 @@
#include "game/area.h"
-#include <math.h>
+#include "engine/area.h"
+#include "game/content.h"
#define LEN(a) (sizeof(a) / sizeof(0[a]))
-const PhAreaDef *
-ph_game_area_def(int area_id)
-{
- size_t i;
-
- for (i = 0; i < LEN(ph_area_defs); ++i)
- if (ph_area_defs[i].id == area_id) return &ph_area_defs[i];
- return NULL;
-}
-
-const PhShopDef *
-ph_game_shop_def(int shop_id)
-{
- size_t i;
-
- for (i = 0; i < LEN(ph_shop_defs); ++i)
- if (ph_shop_defs[i].id == shop_id) return &ph_shop_defs[i];
- return NULL;
-}
-
-const PhMarkerDef *
-ph_game_marker_def(int area_id, unsigned char symbol)
-{
- size_t i;
-
- for (i = 0; i < LEN(ph_marker_defs); ++i)
- if (ph_marker_defs[i].area_id == area_id &&
- ph_marker_defs[i].symbol == symbol) return &ph_marker_defs[i];
- return NULL;
-}
-
-const PhInteractionDef *
-ph_game_interaction_def(int interaction_id)
-{
- size_t i;
-
- for (i = 0; i < LEN(ph_interaction_defs); ++i)
- if (ph_interaction_defs[i].id == interaction_id)
- return &ph_interaction_defs[i];
- return NULL;
-}
-
-static int
-ph_game_load_area(PhArea *area, int area_id)
-{
- const PhAreaDef *def = ph_game_area_def(area_id);
-
- if (!def || ph_area_load(area, def->path) < 0) return -1;
- area->tile_defs = ph_tile_defs;
- area->tile_def_count = (int)LEN(ph_tile_defs);
- return 0;
-}
-
-static PhVec2
-ph_marker_pos(const PhAreaMarker *marker)
-{
- return (PhVec2){ PH_TILE_CENTER(marker->tile_x),
- PH_TILE_CENTER(marker->tile_y) };
-}
-
-static int
-ph_game_add_area_content(PhWorld *world, const PhArea *area, int area_id,
- int *player)
-{
- int i;
-
- for (i = 0; i < area->marker_count; ++i) {
- const PhAreaMarker *marker = &area->markers[i];
- const PhMarkerDef *def = ph_game_marker_def(area_id, marker->symbol);
- int entity;
-
- if (!def) return -1;
- if (def->kind == PH_MARKER_ITEM) {
- if (ph_world_drop_item(world, def->content_id, ph_marker_pos(marker),
- def->amount, area_id) < 0) return -1;
- continue;
- }
- if (def->kind != PH_MARKER_ENTITY && def->kind != PH_MARKER_PLAYER)
- continue;
- if (def->kind == PH_MARKER_PLAYER && *player >= 0) return -1;
- entity = ph_world_spawn_entity(world, def->content_id,
- ph_marker_pos(marker), def->territory_radius, area_id);
- if (entity < 0) return -1;
- if (def->kind == PH_MARKER_PLAYER) *player = entity;
- }
- return 0;
-}
-
-static int
-ph_game_prepare_area(PhWorld *world, const PhArea *area, int area_id, int *player)
-{
- int entity_count = world->entity_count;
- int ground_item_count = world->ground_item_count;
- int old_player = *player;
- int i;
-
- for (i = 0; i < world->initialized_area_count; ++i)
- if (world->initialized_areas[i] == area_id) return 0;
- if (world->initialized_area_count >= PH_MAX_AREAS ||
- ph_game_add_area_content(world, area, area_id, player) < 0) {
- world->entity_count = entity_count;
- world->ground_item_count = ground_item_count;
- *player = old_player;
- return -1;
- }
- world->initialized_areas[world->initialized_area_count++] = area_id;
- return 0;
-}
-
int
ph_game_world_init(PhWorld *world, float viewport_w, float viewport_h)
{
@@ -120,15 +12,15 @@ ph_game_world_init(PhWorld *world, float viewport_w, float viewport_h)
int player = -1;
size_t i;
- if (ph_game_load_area(&area, PH_START_AREA) < 0) return -1;
+ if (ph_area_catalog_load(&area, &ph_area_catalog, PH_START_AREA) < 0)
+ return -1;
ph_world_init(world, area, PH_START_AREA, viewport_w, viewport_h);
for (i = 0; i < LEN(ph_entity_defs); ++i)
if (ph_world_add_entity_def(world, ph_entity_defs[i]) < 0) goto fail;
for (i = 0; i < LEN(ph_item_defs); ++i)
if (ph_world_add_item_def(world, ph_item_defs[i]) < 0) goto fail;
- if (ph_game_prepare_area(world, &world->area, PH_START_AREA, &player) < 0)
- goto fail;
- if (player < 0) goto fail;
+ if (ph_world_prepare_area(world, &world->area, &ph_area_catalog,
+ PH_START_AREA, &player) < 0 || player < 0) goto fail;
for (i = 0; i < LEN(ph_player_equipment); ++i)
if (ph_player_equipment[i] &&
ph_world_equip_item(world, player, ph_player_equipment[i]) < 0)
@@ -140,50 +32,3 @@ fail:
ph_area_free(&world->area);
return -1;
}
-
-int
-ph_game_update_portal(PhWorld *world)
-{
- const PhEntity *player = ph_world_player(world);
- int tile_x;
- int tile_y;
- int i;
-
- if (!player) return -1;
- tile_x = (int)floorf(player->pos.x / PH_TILE_SIZE);
- tile_y = (int)floorf(player->pos.y / PH_TILE_SIZE);
- for (i = 0; i < world->area.marker_count; ++i) {
- const PhAreaMarker *marker = &world->area.markers[i];
- const PhMarkerDef *portal = ph_game_marker_def(world->area_id,
- marker->symbol);
- PhArea area;
- PhVec2 destination;
- int player_index = world->player_index;
- int j;
-
- if (!portal || portal->kind != PH_MARKER_PORTAL ||
- marker->tile_x != tile_x || marker->tile_y != tile_y) continue;
- if (ph_game_load_area(&area, portal->destination_area_id) < 0) return -1;
- for (j = 0; j < area.marker_count; ++j) {
- const PhMarkerDef *arrival = ph_game_marker_def(
- portal->destination_area_id, area.markers[j].symbol);
-
- if (area.markers[j].symbol == portal->destination_symbol && arrival &&
- arrival->kind == PH_MARKER_ARRIVAL) break;
- }
- if (j == area.marker_count) {
- ph_area_free(&area);
- return -1;
- }
- if (ph_game_prepare_area(world, &area, portal->destination_area_id,
- &player_index) < 0) {
- ph_area_free(&area);
- return -1;
- }
- destination = ph_marker_pos(&area.markers[j]);
- ph_world_enter_area(world, area, portal->destination_area_id,
- destination);
- return 1;
- }
- return 0;
-}
diff --git a/src/game/area.h b/src/game/area.h
@@ -1,13 +1,8 @@
#ifndef PH_GAME_AREA_H
#define PH_GAME_AREA_H
-#include "game/content.h"
+#include "engine/world.h"
int ph_game_world_init(PhWorld *world, float viewport_w, float viewport_h);
-int ph_game_update_portal(PhWorld *world);
-const PhAreaDef *ph_game_area_def(int area_id);
-const PhMarkerDef *ph_game_marker_def(int area_id, unsigned char symbol);
-const PhShopDef *ph_game_shop_def(int shop_id);
-const PhInteractionDef *ph_game_interaction_def(int interaction_id);
#endif
diff --git a/src/game/content.c b/src/game/content.c
@@ -182,6 +182,12 @@ const PhMarkerDef ph_marker_defs[PH_MARKER_DEF_COUNT] = {
{ PH_AREA_STONEHOLLOW, 'e', ',', PH_MARKER_ARRIVAL, .content_id = 0 },
};
+const PhAreaCatalog ph_area_catalog = {
+ ph_area_defs, PH_AREA_COUNT,
+ ph_marker_defs, PH_MARKER_DEF_COUNT,
+ ph_tile_defs, PH_TILE_DEF_COUNT,
+};
+
const PhShopDef ph_shop_defs[PH_SHOP_COUNT] = {
{
.id = PH_SHOP_STONEHOLLOW,
@@ -198,3 +204,24 @@ const PhShopDef ph_shop_defs[PH_SHOP_COUNT] = {
const PhInteractionDef ph_interaction_defs[PH_INTERACTION_COUNT] = {
{ PH_INTERACTION_MIRA, PH_INTERACTION_SHOP, PH_SHOP_STONEHOLLOW },
};
+
+const PhShopDef *
+ph_game_shop_def(int shop_id)
+{
+ int i;
+
+ for (i = 0; i < PH_SHOP_COUNT; ++i)
+ if (ph_shop_defs[i].id == shop_id) return &ph_shop_defs[i];
+ return NULL;
+}
+
+const PhInteractionDef *
+ph_game_interaction_def(int interaction_id)
+{
+ int i;
+
+ for (i = 0; i < PH_INTERACTION_COUNT; ++i)
+ if (ph_interaction_defs[i].id == interaction_id)
+ return &ph_interaction_defs[i];
+ return NULL;
+}
diff --git a/src/game/content.h b/src/game/content.h
@@ -1,41 +1,11 @@
#ifndef PH_GAME_CONTENT_H
#define PH_GAME_CONTENT_H
+#include "engine/area.h"
#include "engine/battle.h"
+#include "engine/render.h"
#include "engine/world.h"
-typedef struct {
- const char *path;
- unsigned char key_r;
- unsigned char key_g;
- unsigned char key_b;
-} PhAssetDef;
-
-typedef struct {
- int id;
- const char *path;
-} PhAreaDef;
-
-typedef enum {
- PH_MARKER_ENTITY,
- PH_MARKER_PLAYER,
- PH_MARKER_ITEM,
- PH_MARKER_PORTAL,
- PH_MARKER_ARRIVAL,
-} PhMarkerKind;
-
-typedef struct {
- int area_id;
- unsigned char symbol;
- unsigned char tile_symbol;
- PhMarkerKind kind;
- int content_id;
- int amount;
- int territory_radius;
- int destination_area_id;
- unsigned char destination_symbol;
-} PhMarkerDef;
-
enum { PH_SHOP_ITEM_MAX = 8, PH_SHOP_TALK_LINES = 2 };
typedef struct {
@@ -117,7 +87,11 @@ extern const int ph_player_spells[PH_PLAYER_SPELL_COUNT];
extern const PhSpellBook ph_player_spellbook;
extern const int ph_player_equipment[PH_EQUIP_COUNT];
extern const PhMarkerDef ph_marker_defs[PH_MARKER_DEF_COUNT];
+extern const PhAreaCatalog ph_area_catalog;
extern const PhShopDef ph_shop_defs[PH_SHOP_COUNT];
extern const PhInteractionDef ph_interaction_defs[PH_INTERACTION_COUNT];
+const PhShopDef *ph_game_shop_def(int shop_id);
+const PhInteractionDef *ph_game_interaction_def(int interaction_id);
+
#endif
diff --git a/src/game/main.c b/src/game/main.c
@@ -1,4 +1,5 @@
#include "engine/battle.h"
+#include "engine/area.h"
#include "game/area.h"
#include "game/content.h"
#include "game/presentation.h"
@@ -28,7 +29,7 @@ ph_update_game(PhGameContext *context, PhInput input, float dt)
if (*game == PH_GAME_WORLD && context->menu == PH_MENU_NONE) {
ph_world_tick(world, input, dt);
- if (ph_game_update_portal(world) < 0)
+ if (ph_world_update_portal(world, &ph_area_catalog) < 0)
fprintf(stderr, "failed to enter area\n");
if (world->encounter_index >= 0) {
ph_battle_begin(context->battle, world, world->encounter_index,
@@ -88,7 +89,7 @@ ph_run_game(void)
{
SDL_Window *window;
SDL_Renderer *renderer;
- PhAssets assets;
+ PhRenderAssets assets;
PhWorld world;
PhInventoryUi inventory_ui = { 0 };
PhShopUi shop_ui = { 0 };
@@ -113,7 +114,8 @@ ph_run_game(void)
SDL_Quit();
return 1;
}
- if (ph_load_assets(&assets, renderer) < 0) {
+ if (ph_render_assets_load(&assets, renderer,
+ ph_asset_defs, PH_ASSET_COUNT) < 0) {
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
ph_area_free(&world.area);
@@ -207,7 +209,7 @@ ph_run_game(void)
SDL_Delay(PH_FRAME_DELAY_MS);
}
- ph_destroy_assets(&assets);
+ ph_render_assets_destroy(&assets);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
ph_area_free(&world.area);
diff --git a/src/game/presentation.c b/src/game/presentation.c
@@ -1,8 +1,5 @@
#include "game/presentation.h"
-#include "game/area.h"
-
-#include <math.h>
#include <stdio.h>
#include <string.h>
@@ -32,12 +29,6 @@ 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)
{
@@ -70,209 +61,6 @@ ph_draw_wrapped_text(SDL_Renderer *renderer, float x, float y,
#endif
#if PH_USE_SDL
-static SDL_Texture *
-ph_load_sprite_sheet(SDL_Renderer *renderer, const char *path,
- Uint8 key_r, Uint8 key_g, Uint8 key_b)
-{
- SDL_Surface *surface;
- SDL_Texture *texture;
-
- surface = SDL_LoadBMP(path);
- if (!surface) {
- fprintf(stderr, "SDL_LoadBMP failed for %s: %s\n", path, SDL_GetError());
- return NULL;
- }
-
- SDL_SetSurfaceColorKey(surface, true, SDL_MapSurfaceRGB(surface, key_r, key_g, key_b));
- texture = SDL_CreateTextureFromSurface(renderer, surface);
- SDL_DestroySurface(surface);
- if (!texture) {
- fprintf(stderr, "SDL_CreateTextureFromSurface failed: %s\n", SDL_GetError());
- }
- return texture;
-}
-
-int
-ph_load_assets(PhAssets *assets, SDL_Renderer *renderer)
-{
- size_t i;
-
- memset(assets, 0, sizeof(*assets));
- for (i = 1; i < LEN(ph_asset_defs); ++i) {
- if (!ph_asset_defs[i].path) continue;
- assets->textures[i] = ph_load_sprite_sheet(renderer, ph_asset_defs[i].path,
- ph_asset_defs[i].key_r, ph_asset_defs[i].key_g, ph_asset_defs[i].key_b);
- if (!assets->textures[i]) {
- while (i > 0) SDL_DestroyTexture(assets->textures[--i]);
- return -1;
- }
- }
- return assets->textures[PH_ASSET_TILESET] ? 0 : -1;
-}
-
-void
-ph_destroy_assets(PhAssets *assets)
-{
- size_t i;
-
- for (i = 1; i < LEN(assets->textures); ++i)
- SDL_DestroyTexture(assets->textures[i]);
- memset(assets, 0, sizeof(*assets));
-}
-
-static void
-ph_draw_sprite(SDL_Renderer *renderer, SDL_Texture *texture,
- int sprite_tile_x, int sprite_tile_y, SDL_FRect dst, SDL_FlipMode flip)
-{
- SDL_FRect src = {
- .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,
- };
-
- SDL_RenderTextureRotated(renderer, texture, &src, &dst, 0.0, NULL, flip);
-}
-
-static void
-ph_entity_sprite_frame(const PhEntityDef *def, const PhEntity *entity,
- int *sprite_tile_x, int *sprite_tile_y, SDL_FlipMode *flip)
-{
- int direction = entity->facing_y > 0 ? PH_SPRITE_DOWN :
- entity->facing_y < 0 ? PH_SPRITE_UP : PH_SPRITE_SIDE;
- int frame = ph_entity_animation_frame(def, entity);
-
- *sprite_tile_x = def->sprite_tiles[direction][frame][0];
- *sprite_tile_y = def->sprite_tiles[direction][frame][1];
- *flip = direction == PH_SPRITE_SIDE && entity->facing_x > 0 ?
- SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
-}
-
-static void
-ph_draw_area(SDL_Renderer *renderer, const PhAssets *assets, const PhWorld *world)
-{
- int tx;
- int ty;
-
- for (ty = (int)(world->camera.pos.y / PH_TILE_SIZE);
- ty < world->area.height && (float)(ty * PH_TILE_SIZE) <
- world->camera.pos.y + world->camera.viewport_h; ++ty) {
- for (tx = (int)(world->camera.pos.x / PH_TILE_SIZE);
- tx < world->area.width && (float)(tx * PH_TILE_SIZE) <
- world->camera.pos.x + world->camera.viewport_w; ++tx) {
- const PhTileDef *def = ph_area_tile_def(&world->area, tx, ty);
- SDL_Texture *texture;
- SDL_FRect rect = {
- .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,
- };
- SDL_FRect src;
-
- if (!def || def->asset_id <= PH_ASSET_NONE ||
- (size_t)def->asset_id >= LEN(assets->textures)) continue;
- texture = assets->textures[def->asset_id];
- if (!texture) continue;
- src = (SDL_FRect){
- .x = (float)(def->sprite_tile_x * PH_TILE_SIZE),
- .y = (float)(def->sprite_tile_y * PH_TILE_SIZE),
- .w = (float)PH_TILE_SIZE,
- .h = (float)PH_TILE_SIZE,
- };
-
- SDL_RenderTexture(renderer, texture, &src, &rect);
- }
- }
-}
-
-static void
-ph_draw_items(SDL_Renderer *renderer, const PhAssets *assets, const PhWorld *world)
-{
- int i;
-
- for (i = 0; i < world->ground_item_count; ++i) {
- const PhGroundItem *drop = &world->ground_items[i];
- const PhItemDef *def = ph_world_item_def(world, drop->item_id);
- SDL_Texture *texture = NULL;
- SDL_FRect rect;
-
- if (!drop->active || drop->area_id != world->area_id) {
- continue;
- }
-
- 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;
-
- if (def && def->asset_id > PH_ASSET_NONE &&
- (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 = 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,
- def->sprite_tile_x, def->sprite_tile_y, rect, SDL_FLIP_NONE);
- } else {
- SDL_SetRenderDrawColor(renderer, PH_ITEM_COLOR);
- SDL_RenderFillRect(renderer, &rect);
- }
- }
-}
-
-static void
-ph_draw_entities(SDL_Renderer *renderer, const PhAssets *assets, const PhWorld *world)
-{
- int i;
-
- for (i = 0; i < world->entity_count; ++i) {
- const PhEntity *entity = &world->entities[i];
- const PhEntityDef *def = ph_world_entity_def(world, entity->type_id);
- SDL_Texture *texture = NULL;
- SDL_FRect rect;
- int sprite_tile_x;
- int sprite_tile_y;
- SDL_FlipMode flip;
-
- if (!entity->active || entity->area_id != world->area_id || !def) {
- continue;
- }
-
- if (def->asset_id > PH_ASSET_NONE &&
- (size_t)def->asset_id < LEN(assets->textures))
- texture = assets->textures[def->asset_id];
- 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 = 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 = 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;
-
- if (def->kind == PH_ENTITY_MONSTER) {
- SDL_SetRenderDrawColor(renderer, PH_MONSTER_COLOR);
- } else {
- SDL_SetRenderDrawColor(renderer, PH_NPC_COLOR);
- }
- SDL_RenderFillRect(renderer, &rect);
- }
- }
-}
-
static void
ph_append_bonus(char *text, size_t size, size_t *used, int value, const char *label)
{
@@ -322,7 +110,7 @@ ph_draw_menu(SDL_Renderer *renderer, const char *title, SDL_Scancode key)
}
static void
-ph_draw_character_sheet(SDL_Renderer *renderer, const PhAssets *assets,
+ph_draw_character_sheet(SDL_Renderer *renderer, const PhRenderAssets *assets,
const PhWorld *world)
{
static const char *slot_names[PH_EQUIP_COUNT] = {
@@ -333,6 +121,7 @@ ph_draw_character_sheet(SDL_Renderer *renderer, const PhAssets *assets,
const PhEntity *player = ph_world_player(world);
const PhEntityDef *def;
PhStats stats;
+ SDL_Texture *texture;
SDL_FRect avatar = { 32.0f, 46.0f, PH_TILE_SIZE * 3.0f,
PH_TILE_SIZE * 3.0f };
char text[128];
@@ -343,8 +132,9 @@ ph_draw_character_sheet(SDL_Renderer *renderer, const PhAssets *assets,
ph_draw_menu(renderer, "CHARACTER", PH_KEY_CHARACTER);
SDL_RenderDebugText(renderer, 104.0f, 38.0f, def->name);
- if (def->asset_id > PH_ASSET_NONE && (size_t)def->asset_id < LEN(assets->textures))
- ph_draw_sprite(renderer, assets->textures[def->asset_id],
+ texture = ph_render_asset(assets, def->asset_id);
+ if (texture)
+ ph_render_sprite(renderer, texture,
def->sprite_tiles[PH_SPRITE_DOWN][0][0],
def->sprite_tiles[PH_SPRITE_DOWN][0][1],
avatar, SDL_FLIP_NONE);
@@ -373,16 +163,17 @@ ph_draw_character_sheet(SDL_Renderer *renderer, const PhAssets *assets,
for (slot = PH_EQUIP_WEAPON; slot < PH_EQUIP_COUNT; ++slot) {
const PhItemDef *item = ph_world_item_def(world, player->equipment[slot]);
+ SDL_Texture *icon_texture;
float y = 144.0f + (float)(slot - PH_EQUIP_WEAPON) * 25.0f;
snprintf(text, sizeof(text), "%s %s", slot_names[slot], item ? item->name : "-");
SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR);
SDL_RenderDebugText(renderer, 48.0f, y, text);
if (!item) continue;
- if (item->asset_id > PH_ASSET_NONE &&
- (size_t)item->asset_id < LEN(assets->textures)) {
+ icon_texture = ph_render_asset(assets, item->asset_id);
+ if (icon_texture) {
SDL_FRect icon = { 24.0f, y - 4.0f, PH_TILE_SIZE, PH_TILE_SIZE };
- ph_draw_sprite(renderer, assets->textures[item->asset_id],
+ ph_render_sprite(renderer, icon_texture,
item->sprite_tile_x, item->sprite_tile_y, icon, SDL_FLIP_NONE);
}
ph_format_bonuses(text, sizeof(text), item->bonuses);
@@ -555,7 +346,7 @@ ph_draw_inventory_popup(SDL_Renderer *renderer, const PhWorld *world,
}
static void
-ph_draw_inventory(SDL_Renderer *renderer, const PhAssets *assets,
+ph_draw_inventory(SDL_Renderer *renderer, const PhRenderAssets *assets,
const PhWorld *world, const PhInventoryUi *ui)
{
char text[128];
@@ -574,6 +365,7 @@ ph_draw_inventory(SDL_Renderer *renderer, const PhAssets *assets,
ph_draw_menu(renderer, "INVENTORY", PH_KEY_INVENTORY);
for (i = 0; i < world->item_def_count; ++i) {
const PhItemDef *item = &world->item_defs[i];
+ SDL_Texture *texture;
float y;
if (world->inventory[i] <= 0) continue;
@@ -584,10 +376,10 @@ ph_draw_inventory(SDL_Renderer *renderer, const PhAssets *assets,
y = 48.0f + (float)(rank++ - page * 5) * 27.0f;
SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR);
SDL_RenderDebugText(renderer, 20.0f, y, i == ui->selected ? ">" : " ");
- if (item->asset_id > PH_ASSET_NONE &&
- (size_t)item->asset_id < LEN(assets->textures)) {
+ texture = ph_render_asset(assets, item->asset_id);
+ if (texture) {
SDL_FRect icon = { 32.0f, y - 4.0f, PH_TILE_SIZE, PH_TILE_SIZE };
- ph_draw_sprite(renderer, assets->textures[item->asset_id],
+ ph_render_sprite(renderer, texture,
item->sprite_tile_x, item->sprite_tile_y, icon, SDL_FLIP_NONE);
}
snprintf(text, sizeof(text), "%s x%d",
@@ -804,7 +596,7 @@ ph_draw_shop(SDL_Renderer *renderer, const PhWorld *world, const PhShopUi *ui)
}
static void
-ph_draw_battle_entity(SDL_Renderer *renderer, const PhAssets *assets,
+ph_draw_battle_entity(SDL_Renderer *renderer, const PhRenderAssets *assets,
const PhWorld *world, int entity_index, SDL_FRect rect)
{
const PhEntity *entity = &world->entities[entity_index];
@@ -815,16 +607,15 @@ ph_draw_battle_entity(SDL_Renderer *renderer, const PhAssets *assets,
SDL_FlipMode flip;
if (!def) return;
- if (def->asset_id > PH_ASSET_NONE && (size_t)def->asset_id < LEN(assets->textures))
- texture = assets->textures[def->asset_id];
+ texture = ph_render_asset(assets, def->asset_id);
if (def->kind == PH_ENTITY_PLAYER) {
x = def->sprite_tiles[PH_SPRITE_SIDE][0][0];
y = def->sprite_tiles[PH_SPRITE_SIDE][0][1];
flip = SDL_FLIP_NONE;
} else {
- ph_entity_sprite_frame(def, entity, &x, &y, &flip);
+ ph_render_entity_frame(def, entity, &x, &y, &flip);
}
- if (texture) ph_draw_sprite(renderer, texture, x, y, rect, flip);
+ if (texture) ph_render_sprite(renderer, texture, x, y, rect, flip);
else {
if (def->kind == PH_ENTITY_MONSTER)
SDL_SetRenderDrawColor(renderer, PH_MONSTER_COLOR);
@@ -835,7 +626,7 @@ ph_draw_battle_entity(SDL_Renderer *renderer, const PhAssets *assets,
}
static void
-ph_draw_battle(SDL_Renderer *renderer, const PhAssets *assets,
+ph_draw_battle(SDL_Renderer *renderer, const PhRenderAssets *assets,
const PhWorld *world, const PhBattle *battle)
{
static const char *commands[PH_BATTLE_COMMAND_COUNT] = {
@@ -962,7 +753,7 @@ ph_draw_battle_end(SDL_Renderer *renderer, const PhWorld *world,
}
void
-ph_present_frame(SDL_Renderer *renderer, const PhAssets *assets,
+ph_present_frame(SDL_Renderer *renderer, const PhRenderAssets *assets,
const PhGameContext *context)
{
const PhWorld *world = context->world;
@@ -982,9 +773,7 @@ ph_present_frame(SDL_Renderer *renderer, const PhAssets *assets,
SDL_SetRenderDrawColor(renderer, PH_CLEAR_COLOR);
SDL_RenderClear(renderer);
- ph_draw_area(renderer, assets, world);
- ph_draw_items(renderer, assets, world);
- ph_draw_entities(renderer, assets, world);
+ ph_render_world(renderer, assets, world);
if (context->menu == PH_MENU_CHARACTER) {
ph_draw_character_sheet(renderer, assets, world);
} else if (context->menu == PH_MENU_INVENTORY) {
diff --git a/src/game/presentation.h b/src/game/presentation.h
@@ -2,15 +2,12 @@
#define PH_GAME_PRESENTATION_H
#include "engine/battle.h"
+#include "engine/render.h"
#include "game/content.h"
#if PH_USE_SDL
#include <SDL3/SDL.h>
-typedef struct {
- SDL_Texture *textures[PH_ASSET_COUNT];
-} PhAssets;
-
typedef enum {
PH_MENU_NONE,
PH_MENU_CHARACTER,
@@ -70,15 +67,13 @@ typedef struct {
PhMenu menu;
} PhGameContext;
-int ph_load_assets(PhAssets *assets, SDL_Renderer *renderer);
-void ph_destroy_assets(PhAssets *assets);
int ph_inventory_step(const PhWorld *world, int selected, int direction);
void ph_inventory_open(PhInventoryUi *ui, const PhWorld *world);
int ph_inventory_key(PhInventoryUi *ui, PhWorld *world, SDL_Scancode key);
void ph_shop_open(PhShopUi *ui, int shop_id);
PhMenu ph_interaction_open(PhShopUi *shop_ui, int interaction_id);
int ph_shop_key(PhShopUi *ui, PhWorld *world, SDL_Scancode key);
-void ph_present_frame(SDL_Renderer *renderer, const PhAssets *assets,
+void ph_present_frame(SDL_Renderer *renderer, const PhRenderAssets *assets,
const PhGameContext *context);
#endif
diff --git a/src/tests/render.c b/src/tests/render.c
@@ -1,4 +1,5 @@
#include "engine/battle.h"
+#include "engine/area.h"
#include "game/area.h"
#include "game/content.h"
#include "game/presentation.h"
@@ -13,7 +14,7 @@ main(void)
{
SDL_Surface *surface;
SDL_Renderer *renderer;
- PhAssets assets;
+ PhRenderAssets assets;
PhWorld world;
PhInventoryUi inventory_ui;
PhShopUi shop_ui = { 0 };
@@ -66,7 +67,8 @@ main(void)
first_item = world.ground_items[0].item_id;
second_item = world.ground_items[1].item_id;
- if (ph_load_assets(&assets, renderer) < 0) {
+ if (ph_render_assets_load(&assets, renderer,
+ ph_asset_defs, PH_ASSET_COUNT) < 0) {
ph_area_free(&world.area);
SDL_DestroyRenderer(renderer);
SDL_DestroySurface(surface);
@@ -224,8 +226,8 @@ main(void)
}
game = PH_GAME_WORLD;
for (i = 0; i < world.area.marker_count; ++i) {
- const PhMarkerDef *def = ph_game_marker_def(world.area_id,
- world.area.markers[i].symbol);
+ const PhMarkerDef *def = ph_area_catalog_marker(&ph_area_catalog,
+ world.area_id, world.area.markers[i].symbol);
if (!def || def->kind != PH_MARKER_PORTAL) continue;
world.entities[world.player_index].pos = (PhVec2){
@@ -233,7 +235,8 @@ main(void)
PH_TILE_CENTER(world.area.markers[i].tile_y) };
break;
}
- if (i == world.area.marker_count || ph_game_update_portal(&world) != 1) {
+ if (i == world.area.marker_count ||
+ ph_world_update_portal(&world, &ph_area_catalog) != 1) {
fprintf(stderr, "render smoke test failed: town portal\n");
result = 1;
}
@@ -247,7 +250,7 @@ main(void)
result = 1;
}
- ph_destroy_assets(&assets);
+ ph_render_assets_destroy(&assets);
ph_area_free(&world.area);
SDL_DestroyRenderer(renderer);
SDL_DestroySurface(surface);
diff --git a/src/tests/smoke.c b/src/tests/smoke.c
@@ -1,3 +1,4 @@
+#include "engine/area.h"
#include "engine/world.h"
#include "engine/battle.h"
#include "game/area.h"
@@ -10,6 +11,57 @@
#define LEN(a) (sizeof(a) / sizeof(0[a]))
static int
+ph_area_catalog_test(void)
+{
+ unsigned char tiles[] = { 0, 0, 0, 0 };
+ PhAreaMarker markers[] = {
+ { '@', 0, 0 },
+ { 'a', 1, 0 },
+ { '1', 0, 1 },
+ };
+ const PhMarkerDef marker_defs[] = {
+ { 7, '@', '.', PH_MARKER_PLAYER, 10, 0, 0, 0, 0 },
+ { 7, 'a', '.', PH_MARKER_ENTITY, 20, 0, 3, 0, 0 },
+ { 7, '1', '.', PH_MARKER_ITEM, 30, 2, 0, 0, 0 },
+ };
+ const PhAreaCatalog catalog = {
+ .markers = marker_defs,
+ .marker_count = (int)LEN(marker_defs),
+ };
+ PhArea area = {
+ .name = "Catalog",
+ .width = 2,
+ .height = 2,
+ .tiles = tiles,
+ .markers = markers,
+ .marker_count = (int)LEN(markers),
+ };
+ PhWorld world;
+ int player = -1;
+ int entity_count;
+ int item_count;
+
+ ph_world_init(&world, area, 7, 32.0f, 32.0f);
+ if (ph_world_add_entity_def(&world, (PhEntityDef){
+ .id = 10, .stats = { .max_hp = 10 }, .kind = PH_ENTITY_PLAYER }) < 0 ||
+ ph_world_add_entity_def(&world, (PhEntityDef){
+ .id = 20, .stats = { .max_hp = 5 }, .kind = PH_ENTITY_MONSTER }) < 0 ||
+ ph_world_add_item_def(&world, (PhItemDef){ .id = 30 }) < 0 ||
+ ph_world_prepare_area(&world, &area, &catalog, 7, &player) < 0)
+ return 1;
+ if (player < 0 || world.entity_count != 2 || world.ground_item_count != 1 ||
+ world.entities[1].territory_radius != 3 ||
+ world.ground_items[0].amount != 2 || world.initialized_area_count != 1)
+ return 1;
+ entity_count = world.entity_count;
+ item_count = world.ground_item_count;
+ if (ph_world_prepare_area(&world, &area, &catalog, 7, &player) < 0 ||
+ world.entity_count != entity_count || world.ground_item_count != item_count)
+ return 1;
+ return 0;
+}
+
+static int
ph_movement_test(int open, int blocked)
{
enum {
@@ -231,8 +283,8 @@ ph_enter_portal(PhWorld *world)
for (i = 0; i < world->area.marker_count; ++i) {
const PhAreaMarker *marker = &world->area.markers[i];
- const PhMarkerDef *def = ph_game_marker_def(world->area_id,
- marker->symbol);
+ const PhMarkerDef *def = ph_area_catalog_marker(&ph_area_catalog,
+ world->area_id, marker->symbol);
const PhTileDef *tile;
if (!def || def->kind != PH_MARKER_PORTAL) continue;
@@ -240,7 +292,7 @@ ph_enter_portal(PhWorld *world)
if (!tile || tile->symbol != 'T') return -1;
player->pos = (PhVec2){ PH_TILE_CENTER(marker->tile_x),
PH_TILE_CENTER(marker->tile_y) };
- return ph_game_update_portal(world);
+ return ph_world_update_portal(world, &ph_area_catalog);
}
return -1;
}
@@ -338,6 +390,10 @@ main(void)
fprintf(stderr, "smoke test failed: tile configuration\n");
return 1;
}
+ if (ph_area_catalog_test()) {
+ fprintf(stderr, "smoke test failed: engine area catalog\n");
+ return 1;
+ }
if (ph_movement_test(open, blocked)) {
fprintf(stderr, "smoke test failed: world movement\n");
return 1;