presentation.c (41870B)
1 #include "game/presentation.h" 2 3 #include <stdio.h> 4 #include <string.h> 5 6 #include "config.h" 7 8 #define LEN(a) (sizeof(a) / sizeof(0[a])) 9 10 #if PH_USE_SDL 11 enum { PH_UI_FONT_W = 8 }; 12 13 static int 14 ph_menu_back(SDL_Scancode key) 15 { 16 return key == PH_KEY_MENU_CANCEL || key == PH_KEY_MENU_BACK; 17 } 18 19 static float 20 ph_ui_right(float inset) 21 { 22 return (float)PH_VIEW_W - inset; 23 } 24 25 static float 26 ph_ui_bottom(float inset) 27 { 28 return (float)PH_VIEW_H - inset; 29 } 30 31 static float 32 ph_ui_center_text(const char *text) 33 { 34 return ((float)PH_VIEW_W - (float)strlen(text) * PH_UI_FONT_W) * 0.5f; 35 } 36 37 static float 38 ph_draw_wrapped_text(SDL_Renderer *renderer, float x, float y, 39 float width, float line_height, const char *text) 40 { 41 char line[128]; 42 size_t columns = width > PH_UI_FONT_W ? (size_t)(width / PH_UI_FONT_W) : 1; 43 44 while (text && *text) { 45 size_t remaining; 46 size_t take; 47 size_t i; 48 49 while (*text == ' ') ++text; 50 remaining = strlen(text); 51 take = remaining < columns ? remaining : columns; 52 if (take < remaining) 53 for (i = take; i > 0; --i) 54 if (text[i] == ' ') { 55 take = i; 56 break; 57 } 58 if (take >= sizeof(line)) take = sizeof(line) - 1; 59 memcpy(line, text, take); 60 line[take] = '\0'; 61 SDL_RenderDebugText(renderer, x, y, line); 62 text += take; 63 y += line_height; 64 } 65 return y; 66 } 67 #endif 68 69 #if PH_USE_SDL 70 static void 71 ph_append_bonus(char *text, size_t size, size_t *used, int value, const char *label) 72 { 73 int written; 74 75 if (!value || *used >= size) return; 76 written = snprintf(text + *used, size - *used, "%s%+d %s", 77 *used ? " " : "", value, label); 78 if (written > 0) *used += (size_t)written; 79 } 80 81 static void 82 ph_format_bonuses(char *text, size_t size, PhStats bonuses) 83 { 84 size_t used = 0; 85 86 text[0] = '\0'; 87 ph_append_bonus(text, size, &used, bonuses.max_hp, "HP"); 88 ph_append_bonus(text, size, &used, bonuses.max_mp, "MP"); 89 ph_append_bonus(text, size, &used, bonuses.strength, "STR"); 90 ph_append_bonus(text, size, &used, bonuses.defense, "DEF"); 91 ph_append_bonus(text, size, &used, bonuses.magic, "MAG"); 92 ph_append_bonus(text, size, &used, bonuses.magic_defense, "MDEF"); 93 ph_append_bonus(text, size, &used, bonuses.agility, "AGI"); 94 ph_append_bonus(text, size, &used, bonuses.luck, "LCK"); 95 ph_append_bonus(text, size, &used, bonuses.evasion, "EVA"); 96 ph_append_bonus(text, size, &used, bonuses.accuracy, "ACC"); 97 } 98 99 static void 100 ph_draw_menu(SDL_Renderer *renderer, const char *title, SDL_Scancode key) 101 { 102 SDL_FRect panel = { 12.0f, 12.0f, PH_VIEW_W - 24.0f, PH_VIEW_H - 24.0f }; 103 char text[32]; 104 float x; 105 106 SDL_SetRenderDrawColor(renderer, PH_MENU_COLOR); 107 SDL_RenderFillRect(renderer, &panel); 108 SDL_SetRenderDrawColor(renderer, PH_MENU_BORDER_COLOR); 109 SDL_RenderRect(renderer, &panel); 110 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 111 SDL_RenderDebugText(renderer, 24.0f, 22.0f, title); 112 snprintf(text, sizeof(text), "CLOSE: %s / %s", SDL_GetScancodeName(key), 113 SDL_GetScancodeName(PH_KEY_MENU_BACK)); 114 x = ph_ui_right(20.0f) - (float)strlen(text) * PH_UI_FONT_W; 115 if (x < 24.0f) x = 24.0f; 116 SDL_RenderDebugText(renderer, x, (float)(PH_VIEW_H - 22), text); 117 } 118 119 void 120 ph_options_open(PhOptionsUi *ui) 121 { 122 if (!ui) return; 123 ui->selected = 0; 124 ui->mode = PH_OPTIONS_ROOT; 125 } 126 127 PhOptionsAction 128 ph_options_key(PhOptionsUi *ui, SDL_Scancode key) 129 { 130 int previous = key == PH_KEY_MENU_UP_1 || key == PH_KEY_MENU_UP_2 || 131 key == PH_KEY_MENU_LEFT_1 || key == PH_KEY_MENU_LEFT_2; 132 int next = key == PH_KEY_MENU_DOWN_1 || key == PH_KEY_MENU_DOWN_2 || 133 key == PH_KEY_MENU_RIGHT_1 || key == PH_KEY_MENU_RIGHT_2; 134 135 if (!ui) return PH_OPTIONS_CLOSE; 136 if (ph_menu_back(key)) { 137 if (ui->mode == PH_OPTIONS_SOUND) { 138 ui->mode = PH_OPTIONS_ROOT; 139 ui->selected = 0; 140 return PH_OPTIONS_STAY; 141 } 142 return PH_OPTIONS_CLOSE; 143 } 144 if (previous || next) { 145 ui->selected = (ui->selected + 1) % 2; 146 return PH_OPTIONS_STAY; 147 } 148 if (key != PH_KEY_MENU_ACCEPT) return PH_OPTIONS_STAY; 149 if (ui->mode == PH_OPTIONS_ROOT) { 150 if (ui->selected == 1) return PH_OPTIONS_EXIT; 151 ui->mode = PH_OPTIONS_SOUND; 152 ui->selected = 0; 153 } else if (ui->selected == 0) { 154 ui->music_enabled = !ui->music_enabled; 155 } else { 156 ui->effects_enabled = !ui->effects_enabled; 157 } 158 return PH_OPTIONS_STAY; 159 } 160 161 static void 162 ph_draw_options(SDL_Renderer *renderer, const PhOptionsUi *ui) 163 { 164 static const char *root_options[] = { "SOUND", "EXIT GAME" }; 165 SDL_FRect panel = { PH_VIEW_W * 0.5f - 84.0f, 166 PH_VIEW_H * 0.5f - 58.0f, 168.0f, 116.0f }; 167 char text[48]; 168 int i; 169 170 if (!ui) return; 171 SDL_SetRenderDrawColor(renderer, PH_MENU_COLOR); 172 SDL_RenderFillRect(renderer, &panel); 173 SDL_SetRenderDrawColor(renderer, PH_MENU_BORDER_COLOR); 174 SDL_RenderRect(renderer, &panel); 175 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 176 SDL_RenderDebugText(renderer, panel.x + 12.0f, panel.y + 10.0f, 177 ui->mode == PH_OPTIONS_SOUND ? "SOUND" : "OPTIONS"); 178 for (i = 0; i < 2; ++i) { 179 if (ui->mode == PH_OPTIONS_SOUND) 180 snprintf(text, sizeof(text), "%c %-8s %s", 181 ui->selected == i ? '>' : ' ', i == 0 ? "MUSIC" : "EFFECTS", 182 (i == 0 ? ui->music_enabled : ui->effects_enabled) ? "ON" : "OFF"); 183 else snprintf(text, sizeof(text), "%c %s", 184 ui->selected == i ? '>' : ' ', root_options[i]); 185 SDL_RenderDebugText(renderer, panel.x + 20.0f, 186 panel.y + 38.0f + (float)i * 20.0f, text); 187 } 188 SDL_RenderDebugText(renderer, panel.x + 12.0f, panel.y + 94.0f, 189 "ESC/BKSP BACK"); 190 } 191 192 PhStartAction 193 ph_start_key(PhStartUi *ui, SDL_Scancode key) 194 { 195 int direction = key == PH_KEY_MENU_UP_1 || key == PH_KEY_MENU_UP_2 || 196 key == PH_KEY_MENU_DOWN_1 || key == PH_KEY_MENU_DOWN_2; 197 198 if (!ui) return PH_START_STAY; 199 if (direction) ui->selected = ui->can_continue ? !ui->selected : 1; 200 if (key != PH_KEY_MENU_ACCEPT) return PH_START_STAY; 201 return ui->selected == 0 && ui->can_continue ? 202 PH_START_CONTINUE : PH_START_NEW; 203 } 204 205 static void 206 ph_draw_start(SDL_Renderer *renderer, const PhStartUi *ui) 207 { 208 SDL_FRect panel = { PH_VIEW_W * 0.5f - 82.0f, 209 PH_VIEW_H * 0.5f - 52.0f, 164.0f, 104.0f }; 210 char text[32]; 211 int i; 212 213 if (!ui) return; 214 SDL_SetRenderDrawColor(renderer, PH_MENU_COLOR); 215 SDL_RenderFillRect(renderer, &panel); 216 SDL_SetRenderDrawColor(renderer, PH_MENU_BORDER_COLOR); 217 SDL_RenderRect(renderer, &panel); 218 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 219 SDL_RenderDebugText(renderer, panel.x + 42.0f, panel.y + 12.0f, "PHANTASIA"); 220 for (i = 0; i < 2; ++i) { 221 snprintf(text, sizeof(text), "%c %s", ui->selected == i ? '>' : ' ', 222 i == 0 ? "CONTINUE" : "NEW GAME"); 223 if (i == 0 && !ui->can_continue) 224 SDL_SetRenderDrawColor(renderer, PH_MENU_DISABLED_COLOR); 225 else SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 226 SDL_RenderDebugText(renderer, panel.x + 36.0f, 227 panel.y + 42.0f + (float)i * 18.0f, text); 228 } 229 } 230 231 static void 232 ph_draw_character_sheet(SDL_Renderer *renderer, const PhRenderAssets *assets, 233 const PhWorld *world) 234 { 235 static const char *slot_names[PH_EQUIP_COUNT] = { 236 [PH_EQUIP_WEAPON] = "WEAPON", 237 [PH_EQUIP_ARMOR] = "ARMOR", 238 [PH_EQUIP_CHARM] = "CHARM", 239 }; 240 const PhEntity *player = ph_world_player(world); 241 const PhEntityDef *def; 242 PhStats stats; 243 SDL_Texture *texture; 244 SDL_FRect avatar = { 32.0f, 46.0f, PH_TILE_SIZE * 3.0f, 245 PH_TILE_SIZE * 3.0f }; 246 char text[128]; 247 int slot; 248 249 if (!player || !(def = ph_world_entity_def(world, player->type_id))) return; 250 stats = ph_world_entity_stats(world, player); 251 ph_draw_menu(renderer, "CHARACTER", PH_KEY_CHARACTER); 252 SDL_RenderDebugText(renderer, 104.0f, 38.0f, def->name); 253 254 texture = ph_render_asset(assets, def->asset_id); 255 if (texture) 256 ph_render_sprite(renderer, texture, 257 def->sprite_tiles[PH_SPRITE_DOWN][0][0], 258 def->sprite_tiles[PH_SPRITE_DOWN][0][1], 259 avatar, SDL_FLIP_NONE); 260 261 snprintf(text, sizeof(text), "HP %d / %d", player->vitals.hp, stats.max_hp); 262 SDL_RenderDebugText(renderer, 104.0f, 54.0f, text); 263 snprintf(text, sizeof(text), "MP %d / %d", player->vitals.mp, stats.max_mp); 264 SDL_RenderDebugText(renderer, 104.0f, 66.0f, text); 265 snprintf(text, sizeof(text), "STR %d", stats.strength); 266 SDL_RenderDebugText(renderer, 104.0f, 78.0f, text); 267 snprintf(text, sizeof(text), "DEF %d", stats.defense); 268 SDL_RenderDebugText(renderer, 104.0f, 90.0f, text); 269 snprintf(text, sizeof(text), "MAG %d", stats.magic); 270 SDL_RenderDebugText(renderer, ph_ui_right(88.0f), 54.0f, text); 271 snprintf(text, sizeof(text), "MDEF %d", stats.magic_defense); 272 SDL_RenderDebugText(renderer, ph_ui_right(88.0f), 66.0f, text); 273 snprintf(text, sizeof(text), "AGI %d", stats.agility); 274 SDL_RenderDebugText(renderer, ph_ui_right(88.0f), 78.0f, text); 275 snprintf(text, sizeof(text), "LCK %d", stats.luck); 276 SDL_RenderDebugText(renderer, ph_ui_right(88.0f), 90.0f, text); 277 snprintf(text, sizeof(text), "EVA %d", stats.evasion); 278 SDL_RenderDebugText(renderer, ph_ui_right(88.0f), 102.0f, text); 279 snprintf(text, sizeof(text), "ACC %d", stats.accuracy); 280 SDL_RenderDebugText(renderer, ph_ui_right(88.0f), 114.0f, text); 281 SDL_RenderDebugText(renderer, 24.0f, 128.0f, "EQUIPPED"); 282 283 for (slot = PH_EQUIP_WEAPON; slot < PH_EQUIP_COUNT; ++slot) { 284 const PhItemDef *item = ph_world_item_def(world, player->equipment[slot]); 285 SDL_Texture *icon_texture; 286 float y = 144.0f + (float)(slot - PH_EQUIP_WEAPON) * 25.0f; 287 288 snprintf(text, sizeof(text), "%s %s", slot_names[slot], item ? item->name : "-"); 289 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 290 SDL_RenderDebugText(renderer, 48.0f, y, text); 291 if (!item) continue; 292 icon_texture = ph_render_asset(assets, item->asset_id); 293 if (icon_texture) { 294 SDL_FRect icon = { 24.0f, y - 4.0f, PH_TILE_SIZE, PH_TILE_SIZE }; 295 ph_render_sprite(renderer, icon_texture, 296 item->sprite_tile_x, item->sprite_tile_y, icon, SDL_FLIP_NONE); 297 } 298 ph_format_bonuses(text, sizeof(text), item->bonuses); 299 SDL_SetRenderDrawColor(renderer, PH_MENU_BONUS_COLOR); 300 SDL_RenderDebugText(renderer, 48.0f, y + 10.0f, text); 301 } 302 } 303 304 int 305 ph_inventory_step(const PhWorld *world, int selected, int direction) 306 { 307 int i = selected < 0 ? (direction > 0 ? -1 : 0) : selected; 308 int step; 309 310 for (step = 0; step < world->content.item_count; ++step) { 311 i += direction; 312 if (i < 0) i = world->content.item_count - 1; 313 if (i >= world->content.item_count) i = 0; 314 if (world->inventory[i] > 0) return i; 315 } 316 return -1; 317 } 318 319 void 320 ph_inventory_open(PhInventoryUi *ui, const PhWorld *world) 321 { 322 memset(ui, 0, sizeof(*ui)); 323 ui->selected = ph_inventory_step(world, -1, 1); 324 } 325 326 static void 327 ph_inventory_reselect(PhInventoryUi *ui, const PhWorld *world) 328 { 329 if (ui->selected >= 0 && world->inventory[ui->selected] > 0) return; 330 ui->selected = ph_inventory_step(world, ui->selected, 1); 331 } 332 333 static void 334 ph_inventory_action(PhInventoryUi *ui, PhWorld *world, int action) 335 { 336 const PhItemDef *item; 337 const char *name; 338 int result = -1; 339 340 if (ui->selected < 0) return; 341 item = &world->content.items[ui->selected]; 342 name = item->name ? item->name : "Item"; 343 if (action == PH_INVENTORY_EQUIP) 344 result = ph_world_equip_inventory_item(world, world->player_index, item->id); 345 else if (action == PH_INVENTORY_USE) 346 result = ph_world_use_inventory_item(world, world->player_index, item->id); 347 else if (action == PH_INVENTORY_DROP) 348 result = ph_world_drop_inventory_item(world, world->player_index, item->id); 349 else { 350 ui->mode = PH_INVENTORY_EXAMINE; 351 ui->status[0] = '\0'; 352 return; 353 } 354 355 if (result < 0) { 356 snprintf(ui->status, sizeof(ui->status), "Cannot %s %s.", 357 action == PH_INVENTORY_EQUIP ? "equip" : 358 action == PH_INVENTORY_USE ? "use" : "drop", name); 359 return; 360 } 361 snprintf(ui->status, sizeof(ui->status), "%s %s.", 362 action == PH_INVENTORY_EQUIP ? "Equipped" : 363 action == PH_INVENTORY_USE ? "Used" : "Dropped", name); 364 ui->mode = PH_INVENTORY_BROWSE; 365 ph_inventory_reselect(ui, world); 366 } 367 368 static int 369 ph_inventory_action_enabled(const PhItemDef *item, int action) 370 { 371 if (action == PH_INVENTORY_EQUIP) return item->equip_slot != PH_EQUIP_NONE; 372 if (action == PH_INVENTORY_USE) return item->use != PH_ITEM_USE_NONE; 373 return 1; 374 } 375 376 static void 377 ph_inventory_action_order(const PhItemDef *item, int actions[PH_INVENTORY_ACTION_COUNT]) 378 { 379 if (item->equip_slot != PH_EQUIP_NONE) { 380 actions[0] = PH_INVENTORY_EQUIP; 381 actions[1] = PH_INVENTORY_USE; 382 } else if (item->use != PH_ITEM_USE_NONE) { 383 actions[0] = PH_INVENTORY_USE; 384 actions[1] = PH_INVENTORY_EQUIP; 385 } else { 386 actions[0] = PH_INVENTORY_DROP; 387 actions[1] = PH_INVENTORY_EXAMINE_ACTION; 388 } 389 if (actions[0] < PH_INVENTORY_DROP) { 390 actions[2] = PH_INVENTORY_DROP; 391 actions[3] = PH_INVENTORY_EXAMINE_ACTION; 392 } else { 393 actions[2] = PH_INVENTORY_EQUIP; 394 actions[3] = PH_INVENTORY_USE; 395 } 396 } 397 398 static int 399 ph_inventory_action_step(const PhItemDef *item, int selected, int direction) 400 { 401 int actions[PH_INVENTORY_ACTION_COUNT]; 402 int position = 0; 403 int step; 404 405 ph_inventory_action_order(item, actions); 406 while (position < PH_INVENTORY_ACTION_COUNT && actions[position] != selected) ++position; 407 for (step = 0; step < PH_INVENTORY_ACTION_COUNT; ++step) { 408 position = (position + (direction < 0 ? 409 PH_INVENTORY_ACTION_COUNT - 1 : 1)) % PH_INVENTORY_ACTION_COUNT; 410 if (ph_inventory_action_enabled(item, actions[position])) return actions[position]; 411 } 412 return selected; 413 } 414 415 static void 416 ph_draw_inventory_popup(SDL_Renderer *renderer, const PhWorld *world, 417 const PhInventoryUi *ui) 418 { 419 static const char *action_names[] = { "EQUIP", "USE", "DROP", "EXAMINE" }; 420 const PhItemDef *item = &world->content.items[ui->selected]; 421 int actions[PH_INVENTORY_ACTION_COUNT]; 422 SDL_FRect popup = ui->mode == PH_INVENTORY_EXAMINE ? 423 (SDL_FRect){ 40.0f, 48.0f, PH_VIEW_W - 64.0f, 124.0f } : 424 (SDL_FRect){ PH_VIEW_W - 164.0f, 48.0f, 140.0f, 124.0f }; 425 char text[128]; 426 float x = ui->mode == PH_INVENTORY_EXAMINE ? 52.0f : ph_ui_right(152.0f); 427 int i; 428 429 SDL_SetRenderDrawColor(renderer, PH_MENU_COLOR); 430 SDL_RenderFillRect(renderer, &popup); 431 SDL_SetRenderDrawColor(renderer, PH_MENU_BORDER_COLOR); 432 SDL_RenderRect(renderer, &popup); 433 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 434 if (ui->mode == PH_INVENTORY_EXAMINE) { 435 SDL_RenderDebugText(renderer, x, 58.0f, 436 item->name ? item->name : "ITEM"); 437 SDL_RenderDebugText(renderer, x, 76.0f, 438 item->description ? item->description : "No description."); 439 snprintf(text, sizeof(text), "SELL %d G", item->sell_value); 440 SDL_RenderDebugText(renderer, x, 96.0f, text); 441 ph_format_bonuses(text, sizeof(text), item->bonuses); 442 if (item->use == PH_ITEM_USE_HEAL) 443 snprintf(text, sizeof(text), "HEALS %d HP", item->use_power); 444 else if (item->use == PH_ITEM_USE_RESTORE_MP) 445 snprintf(text, sizeof(text), "RESTORES %d MP", item->use_power); 446 if (text[0]) SDL_RenderDebugText(renderer, x, 112.0f, text); 447 SDL_RenderDebugText(renderer, x, 148.0f, "ENTER BACK"); 448 return; 449 } 450 451 SDL_RenderDebugText(renderer, ph_ui_right(152.0f), 58.0f, "ITEM ACTION"); 452 ph_inventory_action_order(item, actions); 453 for (i = 0; i < PH_INVENTORY_ACTION_COUNT; ++i) { 454 int action = actions[i]; 455 456 if (ph_inventory_action_enabled(item, action)) 457 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 458 else 459 SDL_SetRenderDrawColor(renderer, PH_MENU_DISABLED_COLOR); 460 snprintf(text, sizeof(text), "%c %s", 461 action == ui->option ? '>' : ' ', action_names[action]); 462 SDL_RenderDebugText(renderer, ph_ui_right(152.0f), 463 78.0f + (float)i * 18.0f, text); 464 } 465 } 466 467 static void 468 ph_draw_inventory(SDL_Renderer *renderer, const PhRenderAssets *assets, 469 const PhWorld *world, const PhInventoryUi *ui) 470 { 471 const PhEntity *player = ph_world_player(world); 472 char text[128]; 473 int selected_rank = 0; 474 int total = 0; 475 int rank = 0; 476 int page; 477 int i; 478 479 for (i = 0; i < world->content.item_count; ++i) 480 if (world->inventory[i] > 0) { 481 if (i == ui->selected) selected_rank = total; 482 ++total; 483 } 484 page = selected_rank / 5; 485 ph_draw_menu(renderer, "INVENTORY", PH_KEY_INVENTORY); 486 if (player) { 487 snprintf(text, sizeof(text), "GOLD %d", player->gold); 488 SDL_RenderDebugText(renderer, ph_ui_right(96.0f), 22.0f, text); 489 } 490 for (i = 0; i < world->content.item_count; ++i) { 491 const PhItemDef *item = &world->content.items[i]; 492 SDL_Texture *texture; 493 float y; 494 495 if (world->inventory[i] <= 0) continue; 496 if (rank < page * 5 || rank >= page * 5 + 5) { 497 ++rank; 498 continue; 499 } 500 y = 48.0f + (float)(rank++ - page * 5) * 27.0f; 501 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 502 SDL_RenderDebugText(renderer, 20.0f, y, i == ui->selected ? ">" : " "); 503 texture = ph_render_asset(assets, item->asset_id); 504 if (texture) { 505 SDL_FRect icon = { 32.0f, y - 4.0f, PH_TILE_SIZE, PH_TILE_SIZE }; 506 ph_render_sprite(renderer, texture, 507 item->sprite_tile_x, item->sprite_tile_y, icon, SDL_FLIP_NONE); 508 } 509 snprintf(text, sizeof(text), "%s x%d", 510 item->name ? item->name : "Unknown", world->inventory[i]); 511 SDL_RenderDebugText(renderer, 56.0f, y, text); 512 ph_format_bonuses(text, sizeof(text), item->bonuses); 513 if (!text[0] && item->use == PH_ITEM_USE_HEAL) 514 snprintf(text, sizeof(text), "HEALS %d HP", item->use_power); 515 if (!text[0] && item->use == PH_ITEM_USE_RESTORE_MP) 516 snprintf(text, sizeof(text), "RESTORES %d MP", item->use_power); 517 if (!text[0]) snprintf(text, sizeof(text), "SELL %d G", item->sell_value); 518 SDL_SetRenderDrawColor(renderer, PH_MENU_BONUS_COLOR); 519 SDL_RenderDebugText(renderer, 56.0f, y + 10.0f, text); 520 } 521 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 522 if (!total) SDL_RenderDebugText(renderer, 24.0f, 48.0f, "EMPTY"); 523 if (total > 5) { 524 snprintf(text, sizeof(text), "PAGE %d/%d", page + 1, (total + 4) / 5); 525 SDL_RenderDebugText(renderer, 24.0f, ph_ui_bottom(56.0f), text); 526 } 527 if (ui->status[0]) 528 SDL_RenderDebugText(renderer, 24.0f, ph_ui_bottom(40.0f), ui->status); 529 snprintf(text, sizeof(text), "%s SELECT", 530 SDL_GetScancodeName(PH_KEY_MENU_ACCEPT)); 531 SDL_RenderDebugText(renderer, 24.0f, ph_ui_bottom(24.0f), text); 532 if (ui->selected >= 0 && ui->mode != PH_INVENTORY_BROWSE) 533 ph_draw_inventory_popup(renderer, world, ui); 534 } 535 536 int 537 ph_inventory_key(PhInventoryUi *ui, PhWorld *world, SDL_Scancode key) 538 { 539 int previous = key == PH_KEY_MENU_UP_1 || key == PH_KEY_MENU_UP_2 || 540 key == PH_KEY_MENU_LEFT_1 || key == PH_KEY_MENU_LEFT_2; 541 int next = key == PH_KEY_MENU_DOWN_1 || key == PH_KEY_MENU_DOWN_2 || 542 key == PH_KEY_MENU_RIGHT_1 || key == PH_KEY_MENU_RIGHT_2; 543 544 if (ph_menu_back(key)) { 545 if (ui->mode == PH_INVENTORY_EXAMINE) 546 ui->mode = PH_INVENTORY_ACTIONS; 547 else if (ui->mode == PH_INVENTORY_ACTIONS) 548 ui->mode = PH_INVENTORY_BROWSE; 549 else 550 return 1; 551 ui->status[0] = '\0'; 552 return 0; 553 } 554 if (key == PH_KEY_MENU_ACCEPT) { 555 if (ui->mode == PH_INVENTORY_BROWSE && ui->selected >= 0) { 556 int actions[PH_INVENTORY_ACTION_COUNT]; 557 558 ph_inventory_action_order(&world->content.items[ui->selected], actions); 559 ui->mode = PH_INVENTORY_ACTIONS; 560 ui->option = actions[0]; 561 ui->status[0] = '\0'; 562 } else if (ui->mode == PH_INVENTORY_ACTIONS) { 563 ph_inventory_action(ui, world, ui->option); 564 } else if (ui->mode == PH_INVENTORY_EXAMINE) { 565 ui->mode = PH_INVENTORY_ACTIONS; 566 } 567 return 0; 568 } 569 if ((!previous && !next) || ui->mode == PH_INVENTORY_EXAMINE) return 0; 570 if (ui->mode == PH_INVENTORY_ACTIONS) { 571 ui->option = ph_inventory_action_step(&world->content.items[ui->selected], 572 ui->option, previous ? -1 : 1); 573 } else { 574 ui->selected = ph_inventory_step(world, ui->selected, previous ? -1 : 1); 575 ui->status[0] = '\0'; 576 } 577 return 0; 578 } 579 580 void 581 ph_spellbook_open(PhSpellbookUi *ui, const PhSpellBook *spells) 582 { 583 memset(ui, 0, sizeof(*ui)); 584 ui->selected = spells && spells->learned_count > 0 ? 0 : -1; 585 } 586 587 static int 588 ph_spellbook_action_enabled(const PhSpellDef *spell, int action) 589 { 590 return action == PH_SPELLBOOK_EXAMINE_ACTION || 591 (spell && spell->field_use != PH_SPELL_FIELD_NONE); 592 } 593 594 static int 595 ph_spellbook_action_step(const PhSpellDef *spell, int selected, int direction) 596 { 597 int action; 598 int step; 599 600 for (step = 1; step <= PH_SPELLBOOK_ACTION_COUNT; ++step) { 601 action = (selected + (direction < 0 ? 602 PH_SPELLBOOK_ACTION_COUNT - step : step)) % PH_SPELLBOOK_ACTION_COUNT; 603 if (ph_spellbook_action_enabled(spell, action)) return action; 604 } 605 return selected; 606 } 607 608 static void 609 ph_spellbook_action(PhSpellbookUi *ui, PhWorld *world, PhSpellBook *spells) 610 { 611 const PhSpellDef *spell; 612 int amount; 613 614 if (ui->selected < 0 || ui->selected >= spells->learned_count) return; 615 spell = ph_spellbook_def(spells, spells->learned[ui->selected]); 616 if (!spell) return; 617 if (ui->option == PH_SPELLBOOK_EXAMINE_ACTION) { 618 ui->mode = PH_SPELLBOOK_EXAMINE; 619 ui->status[0] = '\0'; 620 return; 621 } 622 amount = ph_spellbook_use(spells, world, world->player_index, spell->id); 623 if (amount < 0) 624 snprintf(ui->status, sizeof(ui->status), "Cannot use %s.", spell->name); 625 else { 626 snprintf(ui->status, sizeof(ui->status), "Restored %d HP.", amount); 627 ui->spell_used = 1; 628 } 629 ui->mode = PH_SPELLBOOK_BROWSE; 630 } 631 632 static void 633 ph_draw_spellbook_popup(SDL_Renderer *renderer, const PhSpellBook *spells, 634 const PhSpellbookUi *ui) 635 { 636 const PhSpellDef *spell = ph_spellbook_def(spells, 637 spells->learned[ui->selected]); 638 SDL_FRect popup = ui->mode == PH_SPELLBOOK_EXAMINE ? 639 (SDL_FRect){ 40.0f, 48.0f, PH_VIEW_W - 64.0f, 124.0f } : 640 (SDL_FRect){ PH_VIEW_W - 164.0f, 48.0f, 140.0f, 82.0f }; 641 char text[96]; 642 int action; 643 644 if (!spell) return; 645 SDL_SetRenderDrawColor(renderer, PH_MENU_COLOR); 646 SDL_RenderFillRect(renderer, &popup); 647 SDL_SetRenderDrawColor(renderer, PH_MENU_BORDER_COLOR); 648 SDL_RenderRect(renderer, &popup); 649 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 650 if (ui->mode == PH_SPELLBOOK_EXAMINE) { 651 SDL_RenderDebugText(renderer, 52.0f, 58.0f, spell->name); 652 ph_draw_wrapped_text(renderer, 52.0f, 76.0f, popup.w - 24.0f, 12.0f, 653 spell->description ? spell->description : "No description."); 654 snprintf(text, sizeof(text), "%d MP POWER %d DELAY %d%%", 655 spell->mp_cost, spell->power, spell->delay_percent); 656 SDL_RenderDebugText(renderer, 52.0f, 126.0f, text); 657 SDL_RenderDebugText(renderer, 52.0f, 148.0f, "ENTER BACK"); 658 return; 659 } 660 SDL_RenderDebugText(renderer, ph_ui_right(152.0f), 58.0f, "SPELL ACTION"); 661 for (action = 0; action < PH_SPELLBOOK_ACTION_COUNT; ++action) { 662 if (ph_spellbook_action_enabled(spell, action)) 663 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 664 else 665 SDL_SetRenderDrawColor(renderer, PH_MENU_DISABLED_COLOR); 666 snprintf(text, sizeof(text), "%c %s", ui->option == action ? '>' : ' ', 667 action == PH_SPELLBOOK_USE ? "USE" : "EXAMINE"); 668 SDL_RenderDebugText(renderer, ph_ui_right(152.0f), 669 78.0f + (float)action * 18.0f, text); 670 } 671 } 672 673 static void 674 ph_draw_spellbook(SDL_Renderer *renderer, const PhWorld *world, 675 const PhSpellBook *spells, const PhSpellbookUi *ui) 676 { 677 const PhEntity *player = ph_world_player(world); 678 PhStats stats = ph_world_entity_stats(world, player); 679 char text[96]; 680 int page = ui->selected < 0 ? 0 : ui->selected / 6; 681 int i; 682 683 ph_draw_menu(renderer, "MAGICK BOOK", PH_KEY_SPELLBOOK); 684 if (player) { 685 snprintf(text, sizeof(text), "MP %d/%d", player->vitals.mp, stats.max_mp); 686 SDL_RenderDebugText(renderer, ph_ui_right(96.0f), 22.0f, text); 687 } 688 for (i = page * 6; i < spells->learned_count && i < page * 6 + 6; ++i) { 689 const PhSpellDef *spell = ph_spellbook_def(spells, spells->learned[i]); 690 float y = 48.0f + (float)(i - page * 6) * 24.0f; 691 692 if (!spell) continue; 693 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 694 snprintf(text, sizeof(text), "%c %-14s %2d MP", 695 ui->selected == i ? '>' : ' ', spell->name, spell->mp_cost); 696 SDL_RenderDebugText(renderer, 24.0f, y, text); 697 SDL_SetRenderDrawColor(renderer, PH_MENU_BONUS_COLOR); 698 snprintf(text, sizeof(text), "%s POWER %d DELAY %d%%", 699 spell->field_use != PH_SPELL_FIELD_NONE ? "FIELD" : "BATTLE", 700 spell->power, spell->delay_percent); 701 SDL_RenderDebugText(renderer, 40.0f, y + 10.0f, text); 702 } 703 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 704 if (spells->learned_count <= 0) 705 SDL_RenderDebugText(renderer, 24.0f, 48.0f, "NO SPELLS LEARNED"); 706 if (spells->learned_count > 6) { 707 snprintf(text, sizeof(text), "PAGE %d/%d", page + 1, 708 (spells->learned_count + 5) / 6); 709 SDL_RenderDebugText(renderer, 24.0f, ph_ui_bottom(56.0f), text); 710 } 711 if (ui->status[0]) 712 SDL_RenderDebugText(renderer, 24.0f, ph_ui_bottom(40.0f), ui->status); 713 SDL_RenderDebugText(renderer, 24.0f, ph_ui_bottom(24.0f), "ENTER SELECT"); 714 if (ui->selected >= 0 && ui->mode != PH_SPELLBOOK_BROWSE) 715 ph_draw_spellbook_popup(renderer, spells, ui); 716 } 717 718 int 719 ph_spellbook_key(PhSpellbookUi *ui, PhWorld *world, PhSpellBook *spells, 720 SDL_Scancode key) 721 { 722 int previous = key == PH_KEY_MENU_UP_1 || key == PH_KEY_MENU_UP_2 || 723 key == PH_KEY_MENU_LEFT_1 || key == PH_KEY_MENU_LEFT_2; 724 int next = key == PH_KEY_MENU_DOWN_1 || key == PH_KEY_MENU_DOWN_2 || 725 key == PH_KEY_MENU_RIGHT_1 || key == PH_KEY_MENU_RIGHT_2; 726 const PhSpellDef *spell = ui->selected >= 0 && 727 ui->selected < spells->learned_count ? ph_spellbook_def(spells, 728 spells->learned[ui->selected]) : NULL; 729 730 if (ph_menu_back(key)) { 731 if (ui->mode == PH_SPELLBOOK_EXAMINE) 732 ui->mode = PH_SPELLBOOK_ACTIONS; 733 else if (ui->mode == PH_SPELLBOOK_ACTIONS) 734 ui->mode = PH_SPELLBOOK_BROWSE; 735 else return 1; 736 ui->status[0] = '\0'; 737 return 0; 738 } 739 if (key == PH_KEY_MENU_ACCEPT) { 740 if (ui->mode == PH_SPELLBOOK_BROWSE && spell) { 741 ui->mode = PH_SPELLBOOK_ACTIONS; 742 ui->option = ph_spellbook_action_enabled(spell, PH_SPELLBOOK_USE) ? 743 PH_SPELLBOOK_USE : PH_SPELLBOOK_EXAMINE_ACTION; 744 ui->status[0] = '\0'; 745 } else if (ui->mode == PH_SPELLBOOK_ACTIONS) { 746 ph_spellbook_action(ui, world, spells); 747 } else if (ui->mode == PH_SPELLBOOK_EXAMINE) { 748 ui->mode = PH_SPELLBOOK_ACTIONS; 749 } 750 return 0; 751 } 752 if ((!previous && !next) || ui->mode == PH_SPELLBOOK_EXAMINE) return 0; 753 if (ui->mode == PH_SPELLBOOK_ACTIONS) { 754 ui->option = ph_spellbook_action_step(spell, ui->option, 755 previous ? -1 : 1); 756 } else if (spells->learned_count > 0) { 757 ui->selected = (ui->selected + (previous ? 758 spells->learned_count - 1 : 1)) % spells->learned_count; 759 ui->status[0] = '\0'; 760 } 761 return 0; 762 } 763 764 void 765 ph_shop_open(PhShopUi *ui, int shop_id) 766 { 767 memset(ui, 0, sizeof(*ui)); 768 ui->shop_id = shop_id; 769 } 770 771 PhMenu 772 ph_interaction_open(PhShopUi *shop_ui, int interaction_id) 773 { 774 const PhInteractionDef *interaction = ph_game_interaction_def(interaction_id); 775 776 if (!interaction) return PH_MENU_NONE; 777 if (interaction->kind == PH_INTERACTION_SHOP && 778 ph_game_shop_def(interaction->content_id)) { 779 ph_shop_open(shop_ui, interaction->content_id); 780 return PH_MENU_SHOP; 781 } 782 return PH_MENU_NONE; 783 } 784 785 int 786 ph_shop_key(PhShopUi *ui, PhWorld *world, PhSpellBook *spells, SDL_Scancode key) 787 { 788 const PhShopDef *shop = ph_game_shop_def(ui->shop_id); 789 int previous = key == PH_KEY_MENU_UP_1 || key == PH_KEY_MENU_UP_2 || 790 key == PH_KEY_MENU_LEFT_1 || key == PH_KEY_MENU_LEFT_2; 791 int next = key == PH_KEY_MENU_DOWN_1 || key == PH_KEY_MENU_DOWN_2 || 792 key == PH_KEY_MENU_RIGHT_1 || key == PH_KEY_MENU_RIGHT_2; 793 794 if (!shop) return 1; 795 if (ph_menu_back(key)) { 796 if (ui->mode == PH_SHOP_ROOT) return 1; 797 ui->mode = PH_SHOP_ROOT; 798 ui->selected = 0; 799 ui->status[0] = '\0'; 800 return 0; 801 } 802 if (ui->mode == PH_SHOP_TALK) { 803 if (key == PH_KEY_MENU_ACCEPT) ui->mode = PH_SHOP_ROOT; 804 return 0; 805 } 806 if (previous || next) { 807 int count = ui->mode == PH_SHOP_ROOT ? 808 (shop->kind == PH_SHOP_ITEMS ? 3 : 2) : 809 ui->mode == PH_SHOP_BUY ? shop->stock_count : world->content.item_count; 810 811 if (ui->mode == PH_SHOP_SELL) { 812 ui->selected = ph_inventory_step(world, ui->selected, 813 previous ? -1 : 1); 814 } else if (count > 0) { 815 ui->selected = (ui->selected + (previous ? count - 1 : 1)) % count; 816 } 817 ui->status[0] = '\0'; 818 return 0; 819 } 820 if (key != PH_KEY_MENU_ACCEPT) return 0; 821 if (ui->mode == PH_SHOP_ROOT) { 822 ui->mode = shop->kind == PH_SHOP_ITEMS ? 823 (PhShopMode)(ui->selected + 1) : 824 ui->selected == 0 ? PH_SHOP_BUY : PH_SHOP_TALK; 825 ui->selected = ui->mode == PH_SHOP_SELL ? 826 ph_inventory_step(world, -1, 1) : 0; 827 return 0; 828 } 829 if (ui->mode == PH_SHOP_BUY && ui->selected < shop->stock_count) { 830 if (shop->kind == PH_SHOP_SPELLS) { 831 const PhSpellDef *spell = ph_spellbook_def(spells, 832 shop->stock[ui->selected]); 833 834 if (!spell) return 0; 835 if (ph_spellbook_has(spells, spell->id)) 836 snprintf(ui->status, sizeof(ui->status), "Already learned."); 837 else if (ph_spellbook_buy(spells, world, world->player_index, 838 spell->id) < 0) 839 snprintf(ui->status, sizeof(ui->status), "Not enough gold."); 840 else 841 snprintf(ui->status, sizeof(ui->status), "Learned %s.", spell->name); 842 } else { 843 const PhItemDef *item = ph_world_item_def(world, 844 shop->stock[ui->selected]); 845 846 if (!item || ph_world_buy_item(world, world->player_index, item->id) < 0) 847 snprintf(ui->status, sizeof(ui->status), "Not enough gold."); 848 else 849 snprintf(ui->status, sizeof(ui->status), "Bought %s.", item->name); 850 } 851 } else if (ui->mode == PH_SHOP_SELL && ui->selected >= 0) { 852 const PhItemDef *item = &world->content.items[ui->selected]; 853 854 if (ph_world_sell_item(world, world->player_index, item->id) < 0) 855 snprintf(ui->status, sizeof(ui->status), "Cannot sell that."); 856 else { 857 snprintf(ui->status, sizeof(ui->status), "Sold %s.", item->name); 858 if (world->inventory[ui->selected] <= 0) 859 ui->selected = ph_inventory_step(world, ui->selected, 1); 860 } 861 } 862 return 0; 863 } 864 865 static void 866 ph_draw_shop(SDL_Renderer *renderer, const PhWorld *world, 867 const PhSpellBook *spells, const PhShopUi *ui) 868 { 869 static const char *item_options[] = { "BUY", "SELL", "TALK" }; 870 static const char *magick_options[] = { "LEARN", "TALK" }; 871 const PhShopDef *shop = ph_game_shop_def(ui->shop_id); 872 const PhEntity *player = ph_world_player(world); 873 char text[128]; 874 int i; 875 876 if (!shop || !player) return; 877 ph_draw_menu(renderer, shop->name, PH_KEY_MENU_CANCEL); 878 snprintf(text, sizeof(text), "GOLD %d", player->gold); 879 SDL_RenderDebugText(renderer, ph_ui_right(96.0f), 38.0f, text); 880 if (ui->mode == PH_SHOP_ROOT) { 881 const char **options = shop->kind == PH_SHOP_ITEMS ? 882 item_options : magick_options; 883 int option_count = shop->kind == PH_SHOP_ITEMS ? 884 (int)LEN(item_options) : (int)LEN(magick_options); 885 886 for (i = 0; i < option_count; ++i) { 887 snprintf(text, sizeof(text), "%c %s", ui->selected == i ? '>' : ' ', 888 options[i]); 889 SDL_RenderDebugText(renderer, 32.0f, 56.0f + (float)i * 20.0f, text); 890 } 891 SDL_RenderDebugText(renderer, 32.0f, 132.0f, 892 shop->kind == PH_SHOP_ITEMS ? "Welcome, traveler." : 893 "Choose your lesson."); 894 } else if (ui->mode == PH_SHOP_BUY) { 895 SDL_RenderDebugText(renderer, 24.0f, 42.0f, 896 shop->kind == PH_SHOP_ITEMS ? "BUY" : "MAGICK"); 897 for (i = 0; i < shop->stock_count; ++i) { 898 if (shop->kind == PH_SHOP_SPELLS) { 899 const PhSpellDef *spell = ph_spellbook_def(spells, shop->stock[i]); 900 901 if (!spell) continue; 902 if (ph_spellbook_has(spells, spell->id)) 903 snprintf(text, sizeof(text), "%c %-12s LEARNED", 904 ui->selected == i ? '>' : ' ', spell->name); 905 else 906 snprintf(text, sizeof(text), "%c %-12s %4d G %2d MP", 907 ui->selected == i ? '>' : ' ', spell->name, 908 spell->buy_value, spell->mp_cost); 909 } else { 910 const PhItemDef *item = ph_world_item_def(world, shop->stock[i]); 911 912 if (!item) continue; 913 snprintf(text, sizeof(text), "%c %-18s %d G", 914 ui->selected == i ? '>' : ' ', item->name, item->buy_value); 915 } 916 SDL_RenderDebugText(renderer, 24.0f, 62.0f + (float)i * 20.0f, text); 917 } 918 } else if (ui->mode == PH_SHOP_SELL) { 919 int row = 0; 920 921 SDL_RenderDebugText(renderer, 24.0f, 42.0f, "SELL"); 922 for (i = 0; i < world->content.item_count; ++i) { 923 const PhItemDef *item = &world->content.items[i]; 924 925 if (world->inventory[i] <= 0) continue; 926 snprintf(text, sizeof(text), "%c %-16s x%d %d G", 927 ui->selected == i ? '>' : ' ', item->name, world->inventory[i], 928 item->sell_value); 929 SDL_RenderDebugText(renderer, 24.0f, 62.0f + (float)row++ * 18.0f, text); 930 } 931 if (!row) SDL_RenderDebugText(renderer, 24.0f, 62.0f, "Nothing to sell."); 932 } else { 933 float y = 48.0f; 934 float width = ph_ui_right(24.0f) - 24.0f; 935 936 for (i = 0; i < PH_SHOP_TALK_LINES; ++i) { 937 y = ph_draw_wrapped_text(renderer, 24.0f, y, width, 12.0f, 938 shop->talk[i]); 939 y += 6.0f; 940 } 941 SDL_RenderDebugText(renderer, 24.0f, y + 8.0f, "ENTER / ESC BACK"); 942 } 943 if (ui->status[0]) 944 SDL_RenderDebugText(renderer, 24.0f, ph_ui_bottom(36.0f), ui->status); 945 } 946 947 static void 948 ph_draw_battle_entity(SDL_Renderer *renderer, const PhRenderAssets *assets, 949 const PhWorld *world, int entity_index, SDL_FRect rect) 950 { 951 const PhEntity *entity = &world->entities[entity_index]; 952 const PhEntityDef *def = ph_world_entity_def(world, entity->type_id); 953 SDL_Texture *texture = NULL; 954 int x; 955 int y; 956 SDL_FlipMode flip; 957 958 if (!def) return; 959 texture = ph_render_asset(assets, def->asset_id); 960 if (def->kind == PH_ENTITY_PLAYER) { 961 x = def->sprite_tiles[PH_SPRITE_SIDE][0][0]; 962 y = def->sprite_tiles[PH_SPRITE_SIDE][0][1]; 963 flip = SDL_FLIP_NONE; 964 } else { 965 ph_render_entity_frame(def, entity, &x, &y, &flip); 966 } 967 if (texture) ph_render_sprite(renderer, texture, x, y, rect, flip); 968 else { 969 if (def->kind == PH_ENTITY_MONSTER) 970 SDL_SetRenderDrawColor(renderer, PH_MONSTER_COLOR); 971 else 972 SDL_SetRenderDrawColor(renderer, PH_NPC_COLOR); 973 SDL_RenderFillRect(renderer, &rect); 974 } 975 } 976 977 static void 978 ph_draw_battle(SDL_Renderer *renderer, const PhRenderAssets *assets, 979 const PhWorld *world, const PhBattle *battle) 980 { 981 static const char *commands[PH_BATTLE_COMMAND_COUNT] = { 982 [PH_BATTLE_FIGHT] = "FIGHT", 983 [PH_BATTLE_MAGICK] = "MAGICK", 984 [PH_BATTLE_ITEM] = "ITEM", 985 }; 986 const PhEntity *player = ph_world_player(world); 987 const PhEntity *enemy = &world->entities[battle->enemy_index]; 988 const PhEntityDef *enemy_def = ph_world_entity_def(world, enemy->type_id); 989 PhStats player_stats = ph_world_entity_stats(world, player); 990 PhStats enemy_stats = ph_world_entity_stats(world, enemy); 991 int order[PH_BATTLE_ORDER_COUNT]; 992 SDL_FRect ground = { 0.0f, PH_VIEW_H - 136.0f, PH_VIEW_W, 72.0f }; 993 SDL_FRect panel = { 8.0f, PH_VIEW_H - 64.0f, PH_VIEW_W - 16.0f, 56.0f }; 994 SDL_FRect spell_panel = { PH_VIEW_W * 0.5f - 88.0f, 995 PH_VIEW_H - 116.0f, 176.0f, 996 24.0f + (float)battle->spells.learned_count * 12.0f }; 997 SDL_FRect enemy_rect = { 56.0f + ph_battle_lunge(battle, 998 PH_BATTLE_ENEMY_ACTION), PH_VIEW_H - 172.0f, 999 PH_TILE_SIZE * 3.0f, PH_TILE_SIZE * 3.0f }; 1000 SDL_FRect player_rect = { PH_VIEW_W - 88.0f - ph_battle_lunge(battle, 1001 PH_BATTLE_PLAYER_ACTION), PH_VIEW_H - 120.0f, 1002 PH_TILE_SIZE * 3.0f, PH_TILE_SIZE * 3.0f }; 1003 char text[96]; 1004 int i; 1005 1006 SDL_SetRenderDrawColor(renderer, PH_BATTLE_BG_COLOR); 1007 SDL_RenderClear(renderer); 1008 SDL_SetRenderDrawColor(renderer, PH_BATTLE_GROUND_COLOR); 1009 SDL_RenderFillRect(renderer, &ground); 1010 ph_draw_battle_entity(renderer, assets, world, battle->enemy_index, enemy_rect); 1011 ph_draw_battle_entity(renderer, assets, world, world->player_index, player_rect); 1012 if (battle->phase == PH_BATTLE_TARGET) { 1013 SDL_FRect target_rect = battle->choice.target_index == world->player_index ? 1014 player_rect : enemy_rect; 1015 1016 target_rect.x -= 3.0f; 1017 target_rect.y -= 3.0f; 1018 target_rect.w += 6.0f; 1019 target_rect.h += 6.0f; 1020 SDL_SetRenderDrawColor(renderer, PH_MENU_BONUS_COLOR); 1021 SDL_RenderRect(renderer, &target_rect); 1022 } 1023 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 1024 SDL_RenderDebugText(renderer, 16.0f, 18.0f, 1025 enemy_def && enemy_def->name ? enemy_def->name : "ENEMY"); 1026 snprintf(text, sizeof(text), "HP %d/%d", enemy->vitals.hp, enemy_stats.max_hp); 1027 SDL_RenderDebugText(renderer, 16.0f, 30.0f, text); 1028 if (enemy->accuracy.turns > 0) { 1029 snprintf(text, sizeof(text), "ACC -%d%% (%d)", enemy->accuracy.value, 1030 enemy->accuracy.turns); 1031 SDL_RenderDebugText(renderer, 16.0f, 42.0f, text); 1032 } 1033 ph_battle_order(battle, world, order); 1034 SDL_RenderDebugText(renderer, ph_ui_right(96.0f), 18.0f, "TURN ORDER"); 1035 for (i = 0; i < PH_BATTLE_ORDER_COUNT; ++i) { 1036 snprintf(text, sizeof(text), "%d %s", i + 1, 1037 order[i] == world->player_index ? "YOU" : "ENEMY"); 1038 SDL_RenderDebugText(renderer, ph_ui_right(80.0f), 1039 30.0f + (float)i * 11.0f, text); 1040 } 1041 SDL_SetRenderDrawColor(renderer, PH_MENU_COLOR); 1042 SDL_RenderFillRect(renderer, &panel); 1043 SDL_SetRenderDrawColor(renderer, PH_MENU_BORDER_COLOR); 1044 SDL_RenderRect(renderer, &panel); 1045 for (i = 0; i < (int)LEN(commands); ++i) { 1046 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 1047 snprintf(text, sizeof(text), "%c %s", 1048 (battle->phase == PH_BATTLE_COMMAND || battle->phase == PH_BATTLE_MAGIC || 1049 battle->phase == PH_BATTLE_TARGET) && 1050 battle->choice.command == (PhBattleCommand)i ? '>' : ' ', 1051 commands[i]); 1052 SDL_RenderDebugText(renderer, 18.0f, 1053 panel.y + 8.0f + (float)i * 14.0f, text); 1054 } 1055 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 1056 SDL_RenderDebugText(renderer, 104.0f, panel.y + 8.0f, battle->message); 1057 if (battle->phase == PH_BATTLE_TARGET) { 1058 const PhEntityDef *target_def = ph_world_entity_def(world, 1059 world->entities[battle->choice.target_index].type_id); 1060 1061 snprintf(text, sizeof(text), "TARGET: %s ENTER CONFIRM", 1062 target_def && target_def->name ? target_def->name : "UNKNOWN"); 1063 SDL_RenderDebugText(renderer, 104.0f, panel.y + 24.0f, text); 1064 } 1065 snprintf(text, sizeof(text), "HP %d/%d", player->vitals.hp, player_stats.max_hp); 1066 SDL_RenderDebugText(renderer, ph_ui_right(80.0f), ph_ui_bottom(36.0f), text); 1067 snprintf(text, sizeof(text), "MP %d/%d", player->vitals.mp, player_stats.max_mp); 1068 SDL_RenderDebugText(renderer, ph_ui_right(80.0f), ph_ui_bottom(24.0f), text); 1069 if (battle->phase == PH_BATTLE_MAGIC) { 1070 SDL_SetRenderDrawColor(renderer, PH_MENU_COLOR); 1071 SDL_RenderFillRect(renderer, &spell_panel); 1072 SDL_SetRenderDrawColor(renderer, PH_MENU_BORDER_COLOR); 1073 SDL_RenderRect(renderer, &spell_panel); 1074 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 1075 SDL_RenderDebugText(renderer, spell_panel.x + 8.0f, 1076 spell_panel.y + 6.0f, "MAGICK"); 1077 for (i = 0; i < battle->spells.learned_count; ++i) { 1078 const PhSpellDef *choice = ph_spell_def(battle, 1079 battle->spells.learned[i]); 1080 1081 if (!choice) continue; 1082 if (ph_spell_battle_usable(choice)) 1083 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 1084 else 1085 SDL_SetRenderDrawColor(renderer, PH_MENU_DISABLED_COLOR); 1086 snprintf(text, sizeof(text), "%c %s %dMP%s", 1087 battle->choice.spell == i ? '>' : ' ', choice->name, choice->mp_cost, 1088 choice->delay_percent < 100 ? " QUICK" : ""); 1089 SDL_RenderDebugText(renderer, spell_panel.x + 8.0f, 1090 spell_panel.y + 20.0f + (float)i * 12.0f, text); 1091 } 1092 } 1093 } 1094 1095 static void 1096 ph_draw_battle_end(SDL_Renderer *renderer, const PhWorld *world, 1097 const PhBattle *battle, PhGameMode game) 1098 { 1099 const PhItemDef *loot = ph_world_item_def(world, battle->reward.item_id); 1100 char text[96]; 1101 1102 if (game == PH_GAME_VICTORY) 1103 SDL_SetRenderDrawColor(renderer, PH_BATTLE_BG_COLOR); 1104 else 1105 SDL_SetRenderDrawColor(renderer, PH_CLEAR_COLOR); 1106 SDL_RenderClear(renderer); 1107 SDL_SetRenderDrawColor(renderer, PH_MENU_TEXT_COLOR); 1108 if (game == PH_GAME_OVER) { 1109 SDL_RenderDebugText(renderer, ph_ui_center_text("GAME OVER"), 1110 PH_VIEW_H * 0.5f - 12.0f, "GAME OVER"); 1111 return; 1112 } 1113 SDL_RenderDebugText(renderer, ph_ui_center_text("VICTORY!"), 1114 PH_VIEW_H * 0.5f - 44.0f, "VICTORY!"); 1115 snprintf(text, sizeof(text), "XP GAINED %d", battle->reward.xp); 1116 SDL_RenderDebugText(renderer, ph_ui_center_text(text), 1117 PH_VIEW_H * 0.5f - 14.0f, text); 1118 if (loot && battle->reward.amount > 0) 1119 snprintf(text, sizeof(text), "LOOT %s x%d", loot->name, 1120 battle->reward.amount); 1121 else 1122 snprintf(text, sizeof(text), "LOOT NONE"); 1123 SDL_RenderDebugText(renderer, ph_ui_center_text(text), 1124 PH_VIEW_H * 0.5f + 4.0f, text); 1125 SDL_RenderDebugText(renderer, ph_ui_center_text("ENTER TO CONTINUE"), 1126 PH_VIEW_H * 0.5f + 44.0f, "ENTER TO CONTINUE"); 1127 } 1128 1129 void 1130 ph_present_frame(SDL_Renderer *renderer, const PhRenderAssets *assets, 1131 const PhGameContext *context) 1132 { 1133 const PhWorld *world = context->world; 1134 const PhBattle *battle = context->battle; 1135 PhGameMode game = *context->mode; 1136 1137 if (game == PH_GAME_BATTLE) { 1138 ph_draw_battle(renderer, assets, world, battle); 1139 if (context->menu == PH_MENU_MAIN) 1140 ph_draw_options(renderer, context->options); 1141 SDL_RenderPresent(renderer); 1142 return; 1143 } 1144 if (game == PH_GAME_VICTORY || game == PH_GAME_OVER) { 1145 ph_draw_battle_end(renderer, world, battle, game); 1146 if (context->menu == PH_MENU_MAIN) 1147 ph_draw_options(renderer, context->options); 1148 SDL_RenderPresent(renderer); 1149 return; 1150 } 1151 1152 SDL_SetRenderDrawColor(renderer, PH_CLEAR_COLOR); 1153 SDL_RenderClear(renderer); 1154 ph_render_world(renderer, assets, world); 1155 if (context->menu == PH_MENU_CHARACTER) { 1156 ph_draw_character_sheet(renderer, assets, world); 1157 } else if (context->menu == PH_MENU_INVENTORY) { 1158 ph_draw_inventory(renderer, assets, world, context->inventory); 1159 } else if (context->menu == PH_MENU_SPELLBOOK) { 1160 ph_draw_spellbook(renderer, world, context->spells, context->spellbook); 1161 } else if (context->menu == PH_MENU_SHOP) { 1162 ph_draw_shop(renderer, world, context->spells, context->shop); 1163 } else if (context->menu == PH_MENU_MAIN) { 1164 ph_draw_options(renderer, context->options); 1165 } else if (context->menu == PH_MENU_START) { 1166 ph_draw_start(renderer, context->start); 1167 } else if (world->notice.seconds > 0.0f && world->notice.text[0] != '\0') { 1168 SDL_FRect notice_bg = { 1169 .x = 8.0f, 1170 .y = (float)(PH_VIEW_H - 28), 1171 .w = (float)(PH_VIEW_W - 16), 1172 .h = 20.0f, 1173 }; 1174 1175 SDL_SetRenderDrawColor(renderer, PH_NOTICE_COLOR); 1176 SDL_RenderFillRect(renderer, ¬ice_bg); 1177 SDL_SetRenderDrawColor(renderer, PH_NOTICE_TEXT_COLOR); 1178 SDL_RenderDebugText(renderer, 14.0f, (float)(PH_VIEW_H - 22), 1179 world->notice.text); 1180 } 1181 if (game == PH_GAME_TRANSITION) { 1182 float progress = 1.0f - battle->timer / PH_BATTLE_TRANSITION_SECONDS; 1183 float height = progress * PH_VIEW_H * 0.5f; 1184 SDL_FRect top = { 0.0f, 0.0f, PH_VIEW_W, height }; 1185 SDL_FRect bottom = { 0.0f, PH_VIEW_H - height, PH_VIEW_W, height }; 1186 1187 SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); 1188 SDL_RenderFillRect(renderer, &top); 1189 SDL_RenderFillRect(renderer, &bottom); 1190 } 1191 SDL_RenderPresent(renderer); 1192 } 1193 1194 #endif