game.c (19369B)
1 #include "game/game.h" 2 #include "engine/save.h" 3 4 #include <stdio.h> 5 #include <string.h> 6 7 #include "config.h" 8 9 #if PH_CONFIG_VERSION != PH_CONFIG_API_VERSION 10 #error "config.h version does not match the engine; merge config.def.h" 11 #endif 12 #if PH_SIMULATION_HZ <= 0 || PH_MAX_CATCHUP_STEPS <= 0 13 #error "simulation tick rate and catch-up limit must be positive" 14 #endif 15 16 static int 17 ph_menu_direction(PhAction key) 18 { 19 return key == PH_ACTION_UP || 20 key == PH_ACTION_DOWN || 21 key == PH_ACTION_LEFT || 22 key == PH_ACTION_RIGHT; 23 } 24 25 static int 26 ph_menu_back(PhAction key) 27 { 28 return key == PH_ACTION_CANCEL || key == PH_ACTION_BACK; 29 } 30 31 void 32 ph_options_open(PhOptionsUi *ui) 33 { 34 if (!ui) return; 35 ui->selected = 0; 36 ui->mode = PH_OPTIONS_ROOT; 37 } 38 39 PhOptionsAction 40 ph_options_input(PhOptionsUi *ui, PhAction key) 41 { 42 int previous = key == PH_ACTION_UP || 43 key == PH_ACTION_LEFT; 44 int next = key == PH_ACTION_DOWN || 45 key == PH_ACTION_RIGHT; 46 47 if (!ui) return PH_OPTIONS_CLOSE; 48 if (ph_menu_back(key)) { 49 if (ui->mode == PH_OPTIONS_SOUND) { 50 ui->mode = PH_OPTIONS_ROOT; 51 ui->selected = 0; 52 return PH_OPTIONS_STAY; 53 } 54 return PH_OPTIONS_CLOSE; 55 } 56 if (previous || next) { 57 ui->selected = (ui->selected + 1) % 2; 58 return PH_OPTIONS_STAY; 59 } 60 if (key != PH_ACTION_ACCEPT) return PH_OPTIONS_STAY; 61 if (ui->mode == PH_OPTIONS_ROOT) { 62 if (ui->selected == 1) return PH_OPTIONS_EXIT; 63 ui->mode = PH_OPTIONS_SOUND; 64 ui->selected = 0; 65 } else if (ui->selected == 0) { 66 ui->music_enabled = !ui->music_enabled; 67 } else { 68 ui->effects_enabled = !ui->effects_enabled; 69 } 70 return PH_OPTIONS_STAY; 71 } 72 73 PhStartAction 74 ph_start_input(PhStartUi *ui, PhAction key) 75 { 76 int direction = key == PH_ACTION_UP || 77 key == PH_ACTION_DOWN; 78 79 if (!ui) return PH_START_STAY; 80 if (direction) ui->selected = ui->can_continue ? !ui->selected : 1; 81 if (key != PH_ACTION_ACCEPT) return PH_START_STAY; 82 return ui->selected == 0 && ui->can_continue ? 83 PH_START_CONTINUE : PH_START_NEW; 84 } 85 86 int 87 ph_inventory_step(const PhWorld *world, int selected, int direction) 88 { 89 int i = selected < 0 ? (direction > 0 ? -1 : 0) : selected; 90 int step; 91 92 for (step = 0; step < world->content.item_count; ++step) { 93 i += direction; 94 if (i < 0) i = world->content.item_count - 1; 95 if (i >= world->content.item_count) i = 0; 96 if (world->inventory[i] > 0) return i; 97 } 98 return -1; 99 } 100 101 void 102 ph_inventory_open(PhInventoryUi *ui, const PhWorld *world) 103 { 104 memset(ui, 0, sizeof(*ui)); 105 ui->selected = ph_inventory_step(world, -1, 1); 106 } 107 108 static void 109 ph_inventory_reselect(PhInventoryUi *ui, const PhWorld *world) 110 { 111 if (ui->selected >= 0 && world->inventory[ui->selected] > 0) return; 112 ui->selected = ph_inventory_step(world, ui->selected, 1); 113 } 114 115 static void 116 ph_inventory_action(PhInventoryUi *ui, PhWorld *world, int action) 117 { 118 const PhItemDef *item; 119 const char *name; 120 int result = -1; 121 122 if (ui->selected < 0) return; 123 item = &world->content.items[ui->selected]; 124 name = item->name ? item->name : "Item"; 125 if (action == PH_INVENTORY_EQUIP) 126 result = ph_world_equip_inventory_item(world, world->player_index, item->id); 127 else if (action == PH_INVENTORY_USE) 128 result = ph_world_use_inventory_item(world, world->player_index, item->id); 129 else if (action == PH_INVENTORY_DROP) 130 result = ph_world_drop_inventory_item(world, world->player_index, item->id); 131 else { 132 ui->mode = PH_INVENTORY_EXAMINE; 133 ui->status[0] = '\0'; 134 return; 135 } 136 137 if (result < 0) { 138 snprintf(ui->status, sizeof(ui->status), "Cannot %s %s.", 139 action == PH_INVENTORY_EQUIP ? "equip" : 140 action == PH_INVENTORY_USE ? "use" : "drop", name); 141 return; 142 } 143 snprintf(ui->status, sizeof(ui->status), "%s %s.", 144 action == PH_INVENTORY_EQUIP ? "Equipped" : 145 action == PH_INVENTORY_USE ? "Used" : "Dropped", name); 146 ui->mode = PH_INVENTORY_BROWSE; 147 ph_inventory_reselect(ui, world); 148 } 149 150 int 151 ph_inventory_action_enabled(const PhItemDef *item, int action) 152 { 153 if (action == PH_INVENTORY_EQUIP) return item->equip_slot != PH_EQUIP_NONE; 154 if (action == PH_INVENTORY_USE) return item->use != PH_ITEM_USE_NONE; 155 return 1; 156 } 157 158 void 159 ph_inventory_action_order(const PhItemDef *item, int actions[PH_INVENTORY_ACTION_COUNT]) 160 { 161 if (item->equip_slot != PH_EQUIP_NONE) { 162 actions[0] = PH_INVENTORY_EQUIP; 163 actions[1] = PH_INVENTORY_USE; 164 } else if (item->use != PH_ITEM_USE_NONE) { 165 actions[0] = PH_INVENTORY_USE; 166 actions[1] = PH_INVENTORY_EQUIP; 167 } else { 168 actions[0] = PH_INVENTORY_DROP; 169 actions[1] = PH_INVENTORY_EXAMINE_ACTION; 170 } 171 if (actions[0] < PH_INVENTORY_DROP) { 172 actions[2] = PH_INVENTORY_DROP; 173 actions[3] = PH_INVENTORY_EXAMINE_ACTION; 174 } else { 175 actions[2] = PH_INVENTORY_EQUIP; 176 actions[3] = PH_INVENTORY_USE; 177 } 178 } 179 180 static int 181 ph_inventory_action_step(const PhItemDef *item, int selected, int direction) 182 { 183 int actions[PH_INVENTORY_ACTION_COUNT]; 184 int position = 0; 185 int step; 186 187 ph_inventory_action_order(item, actions); 188 while (position < PH_INVENTORY_ACTION_COUNT && actions[position] != selected) ++position; 189 for (step = 0; step < PH_INVENTORY_ACTION_COUNT; ++step) { 190 position = (position + (direction < 0 ? 191 PH_INVENTORY_ACTION_COUNT - 1 : 1)) % PH_INVENTORY_ACTION_COUNT; 192 if (ph_inventory_action_enabled(item, actions[position])) return actions[position]; 193 } 194 return selected; 195 } 196 197 int 198 ph_inventory_input(PhInventoryUi *ui, PhWorld *world, PhAction key) 199 { 200 int previous = key == PH_ACTION_UP || 201 key == PH_ACTION_LEFT; 202 int next = key == PH_ACTION_DOWN || 203 key == PH_ACTION_RIGHT; 204 205 if (ph_menu_back(key)) { 206 if (ui->mode == PH_INVENTORY_EXAMINE) 207 ui->mode = PH_INVENTORY_ACTIONS; 208 else if (ui->mode == PH_INVENTORY_ACTIONS) 209 ui->mode = PH_INVENTORY_BROWSE; 210 else 211 return 1; 212 ui->status[0] = '\0'; 213 return 0; 214 } 215 if (key == PH_ACTION_ACCEPT) { 216 if (ui->mode == PH_INVENTORY_BROWSE && ui->selected >= 0) { 217 int actions[PH_INVENTORY_ACTION_COUNT]; 218 219 ph_inventory_action_order(&world->content.items[ui->selected], actions); 220 ui->mode = PH_INVENTORY_ACTIONS; 221 ui->option = actions[0]; 222 ui->status[0] = '\0'; 223 } else if (ui->mode == PH_INVENTORY_ACTIONS) { 224 ph_inventory_action(ui, world, ui->option); 225 } else if (ui->mode == PH_INVENTORY_EXAMINE) { 226 ui->mode = PH_INVENTORY_ACTIONS; 227 } 228 return 0; 229 } 230 if ((!previous && !next) || ui->mode == PH_INVENTORY_EXAMINE) return 0; 231 if (ui->mode == PH_INVENTORY_ACTIONS) { 232 ui->option = ph_inventory_action_step(&world->content.items[ui->selected], 233 ui->option, previous ? -1 : 1); 234 } else { 235 ui->selected = ph_inventory_step(world, ui->selected, previous ? -1 : 1); 236 ui->status[0] = '\0'; 237 } 238 return 0; 239 } 240 241 void 242 ph_spellbook_open(PhSpellbookUi *ui, const PhSpellBook *spells) 243 { 244 memset(ui, 0, sizeof(*ui)); 245 ui->selected = spells && spells->learned_count > 0 ? 0 : -1; 246 } 247 248 int 249 ph_spellbook_action_enabled(const PhSpellDef *spell, int action) 250 { 251 return action == PH_SPELLBOOK_EXAMINE_ACTION || 252 (spell && spell->field_use != PH_SPELL_FIELD_NONE); 253 } 254 255 static int 256 ph_spellbook_action_step(const PhSpellDef *spell, int selected, int direction) 257 { 258 int action; 259 int step; 260 261 for (step = 1; step <= PH_SPELLBOOK_ACTION_COUNT; ++step) { 262 action = (selected + (direction < 0 ? 263 PH_SPELLBOOK_ACTION_COUNT - step : step)) % PH_SPELLBOOK_ACTION_COUNT; 264 if (ph_spellbook_action_enabled(spell, action)) return action; 265 } 266 return selected; 267 } 268 269 static void 270 ph_spellbook_action(PhSpellbookUi *ui, PhWorld *world, PhSpellBook *spells) 271 { 272 const PhSpellDef *spell; 273 int amount; 274 275 if (ui->selected < 0 || ui->selected >= spells->learned_count) return; 276 spell = ph_spellbook_def(spells, spells->learned[ui->selected]); 277 if (!spell) return; 278 if (ui->option == PH_SPELLBOOK_EXAMINE_ACTION) { 279 ui->mode = PH_SPELLBOOK_EXAMINE; 280 ui->status[0] = '\0'; 281 return; 282 } 283 amount = ph_spellbook_use(spells, world, world->player_index, spell->id); 284 if (amount < 0) 285 snprintf(ui->status, sizeof(ui->status), "Cannot use %s.", spell->name); 286 else { 287 snprintf(ui->status, sizeof(ui->status), "Restored %d HP.", amount); 288 ui->spell_used = 1; 289 } 290 ui->mode = PH_SPELLBOOK_BROWSE; 291 } 292 293 int 294 ph_spellbook_input(PhSpellbookUi *ui, PhWorld *world, PhSpellBook *spells, 295 PhAction key) 296 { 297 int previous = key == PH_ACTION_UP || 298 key == PH_ACTION_LEFT; 299 int next = key == PH_ACTION_DOWN || 300 key == PH_ACTION_RIGHT; 301 const PhSpellDef *spell = ui->selected >= 0 && 302 ui->selected < spells->learned_count ? ph_spellbook_def(spells, 303 spells->learned[ui->selected]) : NULL; 304 305 if (ph_menu_back(key)) { 306 if (ui->mode == PH_SPELLBOOK_EXAMINE) 307 ui->mode = PH_SPELLBOOK_ACTIONS; 308 else if (ui->mode == PH_SPELLBOOK_ACTIONS) 309 ui->mode = PH_SPELLBOOK_BROWSE; 310 else return 1; 311 ui->status[0] = '\0'; 312 return 0; 313 } 314 if (key == PH_ACTION_ACCEPT) { 315 if (ui->mode == PH_SPELLBOOK_BROWSE && spell) { 316 ui->mode = PH_SPELLBOOK_ACTIONS; 317 ui->option = ph_spellbook_action_enabled(spell, PH_SPELLBOOK_USE) ? 318 PH_SPELLBOOK_USE : PH_SPELLBOOK_EXAMINE_ACTION; 319 ui->status[0] = '\0'; 320 } else if (ui->mode == PH_SPELLBOOK_ACTIONS) { 321 ph_spellbook_action(ui, world, spells); 322 } else if (ui->mode == PH_SPELLBOOK_EXAMINE) { 323 ui->mode = PH_SPELLBOOK_ACTIONS; 324 } 325 return 0; 326 } 327 if ((!previous && !next) || ui->mode == PH_SPELLBOOK_EXAMINE) return 0; 328 if (ui->mode == PH_SPELLBOOK_ACTIONS) { 329 ui->option = ph_spellbook_action_step(spell, ui->option, 330 previous ? -1 : 1); 331 } else if (spells->learned_count > 0) { 332 ui->selected = (ui->selected + (previous ? 333 spells->learned_count - 1 : 1)) % spells->learned_count; 334 ui->status[0] = '\0'; 335 } 336 return 0; 337 } 338 339 void 340 ph_shop_open(PhShopUi *ui, int shop_id) 341 { 342 memset(ui, 0, sizeof(*ui)); 343 ui->shop_id = shop_id; 344 } 345 346 PhMenu 347 ph_interaction_open(PhShopUi *shop_ui, int interaction_id) 348 { 349 const PhInteractionDef *interaction = ph_game_interaction_def(interaction_id); 350 351 if (!interaction) return PH_MENU_NONE; 352 if (interaction->kind == PH_INTERACTION_SHOP && 353 ph_game_shop_def(interaction->content_id)) { 354 ph_shop_open(shop_ui, interaction->content_id); 355 return PH_MENU_SHOP; 356 } 357 return PH_MENU_NONE; 358 } 359 360 int 361 ph_shop_input(PhShopUi *ui, PhWorld *world, PhSpellBook *spells, PhAction key) 362 { 363 const PhShopDef *shop = ph_game_shop_def(ui->shop_id); 364 int previous = key == PH_ACTION_UP || 365 key == PH_ACTION_LEFT; 366 int next = key == PH_ACTION_DOWN || 367 key == PH_ACTION_RIGHT; 368 369 if (!shop) return 1; 370 if (ph_menu_back(key)) { 371 if (ui->mode == PH_SHOP_ROOT) return 1; 372 ui->mode = PH_SHOP_ROOT; 373 ui->selected = 0; 374 ui->status[0] = '\0'; 375 return 0; 376 } 377 if (ui->mode == PH_SHOP_TALK) { 378 if (key == PH_ACTION_ACCEPT) ui->mode = PH_SHOP_ROOT; 379 return 0; 380 } 381 if (previous || next) { 382 int count = ui->mode == PH_SHOP_ROOT ? 383 (shop->kind == PH_SHOP_ITEMS ? 3 : 2) : 384 ui->mode == PH_SHOP_BUY ? shop->stock_count : world->content.item_count; 385 386 if (ui->mode == PH_SHOP_SELL) { 387 ui->selected = ph_inventory_step(world, ui->selected, 388 previous ? -1 : 1); 389 } else if (count > 0) { 390 ui->selected = (ui->selected + (previous ? count - 1 : 1)) % count; 391 } 392 ui->status[0] = '\0'; 393 return 0; 394 } 395 if (key != PH_ACTION_ACCEPT) return 0; 396 if (ui->mode == PH_SHOP_ROOT) { 397 ui->mode = shop->kind == PH_SHOP_ITEMS ? 398 (PhShopMode)(ui->selected + 1) : 399 ui->selected == 0 ? PH_SHOP_BUY : PH_SHOP_TALK; 400 ui->selected = ui->mode == PH_SHOP_SELL ? 401 ph_inventory_step(world, -1, 1) : 0; 402 return 0; 403 } 404 if (ui->mode == PH_SHOP_BUY && ui->selected < shop->stock_count) { 405 if (shop->kind == PH_SHOP_SPELLS) { 406 const PhSpellDef *spell = ph_spellbook_def(spells, 407 shop->stock[ui->selected]); 408 409 if (!spell) return 0; 410 if (ph_spellbook_has(spells, spell->id)) 411 snprintf(ui->status, sizeof(ui->status), "Already learned."); 412 else if (ph_spellbook_buy(spells, world, world->player_index, 413 spell->id) < 0) 414 snprintf(ui->status, sizeof(ui->status), "Not enough gold."); 415 else 416 snprintf(ui->status, sizeof(ui->status), "Learned %s.", spell->name); 417 } else { 418 const PhItemDef *item = ph_world_item_def(world, 419 shop->stock[ui->selected]); 420 421 if (!item || ph_world_buy_item(world, world->player_index, item->id) < 0) 422 snprintf(ui->status, sizeof(ui->status), "Not enough gold."); 423 else 424 snprintf(ui->status, sizeof(ui->status), "Bought %s.", item->name); 425 } 426 } else if (ui->mode == PH_SHOP_SELL && ui->selected >= 0) { 427 const PhItemDef *item = &world->content.items[ui->selected]; 428 429 if (ph_world_sell_item(world, world->player_index, item->id) < 0) 430 snprintf(ui->status, sizeof(ui->status), "Cannot sell that."); 431 else { 432 snprintf(ui->status, sizeof(ui->status), "Sold %s.", item->name); 433 if (world->inventory[ui->selected] <= 0) 434 ui->selected = ph_inventory_step(world, ui->selected, 1); 435 } 436 } 437 return 0; 438 } 439 440 int 441 ph_game_input(PhGameContext *context, PhAction key) 442 { 443 PhMenu menu = context->menu; 444 PhGameMode mode = *context->mode; 445 PhBattlePhase phase = context->battle->phase; 446 int result = 0; 447 448 if (context->menu == PH_MENU_START) { 449 PhStartAction action = ph_start_input(context->start, key); 450 451 if (action == PH_START_CONTINUE) context->menu = PH_MENU_NONE; 452 else if (action == PH_START_NEW) { 453 context->time_reset = 1; 454 return PH_GAME_REQUEST_NEW; 455 } 456 } else if (context->menu == PH_MENU_MAIN) { 457 PhOptionsAction action; 458 459 if (ph_menu_direction(key)) result |= PH_GAME_MENU_SOUND; 460 action = ph_options_input(context->options, key); 461 if (action == PH_OPTIONS_CLOSE) context->menu = PH_MENU_NONE; 462 else if (action == PH_OPTIONS_EXIT) result |= PH_GAME_REQUEST_EXIT; 463 } else if (context->menu == PH_MENU_NONE && key == PH_ACTION_CANCEL && 464 (*context->mode == PH_GAME_WORLD || *context->mode == PH_GAME_VICTORY || 465 *context->mode == PH_GAME_OVER || (*context->mode == PH_GAME_BATTLE && 466 context->battle->phase != PH_BATTLE_MAGIC && 467 context->battle->phase != PH_BATTLE_TARGET))) { 468 ph_options_open(context->options); 469 context->menu = PH_MENU_MAIN; 470 } else if (context->menu == PH_MENU_NONE && *context->mode == PH_GAME_BATTLE && 471 (context->battle->phase == PH_BATTLE_COMMAND || 472 context->battle->phase == PH_BATTLE_MAGIC || 473 context->battle->phase == PH_BATTLE_TARGET)) { 474 int previous = key == PH_ACTION_UP || 475 key == PH_ACTION_LEFT; 476 int next = key == PH_ACTION_DOWN || 477 key == PH_ACTION_RIGHT; 478 479 if (context->battle->phase == PH_BATTLE_TARGET) { 480 if (previous || next) { 481 if (ph_battle_target_step(context->battle, context->world, 482 previous ? -1 : 1) >= 0) result |= PH_GAME_MENU_SOUND; 483 } 484 else if (key == PH_ACTION_ACCEPT) 485 ph_battle_confirm_target(context->battle, context->world); 486 else if (ph_menu_back(key)) 487 ph_battle_cancel(context->battle); 488 } else if (context->battle->phase == PH_BATTLE_MAGIC) { 489 if ((previous || next) && context->battle->spells.learned_count > 0) { 490 if (ph_battle_spell_step(context->battle, previous ? -1 : 1) >= 0) 491 result |= PH_GAME_MENU_SOUND; 492 } 493 else if (key == PH_ACTION_ACCEPT) 494 ph_battle_cast(context->battle, context->world); 495 else if (ph_menu_back(key)) 496 ph_battle_cancel(context->battle); 497 } else if (previous || next) { 498 context->battle->choice.command = (PhBattleCommand)((context->battle->choice.command + (previous ? 499 PH_BATTLE_COMMAND_COUNT - 1 : 1)) % PH_BATTLE_COMMAND_COUNT); 500 result |= PH_GAME_MENU_SOUND; 501 } else if (key == PH_ACTION_ACCEPT) { 502 ph_battle_select(context->battle, context->world); 503 } 504 } else if (context->menu == PH_MENU_NONE && *context->mode == PH_GAME_VICTORY && 505 key == PH_ACTION_ACCEPT) { 506 *context->mode = PH_GAME_WORLD; 507 context->menu = PH_MENU_NONE; 508 } else if (*context->mode == PH_GAME_WORLD && context->menu == PH_MENU_CHARACTER && 509 ph_menu_back(key)) { 510 context->menu = PH_MENU_NONE; 511 } else if (*context->mode == PH_GAME_WORLD && 512 (context->menu == PH_MENU_NONE || context->menu == PH_MENU_CHARACTER) && 513 key == PH_ACTION_CHARACTER) { 514 context->menu = context->menu == PH_MENU_CHARACTER ? PH_MENU_NONE : PH_MENU_CHARACTER; 515 } else if (*context->mode == PH_GAME_WORLD && 516 (context->menu == PH_MENU_NONE || context->menu == PH_MENU_INVENTORY) && 517 key == PH_ACTION_INVENTORY) { 518 if (context->menu == PH_MENU_INVENTORY) context->menu = PH_MENU_NONE; 519 else { 520 context->menu = PH_MENU_INVENTORY; 521 ph_inventory_open(context->inventory, context->world); 522 } 523 } else if (*context->mode == PH_GAME_WORLD && context->menu == PH_MENU_INVENTORY) { 524 if (ph_menu_direction(key) && 525 context->inventory->mode != PH_INVENTORY_EXAMINE) 526 result |= PH_GAME_MENU_SOUND; 527 if (ph_inventory_input(context->inventory, context->world, key)) 528 context->menu = PH_MENU_NONE; 529 } else if (*context->mode == PH_GAME_WORLD && 530 (context->menu == PH_MENU_NONE || context->menu == PH_MENU_SPELLBOOK) && 531 key == PH_ACTION_SPELLBOOK) { 532 if (context->menu == PH_MENU_SPELLBOOK) context->menu = PH_MENU_NONE; 533 else { 534 context->menu = PH_MENU_SPELLBOOK; 535 ph_spellbook_open(context->spellbook, context->spells); 536 } 537 } else if (*context->mode == PH_GAME_WORLD && context->menu == PH_MENU_SPELLBOOK) { 538 if (ph_menu_direction(key) && 539 context->spellbook->mode != PH_SPELLBOOK_EXAMINE) 540 result |= PH_GAME_MENU_SOUND; 541 if (ph_spellbook_input(context->spellbook, context->world, context->spells, 542 key)) context->menu = PH_MENU_NONE; 543 } else if (*context->mode == PH_GAME_WORLD && context->menu == PH_MENU_SHOP) { 544 if (ph_menu_direction(key) && 545 context->shop->mode != PH_SHOP_TALK) result |= PH_GAME_MENU_SOUND; 546 if (ph_shop_input(context->shop, context->world, context->spells, key)) 547 context->menu = PH_MENU_NONE; 548 } 549 if (menu != context->menu || mode != *context->mode || phase != context->battle->phase) 550 context->time_reset = 1; 551 return result; 552 } 553 554 int 555 ph_game_update(PhGameContext *context, PhInput input, float dt, const char *save_path) 556 { 557 PhWorld *world = context->world; 558 PhGameMode *game = context->mode; 559 PhBattleResult result = PH_BATTLE_CONTINUE; 560 int old_area = world->area_id; 561 int save_requested = 0; 562 563 if (*game == PH_GAME_WORLD && context->menu == PH_MENU_NONE) { 564 ph_world_tick(world, input, dt); 565 if (ph_world_update_portal(world, ph_game_world.areas) < 0) 566 fprintf(stderr, "failed to enter area\n"); 567 if (world->encounter_index >= 0) { 568 if (save_path && ph_save_write(save_path, world, context->spells) < 0) 569 fprintf(stderr, "failed to save pre-combat checkpoint\n"); 570 ph_battle_begin(context->battle, world, world->encounter_index, 571 *context->spells); 572 *game = PH_GAME_TRANSITION; 573 } 574 } 575 if ((*game == PH_GAME_TRANSITION || *game == PH_GAME_BATTLE) && 576 context->menu == PH_MENU_NONE) 577 result = ph_battle_update(context->battle, world, dt, 578 *game == PH_GAME_TRANSITION); 579 if (result == PH_BATTLE_READY) *game = PH_GAME_BATTLE; 580 else if (result == PH_BATTLE_VICTORY) { 581 *game = PH_GAME_VICTORY; 582 save_requested = 1; 583 } 584 else if (result == PH_BATTLE_DEFEAT) *game = PH_GAME_OVER; 585 return save_requested || world->area_id != old_area; 586 } 587 588 void 589 ph_game_interaction(PhGameContext *context) 590 { 591 if (*context->mode == PH_GAME_WORLD && context->menu == PH_MENU_NONE && 592 context->world->interaction_id > 0) { 593 context->menu = ph_interaction_open(context->shop, context->world->interaction_id); 594 context->world->interaction_id = 0; 595 } 596 } 597 598 int 599 ph_game_steps(PhGameContext *context, uint64_t elapsed_ns) 600 { 601 if (context->time_reset || context->menu != PH_MENU_NONE || 602 *context->mode == PH_GAME_VICTORY || *context->mode == PH_GAME_OVER) { 603 context->clock.credit = 0; 604 context->time_reset = 0; 605 return 0; 606 } 607 return ph_clock_steps(&context->clock, elapsed_ns, 608 PH_SIMULATION_HZ, PH_MAX_CATCHUP_STEPS); 609 }