/src/ui-birth.c

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