phantasia

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

commit d1abfae121fdca02aba9e0d6d9133c21a7da1e94
parent 9d6768ea94eeea7ccb242b77a35e7f82cbc6c21e
Author: beep <beep@wimdupont.com>
Date:   Wed, 29 Jul 2026 11:49:13 +0000

Add inventory menu and spear pickup

Diffstat:
Mconfig.def.h | 22++++++++++++----------
Mphantasia.6 | 4+++-
Msrc/game/main.c | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
3 files changed, 104 insertions(+), 30 deletions(-)

diff --git a/config.def.h b/config.def.h @@ -21,10 +21,10 @@ #define PH_NPC_COLOR 220, 180, 90, 255 #define PH_NOTICE_COLOR 20, 24, 34, 255 #define PH_NOTICE_TEXT_COLOR 232, 236, 244, 255 -#define PH_SHEET_COLOR 20, 24, 34, 255 -#define PH_SHEET_BORDER_COLOR 116, 126, 148, 255 -#define PH_SHEET_TEXT_COLOR 232, 236, 244, 255 -#define PH_SHEET_BONUS_COLOR 126, 220, 146, 255 +#define PH_MENU_COLOR 20, 24, 34, 255 +#define PH_MENU_BORDER_COLOR 116, 126, 148, 255 +#define PH_MENU_TEXT_COLOR 232, 236, 244, 255 +#define PH_MENU_BONUS_COLOR 126, 220, 146, 255 enum { PH_ASSET_NONE, @@ -55,7 +55,7 @@ enum { }; enum { - PH_ITEM_TALENT_SHARD = 1, + PH_ITEM_RUSTED_SPEAR = 1, PH_ITEM_WAYFARER_BLADE, PH_ITEM_TRAVELER_COAT, }; @@ -83,10 +83,11 @@ static const PhEntityDef ph_entity_defs[] = { static const PhItemDef ph_item_defs[] = { { - .id = PH_ITEM_TALENT_SHARD, - .name = "Talent Shard", - .value = 30, - .equip_slot = PH_EQUIP_NONE, + .id = PH_ITEM_RUSTED_SPEAR, + .name = "Rusted Spear", + .value = 12, + .bonuses = { .attack = 2 }, + .equip_slot = PH_EQUIP_WEAPON, .asset_id = PH_ASSET_ITEMS, .sprite_tile_x = 2, .sprite_tile_y = 6, @@ -133,7 +134,7 @@ static const struct { PhVec2 pos; int amount; } ph_item_drops[] = { - { PH_ITEM_TALENT_SHARD, { 104.0f, 80.0f }, 1 }, + { PH_ITEM_RUSTED_SPEAR, { 104.0f, 80.0f }, 1 }, }; #if PH_USE_SDL @@ -148,6 +149,7 @@ static const struct { #define PH_KEY_INTERACT_1 SDL_SCANCODE_E #define PH_KEY_INTERACT_2 SDL_SCANCODE_SPACE #define PH_KEY_CHARACTER SDL_SCANCODE_C +#define PH_KEY_INVENTORY SDL_SCANCODE_I #endif #endif diff --git a/phantasia.6 b/phantasia.6 @@ -1,4 +1,4 @@ -.Dd April 4, 2026 +.Dd July 29, 2026 .Dt PHANTASIA 6 .Os .Sh NAME @@ -20,6 +20,8 @@ Move the player. Interact and pick up nearby loot. .It C Open or close the character sheet. +.It I +Open or close the inventory. .It Window close button Quit the game. .El diff --git a/src/game/main.c b/src/game/main.c @@ -16,6 +16,12 @@ typedef struct { SDL_Texture *textures[LEN(ph_asset_defs)]; } PhAssets; + +typedef enum { + PH_MENU_NONE, + PH_MENU_CHARACTER, + PH_MENU_INVENTORY, +} PhMenu; #endif #if PH_USE_SDL @@ -350,6 +356,25 @@ ph_format_bonuses(char *text, size_t size, PhStats bonuses) } static void +ph_draw_menu(SDL_Renderer *renderer, const char *title, SDL_Scancode key) +{ + SDL_FRect panel = { 12.0f, 12.0f, PH_VIEW_W - 24.0f, PH_VIEW_H - 24.0f }; + char text[32]; + float x; + + SDL_SetRenderDrawColor(renderer, PH_MENU_COLOR); + SDL_RenderFillRect(renderer, &panel); + SDL_SetRenderDrawColor(renderer, PH_MENU_BORDER_COLOR); + SDL_RenderRect(renderer, &panel); + SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); + SDL_RenderDebugText(renderer, 24.0f, 22.0f, title); + snprintf(text, sizeof(text), "CLOSE: %s", SDL_GetScancodeName(key)); + x = (float)(PH_VIEW_W - 20) - (float)strlen(text) * 8.0f; + if (x < 24.0f) x = 24.0f; + SDL_RenderDebugText(renderer, x, (float)(PH_VIEW_H - 22), text); +} + +static void ph_draw_character_sheet(SDL_Renderer *renderer, const PhAssets *assets, const PhWorld *world) { @@ -361,19 +386,13 @@ ph_draw_character_sheet(SDL_Renderer *renderer, const PhAssets *assets, const PhEntity *player = ph_world_player(world); const PhEntityDef *def; PhStats stats; - SDL_FRect panel = { 12.0f, 12.0f, PH_VIEW_W - 24.0f, PH_VIEW_H - 24.0f }; SDL_FRect avatar = { 32.0f, 46.0f, 48.0f, 48.0f }; char text[128]; int slot; if (!player || !(def = ph_world_entity_def(world, player->type_id))) return; stats = ph_world_entity_stats(world, player); - SDL_SetRenderDrawColor(renderer, PH_SHEET_COLOR); - SDL_RenderFillRect(renderer, &panel); - SDL_SetRenderDrawColor(renderer, PH_SHEET_BORDER_COLOR); - SDL_RenderRect(renderer, &panel); - SDL_SetRenderDrawColor(renderer, PH_SHEET_TEXT_COLOR); - SDL_RenderDebugText(renderer, 24.0f, 22.0f, "CHARACTER"); + 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)) @@ -396,7 +415,7 @@ ph_draw_character_sheet(SDL_Renderer *renderer, const PhAssets *assets, float y = 130.0f + (float)(slot - PH_EQUIP_WEAPON) * 27.0f; snprintf(text, sizeof(text), "%s %s", slot_names[slot], item ? item->name : "-"); - SDL_SetRenderDrawColor(renderer, PH_SHEET_TEXT_COLOR); + SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); SDL_RenderDebugText(renderer, 48.0f, y, text); if (!item) continue; if (item->asset_id > PH_ASSET_NONE && @@ -406,27 +425,68 @@ ph_draw_character_sheet(SDL_Renderer *renderer, const PhAssets *assets, item->sprite_tile_x, item->sprite_tile_y, icon, SDL_FLIP_NONE); } ph_format_bonuses(text, sizeof(text), item->bonuses); - SDL_SetRenderDrawColor(renderer, PH_SHEET_BONUS_COLOR); + SDL_SetRenderDrawColor(renderer, PH_MENU_BONUS_COLOR); SDL_RenderDebugText(renderer, 48.0f, y + 10.0f, text); } - SDL_SetRenderDrawColor(renderer, PH_SHEET_TEXT_COLOR); - snprintf(text, sizeof(text), "CLOSE: %s", SDL_GetScancodeName(PH_KEY_CHARACTER)); - SDL_RenderDebugText(renderer, (float)(PH_VIEW_W - 88), (float)(PH_VIEW_H - 22), text); +} + +static void +ph_draw_inventory(SDL_Renderer *renderer, const PhAssets *assets, const PhWorld *world) +{ + char text[128]; + int shown = 0; + int more = 0; + int i; + + ph_draw_menu(renderer, "INVENTORY", PH_KEY_INVENTORY); + for (i = 0; i < world->item_def_count; ++i) { + const PhItemDef *item = &world->item_defs[i]; + int amount = world->inventory[i]; + float y; + + if (amount <= 0) continue; + if (shown >= 6) { + ++more; + continue; + } + y = 48.0f + (float)shown++ * 27.0f; + if (item->asset_id > PH_ASSET_NONE && + (size_t)item->asset_id < LEN(assets->textures)) { + SDL_FRect icon = { 24.0f, y - 4.0f, 16.0f, 16.0f }; + ph_draw_sprite(renderer, assets->textures[item->asset_id], + item->sprite_tile_x, item->sprite_tile_y, icon, SDL_FLIP_NONE); + } + SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); + snprintf(text, sizeof(text), "%s x%d", item->name ? item->name : "Unknown", amount); + SDL_RenderDebugText(renderer, 48.0f, y, text); + ph_format_bonuses(text, sizeof(text), item->bonuses); + if (!text[0]) snprintf(text, sizeof(text), "VALUE %d", item->value); + SDL_SetRenderDrawColor(renderer, PH_MENU_BONUS_COLOR); + SDL_RenderDebugText(renderer, 48.0f, y + 10.0f, text); + } + SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); + if (!shown) SDL_RenderDebugText(renderer, 24.0f, 48.0f, "EMPTY"); + if (more) { + snprintf(text, sizeof(text), "+%d MORE", more); + SDL_RenderDebugText(renderer, 24.0f, 208.0f, text); + } } static void ph_render_frame(SDL_Renderer *renderer, const PhAssets *assets, PhWorld *world, - PhInput input, float dt, int show_character_sheet) + PhInput input, float dt, PhMenu menu) { - if (!show_character_sheet) ph_world_tick(world, input, dt); + if (menu == PH_MENU_NONE) ph_world_tick(world, input, dt); 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); - if (show_character_sheet) { + if (menu == PH_MENU_CHARACTER) { ph_draw_character_sheet(renderer, assets, world); + } else if (menu == PH_MENU_INVENTORY) { + ph_draw_inventory(renderer, assets, world); } else if (world->notice_seconds > 0.0f && world->notice[0] != '\0') { SDL_FRect notice_bg = { .x = 8.0f, @@ -539,9 +599,16 @@ ph_run_render_smoke_test(void) return 1; } + ph_world_tick(&world, (PhInput){ .move_x = 1 }, 0.1f); + ph_world_tick(&world, (PhInput){ .interact = 1 }, 0.0f); + if (ph_world_item_amount(&world, PH_ITEM_RUSTED_SPEAR) != 1) { + fprintf(stderr, "render smoke test failed: spear pickup\n"); + result = 1; + } for (i = 0; i < 8; ++i) { ph_render_frame(renderer, &assets, &world, (PhInput){ 0 }, - 1.0f / 60.0f, i == 7); + 1.0f / 60.0f, i == 6 ? PH_MENU_CHARACTER : + i == 7 ? PH_MENU_INVENTORY : PH_MENU_NONE); } if (!SDL_SaveBMP(surface, PH_RENDER_SMOKE_PATH)) { @@ -567,7 +634,7 @@ ph_run_game(int frame_limit) Uint64 last_ticks; int frame_count = 0; int running = 1; - int show_character_sheet = 0; + PhMenu menu = PH_MENU_NONE; if (!SDL_Init(SDL_INIT_VIDEO)) { fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError()); @@ -610,7 +677,10 @@ ph_run_game(int frame_limit) running = 0; } else if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat && event.key.scancode == PH_KEY_CHARACTER) { - show_character_sheet = !show_character_sheet; + menu = menu == PH_MENU_CHARACTER ? PH_MENU_NONE : PH_MENU_CHARACTER; + } else if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat && + event.key.scancode == PH_KEY_INVENTORY) { + menu = menu == PH_MENU_INVENTORY ? PH_MENU_NONE : PH_MENU_INVENTORY; } } @@ -622,7 +692,7 @@ ph_run_game(int frame_limit) last_ticks = now_ticks; input = ph_read_input(SDL_GetKeyboardState(NULL)); - ph_render_frame(renderer, &assets, &world, input, dt, show_character_sheet); + ph_render_frame(renderer, &assets, &world, input, dt, menu); ++frame_count; if (frame_limit > 0 && frame_count >= frame_limit) { running = 0;