commit 51b7345bc3712bb83e2a2f9e2e2b0efadee20a2d
parent 30803d85bd089d287bb0aa04aaaacbb42f814c65
Author: beep <beep@wimdupont.com>
Date: Wed, 29 Jul 2026 13:58:04 +0000
Fix encounter contact and menu navigation
Diffstat:
6 files changed, 70 insertions(+), 23 deletions(-)
diff --git a/phantasia.6 b/phantasia.6
@@ -27,7 +27,8 @@ Move through inventory items, item actions, battle commands, and learned spells.
.It Enter
Open or confirm the selected inventory action, battle command, or spell.
.It Escape
-Return from the battle spell list to the command menu.
+Return one level from an inventory or battle submenu, or close an open character
+or top-level inventory window.
.It Window close button
Quit the game.
.El
diff --git a/src/engine/world.c b/src/engine/world.c
@@ -142,7 +142,7 @@ ph_world_blocking_entity(const PhWorld *world, int self_index, PhVec2 pos)
}
def = ph_world_entity_def(world, entity->type_id);
- if (!def || !def->blocks_movement) {
+ if (!def || (!def->blocks_movement && i != world->player_index)) {
continue;
}
diff --git a/src/game/battle.c b/src/game/battle.c
@@ -142,6 +142,12 @@ ph_battle_cast(PhBattle *battle, PhWorld *world)
}
void
+ph_battle_cancel(PhBattle *battle)
+{
+ if (battle->phase == PH_BATTLE_MAGIC) battle->phase = PH_BATTLE_COMMAND;
+}
+
+void
ph_battle_order(const PhBattle *battle, const PhWorld *world,
int order[PH_BATTLE_ORDER_COUNT])
{
@@ -158,9 +164,7 @@ ph_battle_order(const PhBattle *battle, const PhWorld *world,
if (i == 0 && actor == world->player_index) {
if (battle->phase == PH_BATTLE_PLAYER_ACTION)
delay = battle->action_delay;
- else if (spell && (battle->phase == PH_BATTLE_MAGIC ||
- (battle->phase == PH_BATTLE_COMMAND &&
- battle->command == PH_BATTLE_MAGICK)))
+ else if (spell && battle->phase == PH_BATTLE_MAGIC)
delay = delay * spell->delay_percent / 100;
if (delay < 1) delay = 1;
}
diff --git a/src/game/battle.h b/src/game/battle.h
@@ -47,6 +47,7 @@ typedef struct {
void ph_battle_begin(PhBattle *battle, const PhWorld *world, int enemy_index);
void ph_battle_select(PhBattle *battle, PhWorld *world);
void ph_battle_cast(PhBattle *battle, PhWorld *world);
+void ph_battle_cancel(PhBattle *battle);
PhBattleResult ph_battle_update(PhBattle *battle, PhWorld *world, float dt,
int transition);
void ph_battle_order(const PhBattle *battle, const PhWorld *world,
diff --git a/src/game/main.c b/src/game/main.c
@@ -670,7 +670,7 @@ ph_draw_inventory(SDL_Renderer *renderer, const PhAssets *assets,
ph_draw_inventory_popup(renderer, world, ui);
}
-static void
+static int
ph_inventory_key(PhInventoryUi *ui, PhWorld *world, SDL_Scancode key)
{
int previous = key == PH_KEY_MENU_UP_1 || key == PH_KEY_MENU_UP_2 ||
@@ -678,6 +678,16 @@ ph_inventory_key(PhInventoryUi *ui, PhWorld *world, SDL_Scancode key)
int next = key == PH_KEY_MENU_DOWN_1 || key == PH_KEY_MENU_DOWN_2 ||
key == PH_KEY_MENU_RIGHT_1 || key == PH_KEY_MENU_RIGHT_2;
+ if (key == PH_KEY_MENU_CANCEL) {
+ if (ui->mode == PH_INVENTORY_EXAMINE)
+ ui->mode = PH_INVENTORY_ACTIONS;
+ else if (ui->mode == PH_INVENTORY_ACTIONS)
+ ui->mode = PH_INVENTORY_BROWSE;
+ else
+ return 1;
+ ui->status[0] = '\0';
+ return 0;
+ }
if (key == PH_KEY_MENU_ACCEPT) {
if (ui->mode == PH_INVENTORY_BROWSE && ui->selected >= 0) {
int actions[PH_INVENTORY_ACTION_COUNT];
@@ -691,9 +701,9 @@ ph_inventory_key(PhInventoryUi *ui, PhWorld *world, SDL_Scancode key)
} else if (ui->mode == PH_INVENTORY_EXAMINE) {
ui->mode = PH_INVENTORY_ACTIONS;
}
- return;
+ return 0;
}
- if ((!previous && !next) || ui->mode == PH_INVENTORY_EXAMINE) return;
+ if ((!previous && !next) || ui->mode == PH_INVENTORY_EXAMINE) return 0;
if (ui->mode == PH_INVENTORY_ACTIONS) {
ui->option = ph_inventory_action_step(&world->item_defs[ui->selected],
ui->option, previous ? -1 : 1);
@@ -701,6 +711,7 @@ ph_inventory_key(PhInventoryUi *ui, PhWorld *world, SDL_Scancode key)
ui->selected = ph_inventory_step(world, ui->selected, previous ? -1 : 1);
ui->status[0] = '\0';
}
+ return 0;
}
static void
@@ -746,14 +757,13 @@ ph_draw_battle(SDL_Renderer *renderer, const PhAssets *assets,
const PhEntity *player = ph_world_player(world);
const PhEntity *enemy = &world->entities[battle->enemy_index];
const PhEntityDef *enemy_def = ph_world_entity_def(world, enemy->type_id);
- const PhSpellDef *spell = ph_battle_spell(battle);
PhStats player_stats = ph_world_entity_stats(world, player);
PhStats enemy_stats = ph_world_entity_stats(world, enemy);
int order[PH_BATTLE_ORDER_COUNT];
SDL_FRect ground = { 0.0f, PH_VIEW_H - 136.0f, PH_VIEW_W, 72.0f };
SDL_FRect panel = { 8.0f, PH_VIEW_H - 64.0f, PH_VIEW_W - 16.0f, 56.0f };
- SDL_FRect spell_panel = { PH_VIEW_W * 0.5f - 64.0f,
- PH_VIEW_H - 116.0f, 144.0f,
+ SDL_FRect spell_panel = { PH_VIEW_W * 0.5f - 88.0f,
+ PH_VIEW_H - 116.0f, 176.0f,
24.0f + (float)LEN(ph_player_spells) * 12.0f };
SDL_FRect enemy_rect = { 56.0f + ph_battle_lunge(battle,
PH_BATTLE_ENEMY_ACTION), PH_VIEW_H - 172.0f,
@@ -803,11 +813,6 @@ ph_draw_battle(SDL_Renderer *renderer, const PhAssets *assets,
}
SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR);
SDL_RenderDebugText(renderer, 104.0f, panel.y + 8.0f, battle->message);
- if ((battle->phase == PH_BATTLE_COMMAND || battle->phase == PH_BATTLE_MAGIC) &&
- battle->command == PH_BATTLE_MAGICK && spell) {
- snprintf(text, sizeof(text), "%s %dMP QUICK", spell->name, spell->mp_cost);
- SDL_RenderDebugText(renderer, 104.0f, ph_ui_bottom(24.0f), text);
- }
snprintf(text, sizeof(text), "HP %d/%d", player->hp, player_stats.max_hp);
SDL_RenderDebugText(renderer, ph_ui_right(80.0f), ph_ui_bottom(36.0f), text);
snprintf(text, sizeof(text), "MP %d/%d", player->mp, player_stats.max_mp);
@@ -824,8 +829,9 @@ ph_draw_battle(SDL_Renderer *renderer, const PhAssets *assets,
const PhSpellDef *choice = ph_spell_def(ph_player_spells[i]);
if (!choice) continue;
- snprintf(text, sizeof(text), "%c %s %dMP",
- battle->spell == i ? '>' : ' ', choice->name, choice->mp_cost);
+ snprintf(text, sizeof(text), "%c %s %dMP%s",
+ battle->spell == i ? '>' : ' ', choice->name, choice->mp_cost,
+ choice->delay_percent < 100 ? " QUICK" : "");
SDL_RenderDebugText(renderer, spell_panel.x + 8.0f,
spell_panel.y + 20.0f + (float)i * 12.0f, text);
}
@@ -1055,6 +1061,15 @@ ph_run_render_smoke_test(void)
fprintf(stderr, "render smoke test failed: inventory actions\n");
result = 1;
}
+ inventory_ui.mode = PH_INVENTORY_EXAMINE;
+ if (ph_inventory_key(&inventory_ui, &world, PH_KEY_MENU_CANCEL) ||
+ inventory_ui.mode != PH_INVENTORY_ACTIONS ||
+ ph_inventory_key(&inventory_ui, &world, PH_KEY_MENU_CANCEL) ||
+ inventory_ui.mode != PH_INVENTORY_BROWSE ||
+ !ph_inventory_key(&inventory_ui, &world, PH_KEY_MENU_CANCEL)) {
+ fprintf(stderr, "render smoke test failed: inventory cancel\n");
+ result = 1;
+ }
ph_inventory_open(&inventory_ui, &world);
for (i = 0; i < 10; ++i) {
inventory_ui.mode = i == 8 ? PH_INVENTORY_EXAMINE :
@@ -1105,8 +1120,8 @@ ph_run_render_smoke_test(void)
}
battle.command = PH_BATTLE_MAGICK;
ph_battle_order(&battle, &world, order);
- if (order[0] != world.player_index || order[1] != world.player_index) {
- fprintf(stderr, "render smoke test failed: quick CTB order\n");
+ if (order[0] != world.player_index || order[1] != enemy_index) {
+ fprintf(stderr, "render smoke test failed: command CTB order\n");
result = 1;
}
ph_battle_select(&battle, &world);
@@ -1114,6 +1129,11 @@ ph_run_render_smoke_test(void)
fprintf(stderr, "render smoke test failed: Magick submenu\n");
result = 1;
}
+ ph_battle_order(&battle, &world, order);
+ if (order[0] != world.player_index || order[1] != world.player_index) {
+ fprintf(stderr, "render smoke test failed: spell CTB order\n");
+ result = 1;
+ }
ph_render_frame(renderer, &assets, &world, (PhInput){ 0 }, 0.0f,
PH_MENU_NONE, &inventory_ui, &game, &battle);
ph_battle_cast(&battle, &world);
@@ -1230,7 +1250,7 @@ ph_run_game(int frame_limit)
else if (key == PH_KEY_MENU_ACCEPT)
ph_battle_cast(&battle, &world);
else if (key == PH_KEY_MENU_CANCEL)
- battle.phase = PH_BATTLE_COMMAND;
+ ph_battle_cancel(&battle);
} else if (previous || next) {
battle.command = (PhBattleCommand)((battle.command + (previous ?
PH_BATTLE_COMMAND_COUNT - 1 : 1)) % PH_BATTLE_COMMAND_COUNT);
@@ -1243,6 +1263,10 @@ ph_run_game(int frame_limit)
game = PH_GAME_WORLD;
menu = PH_MENU_NONE;
} else if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
+ game == PH_GAME_WORLD && menu == PH_MENU_CHARACTER &&
+ event.key.scancode == PH_KEY_MENU_CANCEL) {
+ menu = PH_MENU_NONE;
+ } else if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
game == PH_GAME_WORLD && event.key.scancode == PH_KEY_CHARACTER) {
menu = menu == PH_MENU_CHARACTER ? PH_MENU_NONE : PH_MENU_CHARACTER;
} else if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
@@ -1255,7 +1279,8 @@ ph_run_game(int frame_limit)
}
} else if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
game == PH_GAME_WORLD && menu == PH_MENU_INVENTORY) {
- ph_inventory_key(&inventory_ui, &world, event.key.scancode);
+ if (ph_inventory_key(&inventory_ui, &world, event.key.scancode))
+ menu = PH_MENU_NONE;
}
}
diff --git a/src/tests/smoke.c b/src/tests/smoke.c
@@ -64,6 +64,13 @@ ph_movement_test(int open, int blocked)
ph_world_tick(&world, (PhInput){ .move_x = 1 }, PH_MAX_FRAME_TIME);
if (!world.entities[player_index].moving ||
world.entities[player_index].animation_time <= 0.0f) return 1;
+ world.entities[player_index].pos = player_start;
+ mob->pos = mob->move_target = (PhVec2){
+ PH_TILE_CENTER(PLAYER_COL + 1), player_start.y };
+ mob->moving = 0;
+ mob->move_credit = 0.0f;
+ ph_world_tick(&world, (PhInput){ 0 }, action_seconds);
+ if (world.encounter_index != mob_index) return 1;
world.entities[player_index].pos = player_start;
mob->pos = mob->move_target = mob_start;
@@ -71,6 +78,7 @@ ph_movement_test(int open, int blocked)
mob->moving = 0;
mob->move_credit = 0.0f;
mob->chase_turns = 2;
+ world.encounter_index = -1;
tiles[ROW * area.width + WALL_COL] = (unsigned char)blocked;
ph_world_tick(&world, (PhInput){ 0 }, action_seconds);
if (mob->moving || mob->chase_turns != 1) return 1;
@@ -172,11 +180,19 @@ ph_logic_test(int open, int blocked)
ph_battle_order(&battle, &world, order);
if (order[0] != player_index || order[1] != enemy_index) return 1;
battle.command = PH_BATTLE_MAGICK;
+ ph_battle_order(&battle, &world, order);
+ if (order[0] != player_index || order[1] != enemy_index) return 1;
ph_battle_select(&battle, &world);
- if (battle.phase != PH_BATTLE_MAGIC || world.entities[player_index].mp != 5 ||
+ ph_battle_order(&battle, &world, order);
+ if (battle.phase != PH_BATTLE_MAGIC || order[0] != player_index ||
+ order[1] != player_index || world.entities[player_index].mp != 5 ||
world.encounter_index != enemy_index ||
ph_world_apply_accuracy_penalty(&world, enemy_index, 20, 3) < 0)
return 1;
+ ph_battle_cancel(&battle);
+ ph_battle_order(&battle, &world, order);
+ if (battle.phase != PH_BATTLE_COMMAND || order[0] != player_index ||
+ order[1] != enemy_index) return 1;
ph_world_advance_effects(&world, enemy_index);
ph_world_advance_effects(&world, enemy_index);
ph_world_advance_effects(&world, enemy_index);