root/trunk/src/ui-birth.c

Revision 975, 26.4 kB (checked in by shanoah, 2 months ago)

Get rid of a bunch of compiler warnings.

  • Property svn:eol-style set to native
Line 
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 "game-event.h"
21 #include "game-cmd.h"
22
23 /* Two local-to-this-file globals to hold a bit of state between messages
24    and command requests from the game proper. Probably not strictly necessary,
25    but they reduce code complexity a bit. */
26 static enum birth_stage current_stage = BIRTH_METHOD_CHOICE;
27 static int autoroller_maxes[A_MAX];
28
29 /* ------------------------------------------------------------------------
30  * Quickstart? screen.
31  * ------------------------------------------------------------------------ */
32 static game_command quickstart_question()
33 {
34         char ch;
35         ui_event_data ke;
36         game_command cmd = { CMD_NULL, 0, {0}};
37
38         /* Prompt */
39         while (cmd.command == CMD_NULL)
40         {
41                 put_str("Quick-start character based on previous one (y/n)? ", 2, 2);
42
43                 /* Buttons */
44                 button_kill_all();
45                 button_add("[Exit]", KTRL('X'));
46                 button_add("[ESC]", ESCAPE);
47                 button_add("[y]", 'y');
48                 button_add("[n]", 'n');
49                 button_add("[Help]", '?');
50                
51                 ke = inkey_ex();
52                 ch = ke.key;
53                
54                 if (ch == KTRL('X'))
55                 {
56                         cmd.command = CMD_QUIT;
57                 }
58                 else if (strchr("Nn\r\n", ch))
59                 {
60                         cmd.command = CMD_BIRTH_CHOICE;
61                         cmd.params.choice = 0; /* FIXME */
62                 }
63                 else if (strchr("Yy", ch))
64                 {
65                         cmd.command = CMD_BIRTH_CHOICE;
66                         cmd.params.choice = 1; /* FIXME */
67                 }
68                 else if (ch == '?')
69                         (void)show_file("birth.hlp", NULL, 0, 0);
70                 else
71                         bell("Illegal answer!");
72         }
73        
74         return cmd;
75 }
76
77
78 /* ------------------------------------------------------------------------
79  * The various "menu" bits of the birth process - namely choice of sex,
80  * race, class, and roller type.
81  * ------------------------------------------------------------------------ */
82 static menu_type *current_menu = NULL;
83
84 /* Locations of the menus, etc. on the screen */
85 #define HEADER_ROW       1
86 #define QUESTION_ROW     7
87 #define TABLE_ROW       10
88
89 #define QUESTION_COL     2
90 #define SEX_COL          2
91 #define RACE_COL        14
92 #define RACE_AUX_COL    29
93 #define CLASS_COL       29
94 #define CLASS_AUX_COL   50
95
96 static region gender_region = {SEX_COL, TABLE_ROW, 15, -2};
97 static region race_region = {RACE_COL, TABLE_ROW, 15, -2};
98 static region class_region = {CLASS_COL, TABLE_ROW, 19, -2};
99 static region roller_region = {44, TABLE_ROW, 21, -2};
100
101 /* The various menus */
102 menu_type sex_menu, race_menu, class_menu, roller_menu;
103
104 /* We have one of these structures for each menu we display - it holds
105    the useful information for the menu - text of the menu items, "help"
106    text, current (or default) selection, and whether random selection
107    is allowed. */
108 struct birthmenu_data
109 {
110         int selection;
111         const char **items;
112         const char **help;
113         const char *hint;
114         bool allow_random;
115 };
116
117 /*
118  * Clear the previous question
119  */
120 static void clear_question(void)
121 {
122         int i;
123
124         for (i = QUESTION_ROW; i < TABLE_ROW; i++)
125         {
126                 /* Clear line, position cursor */
127                 Term_erase(0, i, 255);
128         }
129 }
130
131
132 #define BIRTH_MENU_HELPTEXT \
133         "{lightblue}Please select your character from the menu below:{/}\n\n" \
134         "Use the {lightgreen}movement keys{/} to scroll the menu, " \
135         "{lightgreen}Enter{/} to select the current menu item, '{lightgreen}*{/}' " \
136         "for a random menu item, '{lightgreen}ESC{/}' to step back through the " \
137         "birth process, '{lightgreen}={/}' for the birth options, '{lightgreen}?{/} " \
138         "for help, or '{lightgreen}Ctrl-X{/}' to quit."
139
140 /* Show the birth instructions on an otherwise blank screen */ 
141 static void print_menu_instructions()
142 {
143         /* Clear screen */
144         Term_clear();
145        
146         /* Output to the screen */
147         text_out_hook = text_out_to_screen;
148        
149         /* Indent output */
150         text_out_indent = QUESTION_COL;
151         Term_gotoxy(QUESTION_COL, HEADER_ROW);
152        
153         /* Display some helpful information */
154         text_out_e(BIRTH_MENU_HELPTEXT);
155        
156         /* Reset text_out() indentation */
157         text_out_indent = 0;
158 }
159
160 /* Allow the user to select from the current menu, and return the
161    corresponding command to the game.  Some actions are handled entirely
162    by the UI (displaying help text, for instance). */
163 game_command menu_question()
164 {
165         /* Note: the const here is just to quell a compiler warning. */
166         struct birthmenu_data *menu_data = current_menu->menu_data;
167         int cursor = menu_data->selection;
168         game_command cmd = { CMD_NULL, 0, {0}};
169         ui_event_data cx;
170        
171         /* Print the question currently being asked. */
172         clear_question();
173         Term_putstr(QUESTION_COL, QUESTION_ROW, -1, TERM_YELLOW, menu_data->hint);
174
175         current_menu->cmd_keys = "?=*\r\n\x18"/* ?, ,= *, \n, <ctl-X> */
176
177         while (cmd.command == CMD_NULL)
178         {
179                 /* Display the menu, wait for a selection of some sort to be made. */
180                 cx = menu_select(current_menu, &cursor, EVT_CMD);
181
182                 /* As all the menus are displayed in "hierarchical" style, we allow
183                    use of "back" (left arrow key or equivalent) to step back in
184                    the proces as well as "escape". */
185                 if (cx.type == EVT_BACK || cx.type == EVT_ESCAPE)
186                 {
187                         cmd.command = CMD_BIRTH_BACK;
188                 }
189                 /* '\xff' is a mouse selection, '\r' a keyboard one. */
190                 else if (cx.key == '\xff' || cx.key == '\r')
191                 {
192                         cmd.command = CMD_BIRTH_CHOICE;
193                         cmd.params.choice = cursor;
194                 }
195                 /* '*' chooses an option at random from those the game's provided. */
196                 else if (cx.key == '*' && menu_data->allow_random)
197                 {
198                         cmd.command = CMD_BIRTH_CHOICE;
199                         current_menu->cursor = randint0(current_menu->count);
200                         cmd.params.choice = current_menu->cursor;
201                         menu_refresh(current_menu);
202                 }
203                 else if (cx.key == '=')
204                 {
205                         cmd.command = CMD_OPTIONS;
206                 }
207                 else if (cx.key == KTRL('X'))
208                 {
209                         cmd.command = CMD_QUIT;
210                 }
211         }
212        
213         return cmd;
214 }
215
216 /* A custom "display" function for our menus that simply displays the
217    text from our stored data in a different colour if it's currently
218    selected. */
219 static void birthmenu_display(menu_type *menu, int oid, bool cursor,
220                               int row, int col, int width)
221 {
222         struct birthmenu_data *data = menu->menu_data;
223
224         byte attr = curs_attrs[CURS_KNOWN][0 != cursor];
225         c_put_str(attr, data->items[oid], row, col);
226 }
227
228 /* We defer the choice of actual actions until outside of the menu API
229    in menu_question(), so this can be a reasonably simple function
230    for when a menu "command" is activated. */
231 static bool birthmenu_handler(char cmd, void *db, int oid)
232 {
233         return TRUE;
234 }
235
236 /* Our custom menu iterator, only really needed to allow us to override
237    the default handling of "commands" in the standard iterators (hence
238    only defining the display and handler parts). */
239 static const menu_iter birth_iter = { NULL, NULL, birthmenu_display, birthmenu_handler };
240
241 /* Cleans up our stored menu info when we've finished with it. */
242 static void free_birth_menu(menu_type *menu)
243 {
244         struct birthmenu_data *data = menu->menu_data;
245
246         if (data)
247         {
248                 mem_free(data->items);
249                 mem_free(data->help);
250                 mem_free(data);
251         }
252 }
253
254 /* We use different menu "browse functions" to display the help text
255    sometimes supplied with the menu items - currently just the list
256    of bonuses, etc, corresponding to each race and class. */
257 typedef void (*browse_f) (int oid, void *, const region *loc);
258
259 static void race_help(int i, void *data, const region *loc)
260 {
261         struct birthmenu_data *menu_data = data;
262
263         /* Output to the screen */
264         text_out_hook = text_out_to_screen;
265        
266         /* Indent output */
267         text_out_indent = RACE_AUX_COL;
268         Term_gotoxy(RACE_AUX_COL, TABLE_ROW);
269        
270         text_out_e("%s", menu_data->help[i]);
271        
272         /* Reset text_out() indentation */
273         text_out_indent = 0;
274 }
275
276 static void class_help(int i, void *data, const region *loc)
277 {
278         struct birthmenu_data *menu_data = data;
279
280         /* Output to the screen */
281         text_out_hook = text_out_to_screen;
282        
283         /* Indent output */
284         text_out_indent = CLASS_AUX_COL;
285         Term_gotoxy(CLASS_AUX_COL, TABLE_ROW);
286        
287         text_out_e("%s", menu_data->help[i]);
288        
289         /* Reset text_out() indentation */
290         text_out_indent = 0;
291 }
292
293 /* Set up one of our menus ready to display choices for a birth question.
294    This is slightly involved. */
295 static void init_birth_menu(menu_type *menu, game_event_data *data, const region *reg, bool allow_random, browse_f aux)
296 {
297         struct birthmenu_data *menu_data;
298         int i;
299
300         /* Get rid of the previous incarnation of this menu. */
301         free_birth_menu(menu);
302        
303         /* A couple of behavioural flags - we want selections letters in
304            lower case and a double tap to act as a selection. */
305         menu->selections = lower_case;
306         menu->flags = MN_DBL_TAP;
307
308         /* Set the number of choices in the menu to the same as the game
309            has told us we've got to offer. */
310         menu->count = data->birthstage.n_choices;
311
312         /* Allocate sufficient space for our own bits of menu information. */
313         menu_data = mem_alloc(sizeof *menu_data);
314
315         /* Copy across the game's suggested initial selection, etc. */
316         menu_data->selection = data->birthstage.initial_choice;
317         menu_data->allow_random = allow_random;
318
319         /* Allocate space for an array of menu item texts and help texts
320            (where applicable) */
321         menu_data->items = mem_alloc(menu->count * sizeof *menu_data->items);
322
323         if (data->birthstage.helptexts)
324                 menu_data->help = mem_alloc(menu->count * sizeof *menu_data->items);
325         else
326                 menu_data->help = NULL;
327
328         /* Make sure we have the appropriate menu text and help text in arrays.
329            The item text, helptext, etc. are guaranteed to no persistent
330            throughout the birth process (though not beyond), so we can
331            just point to it, having no wish to display it after that. */
332         for (i = 0; i < menu->count; i++)
333         {       
334                 menu_data->items[i] = data->birthstage.choices[i];
335
336                 if (menu_data->help)
337                         menu_data->help[i] = data->birthstage.helptexts[i];
338         }
339
340         /* Help text for the menu as a whole (also guaranteed persistent. */
341         menu_data->hint = data->birthstage.hint;
342
343         /* Poke our menu data in to the assigned slot in the menu structure. */
344         menu->menu_data = menu_data;
345
346         /* Set up the "browse" hook to display help text (where applicable). */
347         menu->browse_hook = aux;
348
349         /* Get ui-menu to initialise whatever it wants to to give us a scrollable
350            menu. */
351         menu_init(menu, MN_SKIN_SCROLL, &birth_iter, reg);
352 }
353
354 /* ------------------------------------------------------------------------
355  * Autoroller-based stat allocation.
356  * ------------------------------------------------------------------------ */
357
358 static bool minstat_keypress(char *buf, size_t buflen, size_t *curs, size_t *len, char keypress, bool firsttime)
359 {
360         if (keypress == KTRL('x'))
361                 quit(NULL);
362
363         return askfor_aux_keypress(buf, buflen, curs, len, keypress, firsttime);
364 }
365
366 #define AUTOROLLER_HELPTEXT \
367   "The auto-roller will automatically ignore characters which do not " \
368   "meet the minimum values for any stats specified below.\n" \
369   "Note that stats are not independent, so it is not possible to get " \
370   "perfect (or even high) values for all your stats."
371
372 void autoroller_start(int stat_maxes[A_MAX])
373 {
374         int i;
375         char inp[80];
376         char buf[80];
377
378         /* Clear */
379         Term_clear();
380
381         /* Output to the screen */
382         text_out_hook = text_out_to_screen;
383        
384         /* Indent output */
385         text_out_indent = 5;
386         text_out_wrap = 75;
387
388         Term_gotoxy(5, 10);     
389         text_out_e("%s", AUTOROLLER_HELPTEXT); 
390
391         /* Reset text_out() indentation */
392         text_out_indent = 0;
393         text_out_wrap = 0;
394
395         /* Prompt for the minimum stats */
396         put_str("Enter minimum value for: ", 15, 2);
397        
398         /* Output the maximum stats */
399         for (i = 0; i < A_MAX; i++)
400         {
401                 int m = stat_maxes[i];
402                 autoroller_maxes[i] = stat_maxes[i];
403
404                 /* Extract a textual format */
405                 /* cnv_stat(m, inp, sizeof(buf); */
406                
407                 /* Above 18 */
408                 if (m > 18)
409                 {
410                         strnfmt(inp, sizeof(inp), "(Max of 18/%02d):", (m - 18));
411                 }
412                
413                 /* From 3 to 18 */
414                 else
415                 {
416                         strnfmt(inp, sizeof(inp), "(Max of %2d):", m);
417                 }
418                
419                 /* Prepare a prompt */
420                 strnfmt(buf, sizeof(buf), "%-5s%-20s", stat_names[i], inp);
421                
422                 /* Dump the prompt */
423                 put_str(buf, 16 + i, 5);
424         }
425 }
426
427 game_command autoroller_command()
428 {
429         int i, v;
430         char inp[80];
431
432         game_command cmd = { CMD_NULL, 0, {0} };
433
434         /* Input the minimum stats */
435         for (i = 0; i < A_MAX; i++)
436         {
437                 /* Get a minimum stat */
438                 while (TRUE)
439                 {
440                         char *s;
441                        
442                         /* Move the cursor */
443                         put_str("", 16 + i, 30);
444                        
445                         /* Default */
446                         inp[0] = '\0';
447                        
448                         /* Get a response (or escape) */
449                         if (!askfor_aux(inp, 9, minstat_keypress))
450                         {
451                                 if (i == 0)
452                                 {
453                                         /* Back a step */
454                                         cmd.command = CMD_BIRTH_BACK;
455                                         return cmd;
456                                 }
457                                 else
458                                 {
459                                         /* Repeat this step */
460                                         return cmd;
461                                 }
462                         }
463                        
464                         /* Hack -- add a fake slash */
465                         my_strcat(inp, "/", sizeof(inp));
466                        
467                         /* Hack -- look for the "slash" */
468                         s = strchr(inp, '/');
469                        
470                         /* Hack -- Nuke the slash */
471                         *s++ = '\0';
472                        
473                         /* Hack -- Extract an input */
474                         v = atoi(inp) + atoi(s);
475                        
476                         /* Break on valid input */
477                         if (v <= autoroller_maxes[i]) break;
478                 }
479                
480                 /* Save the minimum stat */
481                 cmd.params.stat_limits[i] = (v > 0) ? v : 0;
482         }
483
484         cmd.command = CMD_AUTOROLL;
485         return cmd;
486 }
487
488 /* ------------------------------------------------------------------------
489  * The rolling bit of the autoroller/simple roller.
490  * ------------------------------------------------------------------------ */
491 #define ROLLERCOL 42
492 static bool prev_roll = FALSE;
493
494 static void roller_newchar(game_event_type type, game_event_data *data, void *user)
495 {
496         /* Display the player - a cheat really, given the context. */
497         display_player(0);
498
499         /* Non-zero if we've got a previous character to swap with. */
500         prev_roll = data->birthstats.remaining;
501
502         Term_fresh();
503 }
504
505 /*
506    Handles the event we get when the autoroller is looking for a suitable
507    character but hasn't found one yet.
508 */
509 static void roller_autoroll(game_event_type type, game_event_data *data, void *user)
510 {
511         int col = ROLLERCOL;
512         int i;
513         char buf[80];
514
515         /* Label */
516         put_str(" Limit", 2, col+5);
517        
518         /* Label */
519         put_str("  Freq", 2, col+13);
520        
521         /* Label */
522         put_str("  Roll", 2, col+24);
523
524         /* Put the minimal stats */
525         for (i = 0; i < A_MAX; i++)
526         {
527                 /* Label stats */
528                 put_str(stat_names[i], 3+i, col);
529                
530                 /* Put the stat */
531                 cnv_stat(data->birthautoroll.limits[i], buf, sizeof(buf));
532                 c_put_str(TERM_L_BLUE, buf, 3+i, col+5);
533         }
534
535         /* Label count */
536         put_str("Round:", 9, col+13);
537        
538         /* You can't currently interrupt the autoroller */
539 /*      put_str("(Hit ESC to stop)", 12, col+13); */
540
541         /* Put the stats (and percents) */
542         for (i = 0; i < A_MAX; i++)
543         {
544                 /* Put the stat */
545                 cnv_stat(data->birthautoroll.current[i], buf, sizeof(buf));
546                 c_put_str(TERM_L_GREEN, buf, 3+i, col+24);
547                
548                 /* Put the percent */
549                 if (data->birthautoroll.matches[i])
550                 {
551                         int p = 1000L * data->birthautoroll.matches[i] / data->birthautoroll.round;
552                         byte attr = (p < 100) ? TERM_YELLOW : TERM_L_GREEN;
553                         strnfmt(buf, sizeof(buf), "%3d.%d%%", p/10, p%10);
554                         c_put_str(attr, buf, 3+i, col+13);
555                 }
556                
557                 /* Never happened */
558                 else
559                 {
560                         c_put_str(TERM_RED, "(NONE)", 3+i, col+13);
561                 }
562         }
563        
564         /* Dump round */
565         put_str(format("%10ld", data->birthautoroll.round), 9, col+20);
566        
567         /* Make sure they see everything */
568         Term_fresh();
569 }
570
571 static void roller_start()
572 {
573         prev_roll = FALSE;
574         Term_clear();
575
576         event_add_handler(EVENT_BIRTHAUTOROLLER, roller_autoroll, NULL);       
577         event_add_handler(EVENT_BIRTHSTATS, roller_newchar, NULL);     
578 }
579
580 static game_command roller_command()
581 {
582         game_command cmd = { CMD_NULL, 0, {0} };
583         ui_event_data ke;
584         char ch;
585
586         /* bool prev_roll is a static global that's reset when we enter the
587            roller */
588
589         /* Add buttons */
590         button_add("[ESC]", ESCAPE);
591         button_add("[Enter]", '\r');
592         button_add("[r]", 'r');
593         if (prev_roll) button_add("[p]", 'p');
594         clear_from(Term->hgt - 2);
595         redraw_stuff();
596
597         /* Prepare a prompt (must squeeze everything in) */
598         Term_gotoxy(2, 23);
599         Term_addch(TERM_WHITE, '[');
600         Term_addstr(-1, TERM_WHITE, "'r' to reroll");
601         if (prev_roll) Term_addstr(-1, TERM_WHITE, ", 'p' for prev");
602         Term_addstr(-1, TERM_WHITE, ", or 'Enter' to accept");
603         Term_addch(TERM_WHITE, ']');
604        
605         /* Prompt and get a command */
606         ke = inkey_ex();
607         ch = ke.key;
608
609         if (ch == ESCAPE)
610         {
611                 button_kill('r');
612                 button_kill('p');
613
614                 cmd.command = CMD_BIRTH_BACK;
615         }
616
617         /* 'Enter' accepts the roll */
618         if ((ch == '\r') || (ch == '\n'))
619         {
620                 cmd.command = CMD_ACCEPT_STATS;
621         }
622
623         /* Reroll this character */
624         if ((ch == ' ') || (ch == 'r'))
625         {
626                 cmd.command = CMD_ROLL;
627         }
628
629         /* Previous character */
630         if (prev_roll && (ch == 'p'))
631         {
632                 cmd.command = CMD_PREV_STATS;
633         }
634
635         if (ch == KTRL('X'))
636         {
637                 cmd.command = CMD_QUIT;
638         }
639        
640         /* Nothing handled directly here */
641         if (cmd.command == CMD_NULL)
642         {
643                 /* Help XXX */
644                 if (ch == '?')
645                         do_cmd_help();
646                 else
647                         bell("Illegal auto-roller command!");
648         }
649
650         /* Kill buttons */
651         button_kill(ESCAPE);
652         button_kill('\r');
653         button_kill('r');
654         button_kill('p');
655         redraw_stuff();
656
657         return cmd;
658 }
659
660 static void roller_stop()
661 {
662         event_remove_handler(EVENT_BIRTHAUTOROLLER, roller_autoroll, NULL);     
663         event_remove_handler(EVENT_BIRTHSTATS, roller_newchar, NULL);   
664 }
665
666
667 /* ------------------------------------------------------------------------
668  * Point-based stat allocation.
669  * ------------------------------------------------------------------------ */
670
671 /* The locations of the "costs" area on the birth screen. */
672 #define COSTS_ROW 3
673 #define COSTS_COL (42 + 32)
674
675 /* This is called whenever a stat changes.  We take the easy road, and just
676    redisplay them all using the standard function. */
677 static void point_based_stats(game_event_type type, game_event_data *data, void *user)
678 {
679         display_player_stat_info();
680 }
681
682 /* This is called whenever any of the other miscellaneous stat-dependent things
683    changed.  We are hooked into changes in the amount of gold in this case,
684    but redisplay everything because it's easier. */
685 static void point_based_misc(game_event_type type, game_event_data *data, void *user)
686 {
687         display_player_xtra_info();
688 }
689
690 /* This is called whenever the points totals are changed (in birth.c), so
691    that we can update our display of how many points have been spent and
692    are available. */
693 static void point_based_points(game_event_type type, game_event_data *data, void *user)
694 {
695         int i;
696         int sum = 0;
697
698         /* Display the costs header */
699         put_str("Cost", COSTS_ROW - 1, COSTS_COL);
700        
701         /* Display the costs */
702         for (i = 0; i < A_MAX; i++)
703         {
704                 /* Display cost */
705                 put_str(format("%4d", data->birthstats.stats[i]),
706                         COSTS_ROW + i, COSTS_COL);
707
708                 sum += data->birthstats.stats[i];
709         }
710        
711         prt(format("Total Cost %2d/%2d.  Use 2/8 to move, 4/6 to modify, 'Enter' to accept.", sum, data->birthstats.remaining + sum), 0, 0);
712 }
713
714
715 static void point_based_start()
716 {
717         /* Clear */
718         Term_clear();
719
720         /* Display the player */
721         display_player_xtra_info();
722         display_player_stat_info();
723
724         /* Register handlers for various events - cheat a bit because we redraw
725            the lot at once rather than each bit at a time. */
726         event_add_handler(EVENT_STATS, point_based_stats, NULL);       
727         event_add_handler(EVENT_GOLD, point_based_misc, NULL); 
728         event_add_handler(EVENT_BIRTHSTATS, point_based_points, NULL); 
729 }
730
731 static void point_based_stop()
732 {
733         event_remove_handler(EVENT_STATS, point_based_stats, NULL);     
734         event_remove_handler(EVENT_GOLD, point_based_misc, NULL);       
735         event_remove_handler(EVENT_BIRTHSTATS, point_based_points, NULL);
736 }
737
738 static game_command point_based_command()
739 {
740         game_command cmd = { CMD_NULL, 0, {0} };
741         static int stat = 0;
742         char ch;
743
744         while (cmd.command == CMD_NULL)
745         {
746                 /* Place cursor just after cost of current stat */
747                 Term_gotoxy(COSTS_COL + 4, COSTS_ROW + stat);
748
749                 /* Get key */
750                 ch = inkey();
751
752                 if (ch == KTRL('X'))
753                 {
754                         cmd.command = CMD_QUIT;
755                 }
756
757                 /* Go back a step, or back to the start of this step */
758                 else if (ch == ESCAPE)
759                 {
760                         cmd.command = CMD_BIRTH_BACK;
761                 }
762
763                 /* Done */
764                 else if ((ch == '\r') || (ch == '\n'))
765                 {
766                         cmd.command = CMD_ACCEPT_STATS;
767                 }
768                 else
769                 {
770                         ch = target_dir(ch);
771                        
772                         /* Prev stat, looping round to the bottom when going off the top */
773                         if (ch == 8)
774                                 stat = (stat + A_MAX - 1) % A_MAX;
775                        
776                         /* Next stat, looping round to the top when going off the bottom */
777                         if (ch == 2)
778                                 stat = (stat + 1) % A_MAX;
779                        
780                         /* Decrease stat (if possible) */
781                         if (ch == 4)
782                         {
783                                 cmd.command = CMD_SELL_STAT;
784                                 cmd.params.choice = stat;
785                         }
786                        
787                         /* Increase stat (if possible) */
788                         if (ch == 6)
789                         {
790                                 cmd.command = CMD_BUY_STAT;
791                                 cmd.params.choice = stat;
792                         }
793                 }
794         }
795
796         return cmd;
797 }
798        
799 /* ------------------------------------------------------------------------
800  * Asking for the player's chosen name.
801  * ------------------------------------------------------------------------ */
802 static game_command get_name_command()
803 {
804         game_command cmd;
805         char name[32];
806
807         if (get_name(name, sizeof(name)))
808         {
809                 cmd.command = CMD_NAME_CHOICE;
810                 cmd.params.string = string_make(name);
811         }
812         else
813         {
814                 cmd.command = CMD_BIRTH_BACK;
815         }
816
817         return cmd;
818 }
819
820 /* ------------------------------------------------------------------------
821  * Final confirmation of character.
822  * ------------------------------------------------------------------------ */
823 static game_command get_confirm_command()
824 {
825         const char *prompt = "['ESC' to step back, 'S' to start over, or any other key to continue]";
826         ui_event_data ke;
827
828         game_command cmd;
829
830         /* Prompt for it */
831         prt(prompt, Term->hgt - 1, Term->wid / 2 - strlen(prompt) / 2);
832        
833         /* Buttons */
834         button_kill_all();
835         button_add("[Continue]", 'q');
836         button_add("[ESC]", ESCAPE);
837         button_add("[S]", 'S');
838         redraw_stuff();
839        
840         /* Get a key */
841         ke = inkey_ex();
842        
843         /* Start over */
844         if (ke.key == 'S')
845         {
846                 cmd.command = CMD_BIRTH_RESTART;
847         }
848         else if (ke.key == KTRL('X'))
849         {
850                 cmd.command = CMD_QUIT;
851         }
852         else if (ke.key == ESCAPE)
853         {
854                 cmd.command = CMD_BIRTH_BACK;
855         }
856         else
857         {
858                 cmd.command = CMD_ACCEPT_CHARACTER;
859         }
860        
861         /* Buttons */
862         button_kill_all();
863         redraw_stuff();
864
865         /* Clear prompt */
866         clear_from(23);
867
868         return cmd;
869 }
870
871
872
873 /* ------------------------------------------------------------------------
874  * Things that relate to the world outside this file: receiving game events
875  * and being asked for game commands.
876  * ------------------------------------------------------------------------ */
877
878 /*
879  * This is called on EVENT_BIRTHSTAGE, and we use it to do any initialising
880  * of data structures, or setting up of bits of the screen that need to be
881  * done when we first enter each stage.
882  */
883 static void birth_stage_changed(game_event_type type, game_event_data *data, void *user)
884 {
885         /* Before we update current_stage, we'll release handlers, etc. that
886            relate to the previous "current" stage. */
887         switch (current_stage)
888         {
889                 case BIRTH_POINTBASED:
890                 {
891                         point_based_stop();