Ticket #812: ui-birth.c

File ui-birth.c, 26.5 KB (added by Sean <kingofnorway@…>, 11 years ago)

This is the unmodified file in question

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