/src/ui-birth.c
C | 981 lines | 599 code | 180 blank | 202 comment | 135 complexity | 0f1d18a0fb0cda18b9c06c97be895626 MD5 | raw file
1/* 2 * File: ui-birth.c 3 * Purpose: Text-based user interface for character creation 4 * 5 * Copyright (c) 1987 - 2007 Angband contributors 6 * 7 * This work is free software; you can redistribute it and/or modify it 8 * under the terms of either: 9 * 10 * a) the GNU General Public License as published by the Free Software 11 * Foundation, version 2, or 12 * 13 * b) the "Angband licence": 14 * This software may be copied and distributed for educational, research, 15 * and not for profit purposes provided that this copyright and statement 16 * are included in all such copies. Other copyrights may also apply. 17 */ 18#include "angband.h" 19#include "ui-menu.h" 20#include "ui-birth.h" 21#include "game-event.h" 22#include "game-cmd.h" 23#include "cmds.h" 24 25/* 26 * Overview 27 * ======== 28 * This file implements the user interface side of the birth process 29 * for the classic terminal-based UI of Angband. 30 * 31 * It models birth as a series of steps which must be carried out in 32 * a specified order, with the option of stepping backwards to revisit 33 * past choices. 34 * 35 * It starts when we receive the EVENT_ENTER_BIRTH event from the game, 36 * and ends when we receive the EVENT_LEAVE_BIRTH event. In between, 37 * we will repeatedly be asked to supply a game command, which change 38 * the state of the character being rolled. Once the player is happy 39 * with their character, we send the CMD_ACCEPT_CHARACTER command. 40 */ 41 42 43/* A local-to-this-file global to hold the most important bit of state 44 between calls to the game proper. Probably not strictly necessary, 45 but reduces complexity a bit. */ 46enum birth_stage 47{ 48 BIRTH_BACK = -1, 49 BIRTH_RESET = 0, 50 BIRTH_QUICKSTART, 51 BIRTH_SEX_CHOICE, 52 BIRTH_RACE_CHOICE, 53 //BIRTH_CLASS_CHOICE, 54 BIRTH_ROLLER_CHOICE, 55 BIRTH_POINTBASED, 56 BIRTH_ROLLER, 57 BIRTH_NAME_CHOICE, 58 BIRTH_FINAL_CONFIRM, 59 BIRTH_COMPLETE 60}; 61 62 63enum birth_questions 64{ 65 BQ_METHOD = 0, 66 BQ_SEX, 67 BQ_RACE, 68 //BQ_CLASS, 69 BQ_ROLLER, 70 MAX_BIRTH_QUESTIONS 71}; 72 73enum birth_rollers 74{ 75 BR_POINTBASED = 0, 76 BR_NORMAL, 77 MAX_BIRTH_ROLLERS 78}; 79 80 81static void point_based_start(void); 82static bool quickstart_allowed = FALSE; 83 84/* ------------------------------------------------------------------------ 85 * Quickstart? screen. 86 * ------------------------------------------------------------------------ */ 87static enum birth_stage get_quickstart_command(void) 88{ 89 const char *prompt = "['Y' to use this character, 'N' to start afresh, 'C' to change name]"; 90 ui_event_data ke; 91 92 enum birth_stage next = BIRTH_QUICKSTART; 93 94 /* Prompt for it */ 95 prt("New character based on previous one:", 0, 0); 96 prt(prompt, Term->hgt - 1, Term->wid / 2 - strlen(prompt) / 2); 97 98 /* Buttons */ 99 button_kill_all(); 100 button_add("[Y]", 'y'); 101 button_add("[N]", 'n'); 102 button_add("[C]", 'c'); 103 redraw_stuff(); 104 105 do 106 { 107 /* Get a key */ 108 ke = inkey_ex(); 109 110 if (ke.key == 'N' || ke.key == 'n') 111 { 112 cmd_insert(CMD_BIRTH_RESET, TRUE); 113 next = BIRTH_SEX_CHOICE; 114 } 115 else if (ke.key == KTRL('X')) 116 { 117 cmd_insert(CMD_QUIT); 118 next = BIRTH_COMPLETE; 119 } 120 else if (ke.key == 'C' || ke.key == 'c') 121 { 122 next = BIRTH_NAME_CHOICE; 123 } 124 else if (ke.key == 'Y' || ke.key == 'y') 125 { 126 cmd_insert(CMD_ACCEPT_CHARACTER); 127 next = BIRTH_COMPLETE; 128 } 129 } while (next == BIRTH_QUICKSTART); 130 131 /* Buttons */ 132 button_kill_all(); 133 redraw_stuff(); 134 135 /* Clear prompt */ 136 clear_from(23); 137 138 return next; 139} 140 141/* ------------------------------------------------------------------------ 142 * The various "menu" bits of the birth process - namely choice of sex, 143 * race, class, and roller type. 144 * ------------------------------------------------------------------------ */ 145 146/* The various menus */ 147static menu_type sex_menu, race_menu, class_menu, roller_menu; 148 149/* Locations of the menus, etc. on the screen */ 150#define HEADER_ROW 1 151#define QUESTION_ROW 7 152#define TABLE_ROW 10 153 154#define QUESTION_COL 2 155#define SEX_COL 2 156#define RACE_COL 14 157#define RACE_AUX_COL 29 158#define CLASS_COL 29 159#define CLASS_AUX_COL 50 160 161static region gender_region = {SEX_COL, TABLE_ROW, 15, -2}; 162static region race_region = {RACE_COL, TABLE_ROW, 15, -2}; 163static region class_region = {CLASS_COL, TABLE_ROW, 19, -2}; 164static region roller_region = {44, TABLE_ROW, 21, -2}; 165 166/* We use different menu "browse functions" to display the help text 167 sometimes supplied with the menu items - currently just the list 168 of bonuses, etc, corresponding to each race and class. */ 169typedef void (*browse_f) (int oid, void *db, const region *l); 170 171/* We have one of these structures for each menu we display - it holds 172 the useful information for the menu - text of the menu items, "help" 173 text, current (or default) selection, and whether random selection 174 is allowed. */ 175struct birthmenu_data 176{ 177 const char **items; 178 const char *hint; 179 bool allow_random; 180}; 181 182/* A custom "display" function for our menus that simply displays the 183 text from our stored data in a different colour if it's currently 184 selected. */ 185static void birthmenu_display(menu_type *menu, int oid, bool cursor, 186 int row, int col, int width) 187{ 188 struct birthmenu_data *data = menu->menu_data; 189 190 byte attr = curs_attrs[CURS_KNOWN][0 != cursor]; 191 c_put_str(attr, data->items[oid], row, col); 192} 193 194/* We defer the choice of actual actions until outside of the menu API 195 in menu_question(), so this can be a reasonably simple function 196 for when a menu "command" is activated. */ 197static bool birthmenu_handler(char cmd, void *db, int oid) 198{ 199 return TRUE; 200} 201 202/* Our custom menu iterator, only really needed to allow us to override 203 the default handling of "commands" in the standard iterators (hence 204 only defining the display and handler parts). */ 205static const menu_iter birth_iter = { NULL, NULL, birthmenu_display, birthmenu_handler }; 206 207static void race_help(int i, void *db, const region *l) 208{ 209 int j; 210 211 /* Output to the screen */ 212 text_out_hook = text_out_to_screen; 213 214 /* Indent output */ 215 text_out_indent = RACE_AUX_COL; 216 Term_gotoxy(RACE_AUX_COL, TABLE_ROW); 217 218 for (j = 0; j < A_MAX; j++) 219 { 220 text_out_e("%s%+d\n", stat_names_reduced[j], p_info[i].r_adj[j]); 221 } 222 223 text_out_e("Hit die: %d\n", p_info[i].r_mhp); 224 text_out_e("Experience: %d%%\n", p_info[i].r_exp); 225 text_out_e("Infravision: %d ft", p_info[i].infra * 10); 226 227 /* Reset text_out() indentation */ 228 text_out_indent = 0; 229} 230 231static void class_help(int i, void *db, const region *l) 232{ 233 int j; 234 235 /* Output to the screen */ 236 text_out_hook = text_out_to_screen; 237 238 /* Indent output */ 239 text_out_indent = CLASS_AUX_COL; 240 Term_gotoxy(CLASS_AUX_COL, TABLE_ROW); 241 242 for (j = 0; j < A_MAX; j++) 243 { 244 text_out_e("%s%+d\n", stat_names_reduced[j], c_info[i].c_adj[j]); 245 } 246 247 text_out_e("Hit die: %d\n", c_info[i].c_mhp); 248 text_out_e("Experience: %d%%", c_info[i].c_exp); 249 250 /* Reset text_out() indentation */ 251 text_out_indent = 0; 252} 253 254/* Set up one of our menus ready to display choices for a birth question. 255 This is slightly involved. */ 256static void init_birth_menu(menu_type *menu, int n_choices, int initial_choice, const region *reg, bool allow_random, browse_f aux) 257{ 258 struct birthmenu_data *menu_data; 259 260 /* A couple of behavioural flags - we want selections letters in 261 lower case and a double tap to act as a selection. */ 262 menu->selections = lower_case; 263 menu->flags = MN_DBL_TAP; 264 265 /* Set the number of choices in the menu to the same as the game 266 has told us we've got to offer. */ 267 menu->count = n_choices; 268 269 /* Allocate sufficient space for our own bits of menu information. */ 270 menu_data = mem_alloc(sizeof *menu_data); 271 272 /* Copy across the game's suggested initial selection, etc. */ 273 menu->cursor = initial_choice; 274 menu_data->allow_random = allow_random; 275 276 /* Allocate space for an array of menu item texts and help texts 277 (where applicable) */ 278 menu_data->items = mem_alloc(menu->count * sizeof *menu_data->items); 279 280 /* Poke our menu data in to the assigned slot in the menu structure. */ 281 menu->menu_data = menu_data; 282 283 /* Set up the "browse" hook to display help text (where applicable). */ 284 menu->browse_hook = aux; 285 286 /* Get ui-menu to initialise whatever it wants to to give us a scrollable 287 menu. */ 288 menu_init(menu, MN_SKIN_SCROLL, &birth_iter, reg); 289} 290 291 292 293static void setup_menus() 294{ 295 int i; 296 297 const char *roller_choices[MAX_BIRTH_ROLLERS] = { 298 "Point-based", 299 "Standard roller" 300 }; 301 302 struct birthmenu_data *mdata; 303 304 /* Sex menu fairly straightforward */ 305 init_birth_menu(&sex_menu, MAX_SEXES, p_ptr->psex, &gender_region, TRUE, NULL); 306 mdata = sex_menu.menu_data; 307 for (i = 0; i < MAX_SEXES; i++) 308 { 309 mdata->items[i] = sex_info[i].title; 310 } 311 mdata->hint = "Your 'sex' does not have any significant gameplay effects."; 312 313 /* Race menu more complicated. */ 314 init_birth_menu(&race_menu, z_info->p_max, p_ptr->prace, &race_region, TRUE, race_help); 315 mdata = race_menu.menu_data; 316 317 for (i = 0; i < z_info->p_max; i++) 318 { 319 mdata->items[i] = p_name + p_info[i].name; 320 } 321 mdata->hint = "Your 'race' determines various intrinsic factors and bonuses."; 322 323 /* Class menu similar to race. */ 324 init_birth_menu(&class_menu, z_info->c_max, p_ptr->pclass, &class_region, TRUE, class_help); 325 mdata = class_menu.menu_data; 326 327 for (i = 0; i < z_info->c_max; i++) 328 { 329 mdata->items[i] = c_name + c_info[i].name; 330 } 331 mdata->hint = "Your 'class' determines various intrinsic abilities and bonuses"; 332 333 /* Roller menu straightforward again */ 334 init_birth_menu(&roller_menu, MAX_BIRTH_ROLLERS, 0, &roller_region, FALSE, NULL); 335 mdata = roller_menu.menu_data; 336 for (i = 0; i < MAX_BIRTH_ROLLERS; i++) 337 { 338 mdata->items[i] = roller_choices[i]; 339 } 340 mdata->hint = "Your choice of character generation. Point-based is recommended."; 341} 342 343/* Cleans up our stored menu info when we've finished with it. */ 344static void free_birth_menu(menu_type *menu) 345{ 346 struct birthmenu_data *data = menu->menu_data; 347 348 if (data) 349 { 350 mem_free(data->items); 351 mem_free(data); 352 } 353} 354 355static void free_birth_menus() 356{ 357 /* We don't need these any more. */ 358 free_birth_menu(&sex_menu); 359 free_birth_menu(&race_menu); 360 free_birth_menu(&class_menu); 361 free_birth_menu(&roller_menu); 362} 363 364/* 365 * Clear the previous question 366 */ 367static void clear_question(void) 368{ 369 int i; 370 371 for (i = QUESTION_ROW; i < TABLE_ROW; i++) 372 { 373 /* Clear line, position cursor */ 374 Term_erase(0, i, 255); 375 } 376} 377 378 379#define BIRTH_MENU_HELPTEXT \ 380 "{lightblue}Please select your character from the menu below:{/}\n\n" \ 381 "Use the {lightgreen}movement keys{/} to scroll the menu, " \ 382 "{lightgreen}Enter{/} to select the current menu item, '{lightgreen}*{/}' " \ 383 "for a random menu item, '{lightgreen}ESC{/}' to step back through the " \ 384 "birth process, '{lightgreen}={/}' for the birth options, '{lightgreen}?{/} " \ 385 "for help, or '{lightgreen}Ctrl-X{/}' to quit." 386 387/* Show the birth instructions on an otherwise blank screen */ 388static void print_menu_instructions(void) 389{ 390 /* Clear screen */ 391 Term_clear(); 392 393 /* Output to the screen */ 394 text_out_hook = text_out_to_screen; 395 396 /* Indent output */ 397 text_out_indent = QUESTION_COL; 398 Term_gotoxy(QUESTION_COL, HEADER_ROW); 399 400 /* Display some helpful information */ 401 text_out_e(BIRTH_MENU_HELPTEXT); 402 403 /* Reset text_out() indentation */ 404 text_out_indent = 0; 405} 406 407/* Allow the user to select from the current menu, and return the 408 corresponding command to the game. Some actions are handled entirely 409 by the UI (displaying help text, for instance). */ 410static enum birth_stage menu_question(enum birth_stage current, menu_type *current_menu, cmd_code choice_command) 411{ 412 struct birthmenu_data *menu_data = current_menu->menu_data; 413 int cursor = current_menu->cursor; 414 ui_event_data cx; 415 416 enum birth_stage next = BIRTH_RESET; 417 418 /* Print the question currently being asked. */ 419 clear_question(); 420 Term_putstr(QUESTION_COL, QUESTION_ROW, -1, TERM_YELLOW, menu_data->hint); 421 422 current_menu->cmd_keys = "?=*\r\n\x18"; /* ?, ,= *, \n, <ctl-X> */ 423 424 while (next == BIRTH_RESET) 425 { 426 /* Display the menu, wait for a selection of some sort to be made. */ 427 cx = menu_select(current_menu, &cursor, EVT_CMD); 428 429 /* As all the menus are displayed in "hierarchical" style, we allow 430 use of "back" (left arrow key or equivalent) to step back in 431 the proces as well as "escape". */ 432 if (cx.type == EVT_BACK || cx.type == EVT_ESCAPE) 433 { 434 next = BIRTH_BACK; 435 } 436 /* '\xff' is a mouse selection, '\r' a keyboard one. */ 437 else if (cx.key == '\xff' || cx.key == '\r') 438 { 439 if (current == BIRTH_ROLLER_CHOICE) 440 { 441 if (cursor) 442 { 443 /* Do a first roll of the stats */ 444 cmd_insert(CMD_ROLL_STATS); 445 next = current + 2; 446 } 447 else 448 { 449 /* 450 * Make sure we've got a point-based char to play with. 451 * We call point_based_start here to make sure we get 452 * an update on the points totals before trying to 453 * display the screen. The call to CMD_RESET_STATS 454 * forces a rebuying of the stats to give us up-to-date 455 * totals. This is, it should go without saying, a hack. 456 */ 457 point_based_start(); 458 cmd_insert(CMD_RESET_STATS, TRUE); 459 next = current + 1; 460 } 461 } 462 else 463 { 464 cmd_insert(choice_command, cursor); 465 next = current + 1; 466 } 467 } 468 /* '*' chooses an option at random from those the game's provided. */ 469 else if (cx.key == '*' && menu_data->allow_random) 470 { 471 current_menu->cursor = randint0(current_menu->count); 472 cmd_insert(choice_command, current_menu->cursor); 473 474 menu_refresh(current_menu); 475 next = current + 1; 476 } 477 else if (cx.key == '=') 478 { 479 do_cmd_options(); 480 next = current; 481 } 482 else if (cx.key == KTRL('X')) 483 { 484 cmd_insert(CMD_QUIT); 485 next = BIRTH_COMPLETE; 486 } 487 else if (cx.key == '?') 488 { 489 do_cmd_help(); 490 } 491 } 492 493 return next; 494} 495 496/* ------------------------------------------------------------------------ 497 * The rolling bit of the roller. 498 * ------------------------------------------------------------------------ */ 499#define ROLLERCOL 42 500 501static enum birth_stage roller_command(bool first_call) 502{ 503 char prompt[80] = ""; 504 size_t promptlen = 0; 505 506 ui_event_data ke; 507 char ch; 508 509 enum birth_stage next = BIRTH_ROLLER; 510 511 /* Used to keep track of whether we've rolled a character before or not. */ 512 static bool prev_roll = FALSE; 513 514 /* Display the player - a bit cheaty, but never mind. */ 515 display_player(0); 516 517 if (first_call) 518 prev_roll = FALSE; 519 520 /* Add buttons */ 521 button_add("[ESC]", ESCAPE); 522 button_add("[Enter]", '\r'); 523 button_add("[r]", 'r'); 524 if (prev_roll) button_add("[p]", 'p'); 525 clear_from(Term->hgt - 2); 526 redraw_stuff(); 527 528 /* Prepare a prompt (must squeeze everything in) */ 529 strnfcat(prompt, sizeof (prompt), &promptlen, "['r' to reroll"); 530 if (prev_roll) 531 strnfcat(prompt, sizeof(prompt), &promptlen, ", 'p' for prev"); 532 strnfcat(prompt, sizeof (prompt), &promptlen, " or 'Enter' to accept]"); 533 534 /* Prompt for it */ 535 prt(prompt, Term->hgt - 1, Term->wid / 2 - promptlen / 2); 536 537 /* Prompt and get a command */ 538 ke = inkey_ex(); 539 ch = ke.key; 540 541 if (ch == ESCAPE) 542 { 543 button_kill('r'); 544 button_kill('p'); 545 546 next = BIRTH_BACK; 547 } 548 549 /* 'Enter' accepts the roll */ 550 if ((ch == '\r') || (ch == '\n')) 551 { 552 next = BIRTH_NAME_CHOICE; 553 } 554 555 /* Reroll this character */ 556 else if ((ch == ' ') || (ch == 'r')) 557 { 558 cmd_insert(CMD_ROLL_STATS); 559 prev_roll = TRUE; 560 } 561 562 /* Previous character */ 563 else if (prev_roll && (ch == 'p')) 564 { 565 cmd_insert(CMD_PREV_STATS); 566 } 567 568 /* Quit */ 569 else if (ch == KTRL('X')) 570 { 571 cmd_insert(CMD_QUIT); 572 next = BIRTH_COMPLETE; 573 } 574 575 /* Help XXX */ 576 else if (ch == '?') 577 { 578 do_cmd_help(); 579 } 580 581 /* Nothing handled directly here */ 582 else 583 { 584 bell("Illegal roller command!"); 585 } 586 587 /* Kill buttons */ 588 button_kill(ESCAPE); 589 button_kill('\r'); 590 button_kill('r'); 591 button_kill('p'); 592 redraw_stuff(); 593 594 return next; 595} 596 597/* ------------------------------------------------------------------------ 598 * Point-based stat allocation. 599 * ------------------------------------------------------------------------ */ 600 601/* The locations of the "costs" area on the birth screen. */ 602#define COSTS_ROW 2 603#define COSTS_COL (42 + 32) 604#define TOTAL_COL (42 + 19) 605 606/* This is called whenever a stat changes. We take the easy road, and just 607 redisplay them all using the standard function. */ 608static void point_based_stats(game_event_type type, game_event_data *data, void *user) 609{ 610 display_player_stat_info(); 611} 612 613/* This is called whenever any of the other miscellaneous stat-dependent things 614 changed. We are hooked into changes in the amount of gold in this case, 615 but redisplay everything because it's easier. */ 616static void point_based_misc(game_event_type type, game_event_data *data, void *user) 617{ 618 display_player_xtra_info(); 619} 620 621 622/* This is called whenever the points totals are changed (in birth.c), so 623 that we can update our display of how many points have been spent and 624 are available. */ 625static void point_based_points(game_event_type type, game_event_data *data, void *user) 626{ 627 int i; 628 int sum = 0; 629 int *stats = data->birthstats.stats; 630 631 /* Display the costs header */ 632 put_str("Cost", COSTS_ROW - 1, COSTS_COL); 633 634 /* Display the costs */ 635 for (i = 0; i < A_MAX; i++) 636 { 637 /* Display cost */ 638 put_str(format("%4d", stats[i]), COSTS_ROW + i, COSTS_COL); 639 sum += stats[i]; 640 } 641 642 put_str(format("Total Cost: %2d/%2d", sum, data->birthstats.remaining + sum), COSTS_ROW + A_MAX, TOTAL_COL); 643} 644 645static void point_based_start(void) 646{ 647 const char *prompt = "[up/down to move, left/right to modify, 'r' to reset, 'Enter' to accept]"; 648 649 /* Clear */ 650 Term_clear(); 651 652 /* Display the player */ 653 display_player_xtra_info(); 654 display_player_stat_info(); 655 656 prt(prompt, Term->hgt - 1, Term->wid / 2 - strlen(prompt) / 2); 657 658 /* Register handlers for various events - cheat a bit because we redraw 659 the lot at once rather than each bit at a time. */ 660 event_add_handler(EVENT_BIRTHPOINTS, point_based_points, NULL); 661 event_add_handler(EVENT_STATS, point_based_stats, NULL); 662 event_add_handler(EVENT_GOLD, point_based_misc, NULL); 663} 664 665static void point_based_stop(void) 666{ 667 event_remove_handler(EVENT_BIRTHPOINTS, point_based_points, NULL); 668 event_remove_handler(EVENT_STATS, point_based_stats, NULL); 669 event_remove_handler(EVENT_GOLD, point_based_misc, NULL); 670} 671 672static enum birth_stage point_based_command(void) 673{ 674 static int stat = 0; 675 char ch; 676 enum birth_stage next = BIRTH_POINTBASED; 677 678/* point_based_display();*/ 679 680 /* Place cursor just after cost of current stat */ 681 Term_gotoxy(COSTS_COL + 4, COSTS_ROW + stat); 682 683 /* Get key */ 684 ch = inkey(); 685 686 if (ch == KTRL('X')) 687 { 688 cmd_insert(CMD_QUIT); 689 next = BIRTH_COMPLETE; 690 } 691 692 /* Go back a step, or back to the start of this step */ 693 else if (ch == ESCAPE) 694 { 695 next = BIRTH_BACK; 696 } 697 698 else if (ch == 'r' || ch == 'R') 699 { 700 cmd_insert(CMD_RESET_STATS, FALSE); 701 } 702 703 /* Done */ 704 else if ((ch == '\r') || (ch == '\n')) 705 { 706 next = BIRTH_NAME_CHOICE; 707 } 708 else 709 { 710 ch = target_dir(ch); 711 712 /* Prev stat, looping round to the bottom when going off the top */ 713 if (ch == 8) 714 stat = (stat + A_MAX - 1) % A_MAX; 715 716 /* Next stat, looping round to the top when going off the bottom */ 717 if (ch == 2) 718 stat = (stat + 1) % A_MAX; 719 720 /* Decrease stat (if possible) */ 721 if (ch == 4) 722 { 723 cmd_insert(CMD_SELL_STAT, stat); 724 } 725 726 /* Increase stat (if possible) */ 727 if (ch == 6) 728 { 729 cmd_insert(CMD_BUY_STAT, stat); 730 } 731 } 732 733 return next; 734} 735 736/* ------------------------------------------------------------------------ 737 * Asking for the player's chosen name. 738 * ------------------------------------------------------------------------ */ 739static enum birth_stage get_name_command(void) 740{ 741 enum birth_stage next; 742 char name[32]; 743 744 if (get_name(name, sizeof(name))) 745 { 746 cmd_insert(CMD_NAME_CHOICE, name); 747 next = BIRTH_FINAL_CONFIRM; 748 } 749 else 750 { 751 next = BIRTH_BACK; 752 } 753 754 return next; 755} 756 757/* ------------------------------------------------------------------------ 758 * Final confirmation of character. 759 * ------------------------------------------------------------------------ */ 760static enum birth_stage get_confirm_command(void) 761{ 762 const char *prompt = "['ESC' to step back, 'S' to start over, or any other key to continue]"; 763 ui_event_data ke; 764 765 enum birth_stage next; 766 767 /* Prompt for it */ 768 prt(prompt, Term->hgt - 1, Term->wid / 2 - strlen(prompt) / 2); 769 770 /* Buttons */ 771 button_kill_all(); 772 button_add("[Continue]", 'q'); 773 button_add("[ESC]", ESCAPE); 774 button_add("[S]", 'S'); 775 redraw_stuff(); 776 777 /* Get a key */ 778 ke = inkey_ex(); 779 780 /* Start over */ 781 if (ke.key == 'S' || ke.key == 's') 782 { 783 next = BIRTH_RESET; 784 } 785 else if (ke.key == KTRL('X')) 786 { 787 cmd_insert(CMD_QUIT); 788 next = BIRTH_COMPLETE; 789 } 790 else if (ke.key == ESCAPE) 791 { 792 next = BIRTH_BACK; 793 } 794 else 795 { 796 cmd_insert(CMD_ACCEPT_CHARACTER); 797 next = BIRTH_COMPLETE; 798 } 799 800 /* Buttons */ 801 button_kill_all(); 802 redraw_stuff(); 803 804 /* Clear prompt */ 805 clear_from(23); 806 807 return next; 808} 809 810 811 812/* ------------------------------------------------------------------------ 813 * Things that relate to the world outside this file: receiving game events 814 * and being asked for game commands. 815 * ------------------------------------------------------------------------ */ 816 817/* 818 * This is called when we receive a request for a command in the birth 819 * process. 820 821 * The birth process continues until we send a final character confirmation 822 * command (or quit), so this is effectively called in a loop by the main 823 * game. 824 * 825 * We're imposing a step-based system onto the main game here, so we need 826 * to keep track of where we're up to, where each step moves on to, etc. 827 */ 828errr get_birth_command(bool wait) 829{ 830 static enum birth_stage current_stage = BIRTH_RESET; 831 static enum birth_stage prev; 832 static enum birth_stage roller = BIRTH_RESET; 833 enum birth_stage next = current_stage; 834 835 switch (current_stage) 836 { 837 case BIRTH_RESET: 838 { 839 cmd_insert(CMD_BIRTH_RESET, TRUE); 840 roller = BIRTH_RESET; 841 842 if (quickstart_allowed) 843 next = BIRTH_QUICKSTART; 844 else 845 next = BIRTH_SEX_CHOICE; 846 847 break; 848 } 849 850 case BIRTH_QUICKSTART: 851 { 852 display_player(0); 853 next = get_quickstart_command(); 854 break; 855 } 856 857 case BIRTH_SEX_CHOICE: 858 //case BIRTH_CLASS_CHOICE: 859 case BIRTH_RACE_CHOICE: 860 case BIRTH_ROLLER_CHOICE: 861 { 862 menu_type *menu = &sex_menu; 863 cmd_code command = CMD_CHOOSE_SEX; 864 865 Term_clear(); 866 print_menu_instructions(); 867 868 if (current_stage > BIRTH_SEX_CHOICE) 869 { 870 menu_refresh(&sex_menu); 871 menu = &race_menu; 872 command = CMD_CHOOSE_RACE; 873 } 874 875 if (current_stage > BIRTH_RACE_CHOICE) 876 { 877 menu_refresh(&race_menu); 878 menu = &roller_menu; 879 command = CMD_NULL; 880 } 881 882 next = menu_question(current_stage, menu, command); 883 884 if (next == BIRTH_BACK) 885 next = current_stage - 1; 886 887 /* Make sure that the character gets reset before quickstarting */ 888 if (next == BIRTH_QUICKSTART) 889 next = BIRTH_RESET; 890 891 break; 892 } 893 894 case BIRTH_POINTBASED: 895 { 896 roller = BIRTH_POINTBASED; 897 898 if (prev > BIRTH_POINTBASED) 899 point_based_start(); 900 901 next = point_based_command(); 902 903 if (next == BIRTH_BACK) 904 next = BIRTH_ROLLER_CHOICE; 905 906 if (next != BIRTH_POINTBASED) 907 point_based_stop(); 908 909 break; 910 } 911 912 case BIRTH_ROLLER: 913 { 914 roller = BIRTH_ROLLER; 915 next = roller_command(prev < BIRTH_ROLLER); 916 if (next == BIRTH_BACK) 917 next = BIRTH_ROLLER_CHOICE; 918 919 break; 920 } 921 922 case BIRTH_NAME_CHOICE: 923 { 924 if (prev < BIRTH_NAME_CHOICE) 925 display_player(0); 926 927 next = get_name_command(); 928 if (next == BIRTH_BACK) 929 next = roller; 930 931 break; 932 } 933 934 case BIRTH_FINAL_CONFIRM: 935 { 936 if (prev < BIRTH_FINAL_CONFIRM) 937 display_player(0); 938 939 next = get_confirm_command(); 940 if (next == BIRTH_BACK) 941 next = BIRTH_NAME_CHOICE; 942 943 break; 944 } 945 946 default: 947 { 948 /* Remove dodgy compiler warning, */ 949 } 950 } 951 952 prev = current_stage; 953 current_stage = next; 954 955 return 0; 956} 957 958/* 959 * Called when we enter the birth mode - so we set up handlers, command hooks, 960 * etc, here. 961 */ 962static void ui_enter_birthscreen(game_event_type type, game_event_data *data, void *user) 963{ 964 /* Set the ugly static global that tells us if quickstart's available. */ 965 quickstart_allowed = data->flag; 966 967 setup_menus(); 968} 969 970static void ui_leave_birthscreen(game_event_type type, game_event_data *data, void *user) 971{ 972 free_birth_menus(); 973} 974 975 976void ui_init_birthstate_handlers(void) 977{ 978 event_add_handler(EVENT_ENTER_BIRTH, ui_enter_birthscreen, NULL); 979 event_add_handler(EVENT_LEAVE_BIRTH, ui_leave_birthscreen, NULL); 980} 981