phantasia

Phantasia - 2D SDL3 RPG prototype.
git clone git://git.beep.wimdupont.com/phantasia.git
Log | Files | Refs | README | LICENSE

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:
MMakefile | 13+++++++++----
MREADME.adoc | 33++++++++++++++++++---------------
Asrc/engine/area.c | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/engine/area.h | 48++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/engine/render.c | 232+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/engine/render.h | 36++++++++++++++++++++++++++++++++++++
Msrc/game/area.c | 167+++----------------------------------------------------------------------------
Msrc/game/area.h | 7+------
Msrc/game/content.c | 27+++++++++++++++++++++++++++
Msrc/game/content.h | 38++++++--------------------------------
Msrc/game/main.c | 10++++++----
Msrc/game/presentation.c | 253+++++++------------------------------------------------------------------------
Msrc/game/presentation.h | 9++-------
Msrc/tests/render.c | 15+++++++++------
Msrc/tests/smoke.c | 62+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
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;