Changeset 815

Show
Ignore:
Timestamp:
03/25/08 12:09:16 (6 months ago)
Author:
ajps
Message:

THe next stage of "fixing" bug #348, the "improved abstraction of the
UI" bug. This implements a set of game events and messages covering the
birth process, and splits the text-based UI into a new file, ui-birth.c,
with only minor (hopefully uncontroversial) changes in functionality,
e.g.:

  • quickstart now doesn't preserve HP rolls from the previous character
  • the autoroller can't be aborted part way through (the limit for rolls

could perhaps be reduced if this is a problem)

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/src/Makefile.inc

    r789 r815  
    1919birth.o: birth.c angband.h h-basic.h z-file.h z-form.h z-util.h z-virt.h \ 
    2020  z-rand.h z-term.h ui-event.h z-quark.h z-msg.h config.h defines.h \ 
    21   option.h types.h ui.h z-type.h object.h externs.h cmds.h ui-menu.h 
     21  option.h types.h ui.h z-type.h object.h externs.h cmds.h game-event.h \ 
     22  game-cmd.h ui-menu.h 
    2223button.o: button.c angband.h h-basic.h z-file.h z-form.h z-util.h \ 
    2324  z-virt.h z-rand.h z-term.h ui-event.h z-quark.h z-msg.h config.h \ 
     
    169170  z-rand.h z-term.h ui-event.h z-quark.h z-msg.h config.h defines.h \ 
    170171  option.h types.h ui.h z-type.h object.h externs.h 
     172ui-birth.o: ui-birth.c angband.h h-basic.h z-file.h z-form.h z-util.h \ 
     173  z-virt.h z-rand.h z-term.h ui-event.h z-quark.h z-msg.h config.h \ 
     174  defines.h option.h types.h ui.h z-type.h object.h externs.h ui-menu.h \ 
     175  game-event.h game-cmd.h 
    171176ui-event.o: ui-event.c angband.h h-basic.h z-file.h z-form.h z-util.h \ 
    172177  z-virt.h z-rand.h z-term.h ui-event.h z-quark.h z-msg.h config.h \ 
  • trunk/src/Makefile.src

    r702 r815  
    106106        trap.o \ 
    107107        ui.o \ 
     108        ui-birth.o \ 
    108109        ui-event.o \ 
    109110        ui-menu.o \ 
  • trunk/src/birth.c

    r789 r815  
    1818#include "angband.h" 
    1919#include "cmds.h" 
     20#include "game-event.h" 
     21#include "game-cmd.h" 
    2022#include "ui-menu.h" 
     23 
     24/* 
     25 * Overview 
     26 * ======== 
     27 * This file contains the game-mechanical part of the birth process. 
     28 * To follow the code, start at player_birth towards the bottom of 
     29 * the file - that is the only external entry point to the functions 
     30 * defined here. 
     31 *  
     32 * Player (in the Angband sense of character) birth is a series of  
     33 * steps which must be carried out in a specified order, with choices 
     34 * in one step affecting those further along (e.g. race and class  
     35 * choices determine maximum values for stats in the autoroller). 
     36 * 
     37 * We implement this through a system of "birth stages".  As the  
     38 * game enters each birth stage, we typically do some minor 
     39 * housekeeping before sending a signal to the UI so that it can update  
     40 * its display, or do whatever else it deems fit.  Then we send other 
     41 * messages (such as updates of the progress of the autoroller) or 
     42 * request a command (such as buying a stat, or stepping back in the 
     43 * process).  This file simply responds to such commands by stepping 
     44 * back and forth through the birth sequence, updating values, and so on, 
     45 * until a suitable character has been chosen.  It then does more 
     46 * housekeeping to ensure the "player" is ready to start the game  
     47 * (clearing the history log, making sure options are set, etc) before  
     48 * returning contrl to the game proper. 
     49 */ 
    2150 
    2251 
     
    3261struct birther 
    3362{ 
     63        byte sex; 
     64        byte race; 
     65        byte class; 
     66 
    3467        s16b age; 
    3568        s16b wt; 
     
    4174        s16b stat[A_MAX]; 
    4275 
    43         char *name; 
    44  
    4576        char history[250]; 
    4677}; 
     
    4980 
    5081/* 
    51  * The last character displayed 
    52  */ 
    53 static birther prev; 
    54  
    55  
    56 /* 
    57  * Current stats (when rolling a character). 
    58  */ 
    59 static s16b stat_use[A_MAX]; 
    60  
    61  
    62  
    63 /* 
    64  * Save the currently rolled data for later. 
    65  */ 
    66 static void save_prev_data(void) 
     82 * Save the currently rolled data into the supplied 'player'. 
     83 */ 
     84static void save_roller_data(birther *player) 
    6785{ 
    6886        int i; 
    6987 
    70  
    71         /*** Save the current data ***/ 
    72  
    7388        /* Save the data */ 
    74         prev.age = p_ptr->age; 
    75         prev.wt = p_ptr->wt; 
    76         prev.ht = p_ptr->ht; 
    77         prev.sc = p_ptr->sc; 
    78         prev.au = p_ptr->au; 
     89        player->sex = p_ptr->psex; 
     90        player->race = p_ptr->prace; 
     91        player->class = p_ptr->pclass; 
     92        player->age = p_ptr->age; 
     93        player->wt = p_ptr->wt; 
     94        player->ht = p_ptr->ht; 
     95        player->sc = p_ptr->sc; 
     96        player->au = p_ptr->au; 
    7997 
    8098        /* Save the stats */ 
    8199        for (i = 0; i < A_MAX; i++) 
    82100        { 
    83                 prev.stat[i] = p_ptr->stat_max[i]; 
     101                player->stat[i] = p_ptr->stat_max[i]; 
    84102        } 
    85103 
    86104        /* Save the history */ 
    87         my_strcpy(prev.history, p_ptr->history, sizeof(prev.history)); 
     105        my_strcpy(player->history, p_ptr->history, sizeof(player->history)); 
    88106} 
    89107 
    90108 
    91109/* 
    92  * Load the previously rolled data. 
    93  */ 
    94 static void load_prev_data(void) 
     110 * Load stored player data from 'player' as the currently rolled data, 
     111 * optionally placing the current data in 'prev_player' (if 'prev_player' 
     112 * is non-NULL). 
     113 * 
     114 * It is perfectly legal to specify the same "birther" for both 'player' 
     115 * and 'prev_player'. 
     116 */ 
     117static void load_roller_data(birther *player, birther *prev_player) 
    95118{ 
    96119        int i; 
    97  
    98         birther temp; 
    99  
    100  
    101         /*** Save the current data ***/ 
    102  
    103         /* Save the data */ 
    104         temp.age = p_ptr->age; 
    105         temp.wt = p_ptr->wt; 
    106         temp.ht = p_ptr->ht; 
    107         temp.sc = p_ptr->sc; 
    108         temp.au = p_ptr->au; 
    109  
    110         /* Save the stats */ 
    111         for (i = 0; i < A_MAX; i++) 
    112         { 
    113                 temp.stat[i] = p_ptr->stat_max[i]; 
    114         } 
    115  
    116         /* Save the history */ 
    117         my_strcpy(temp.history, p_ptr->history, sizeof(temp.history)); 
    118  
     120         
     121    /* The initialisation is just paranoia - structure assignment is 
     122           (perhaps) not strictly defined to work with uninitialised parts 
     123           of structures. */ 
     124        birther temp = { 0 }; 
     125 
     126        /*** Save the current data if we'll need it later ***/ 
     127        if (prev_player) 
     128                save_roller_data(&temp); 
    119129 
    120130        /*** Load the previous data ***/ 
    121131 
    122132        /* Load the data */ 
    123         p_ptr->age = prev.age; 
    124         p_ptr->wt = p_ptr->wt_birth = prev.wt; 
    125         p_ptr->ht = p_ptr->ht_birth = prev.ht; 
    126         p_ptr->sc = prev.sc; 
    127         p_ptr->au = p_ptr->au_birth = prev.au; 
     133        p_ptr->psex = player->sex; 
     134        p_ptr->prace = player->race; 
     135        p_ptr->pclass = player->class; 
     136        p_ptr->age = player->age; 
     137        p_ptr->wt = p_ptr->wt_birth = player->wt; 
     138        p_ptr->ht = p_ptr->ht_birth = player->ht; 
     139        p_ptr->sc = player->sc; 
     140        p_ptr->au = p_ptr->au_birth = player->au; 
    128141 
    129142        /* Load the stats */ 
    130143        for (i = 0; i < A_MAX; i++) 
    131144        { 
    132                 p_ptr->stat_max[i] = p_ptr->stat_cur[i] = p_ptr->stat_birth[i] = prev.stat[i]; 
     145                p_ptr->stat_max[i] = p_ptr->stat_cur[i] = p_ptr->stat_birth[i] = player->stat[i]; 
    133146        } 
    134147 
    135148        /* Load the history */ 
    136         my_strcpy(p_ptr->history, prev.history, sizeof(p_ptr->history)); 
    137  
    138  
    139         /*** Save the current data ***/ 
    140  
    141         /* Save the data */ 
    142         prev.age = temp.age; 
    143         prev.wt = temp.wt; 
    144         prev.ht = temp.ht; 
    145         prev.sc = temp.sc; 
    146         prev.au = temp.au; 
    147  
    148         /* Save the stats */ 
    149         for (i = 0; i < A_MAX; i++) 
    150         { 
    151                 prev.stat[i] = temp.stat[i]; 
    152         } 
    153  
    154         /* Save the history */ 
    155         my_strcpy(prev.history, temp.history, sizeof(prev.history)); 
     149        my_strcpy(p_ptr->history, player->history, sizeof(p_ptr->history)); 
     150 
     151 
     152        /*** Save the current data if the caller is interested in it. ***/ 
     153        if (prev_player) 
     154                *prev_player = temp; 
    156155} 
    157  
    158  
    159156 
    160157 
     
    218215 * For efficiency, we include a chunk of "calc_bonuses()". 
    219216 */ 
    220 static void get_stats(void
     217static void get_stats(int stat_use[A_MAX]
    221218{ 
    222219        int i, j; 
     
    287284{ 
    288285        int i, j, min_value, max_value; 
    289  
    290286 
    291287        /* Level one */ 
     
    334330 
    335331 
     332static void get_bonuses() 
     333{ 
     334        /* Calculate the bonuses and hitpoints */ 
     335        p_ptr->update |= (PU_BONUS | PU_HP); 
     336 
     337        /* Update stuff */ 
     338        update_stuff(); 
     339 
     340        /* Fully healed */ 
     341        p_ptr->chp = p_ptr->mhp; 
     342 
     343        /* Fully rested */ 
     344        p_ptr->csp = p_ptr->msp; 
     345} 
     346 
     347 
    336348/* 
    337349 * Get the racial history, and social class, using the "history charts". 
     
    415427 * Get the player's starting money 
    416428 */ 
    417 static void get_money(void
     429static void get_money(int stat_use[A_MAX]
    418430{ 
    419431        int i; 
     
    450462        int i; 
    451463 
    452         /* Backup the player choices */ 
    453         byte psex = p_ptr->psex; 
    454         byte prace = p_ptr->prace; 
    455         byte pclass = p_ptr->pclass; 
    456  
    457464        /* Wipe the player */ 
    458465        (void)WIPE(p_ptr, player_type); 
    459  
    460         /* Restore the choices */ 
    461         p_ptr->psex = psex; 
    462         p_ptr->prace = prace; 
    463         p_ptr->pclass = pclass; 
    464466 
    465467        /* Clear the inventory */ 
     
    681683 
    682684 
    683 /* Locations of the tables on the screen */ 
    684 #define HEADER_ROW       1 
    685 #define QUESTION_ROW     7 
    686 #define TABLE_ROW       10 
    687  
    688 #define QUESTION_COL     2 
    689 #define SEX_COL          2 
    690 #define RACE_COL        14 
    691 #define RACE_AUX_COL    29 
    692 #define CLASS_COL       29 
    693 #define CLASS_AUX_COL   50 
    694  
    695 /* 
    696  * Clear the previous question 
    697  */ 
    698 static void clear_question(void) 
     685/* 
     686 * Get a command for the birth process, handling the command cases here  
     687 * (i.e. quitting and options. 
     688 * 
     689 * NOTE: We would also handle help here if it was eventually decided 
     690 * there should be a game help mode rather than it being entirely at 
     691 * the UI level. 
     692 */ 
     693static game_command get_birth_command() 
     694
     695        game_command cmd = { CMD_NULL }; 
     696 
     697        while (cmd.command == CMD_NULL) 
     698        { 
     699                cmd = get_game_command(); 
     700 
     701                if (cmd.command == CMD_QUIT)  
     702                        quit(NULL); 
     703 
     704                if (cmd.command == CMD_OPTIONS)  
     705                { 
     706                        /* TODO: Change this to use whatever sort of message passing 
     707                           system we eventually decide on for options.  That might 
     708                           still be calling do_cmd_option. :) */ 
     709                        do_cmd_options(); 
     710 
     711                        /* We've already handled it, so don't pass it on. */ 
     712                        cmd.command = CMD_NULL; 
     713                } 
     714        } 
     715 
     716        /* TODO: Check against list of permitted commands for the given stage. Probably. */ 
     717 
     718        return cmd; 
     719
     720 
     721/* 
     722 * Cost of each "point" of a stat. 
     723 */ 
     724static const int birth_stat_costs[18 + 1] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 4 }; 
     725 
     726/* It is feasible to get base 17 in 3 stats with the autoroller */ 
     727#define MAX_BIRTH_POINTS 24 /* 3 * (1+1+1+1+1+1+2) */ 
     728 
     729 
     730static void recalculate_stats(int *stats, int points_left) 
    699731{ 
    700732        int i; 
    701733 
    702         for (i = QUESTION_ROW; i < TABLE_ROW; i++) 
    703         { 
    704                 /* Clear line, position cursor */ 
    705                 Term_erase(0, i, 255); 
    706         } 
     734        /* Process stats */ 
     735        for (i = 0; i < A_MAX; i++) 
     736        { 
     737                /* Variable stat maxes */ 
     738                if (adult_maximize) 
     739                { 
     740                        /* Reset stats */ 
     741                        p_ptr->stat_cur[i] = p_ptr->stat_max[i] = p_ptr->stat_birth[i] = stats[i]; 
     742                } 
     743                 
     744                /* Fixed stat maxes */ 
     745                else 
     746                { 
     747                        /* Obtain a "bonus" for "race" and "class" */ 
     748                        int bonus = rp_ptr->r_adj[i] + cp_ptr->c_adj[i]; 
     749                         
     750                        /* Apply the racial/class bonuses */ 
     751                        p_ptr->stat_cur[i] = p_ptr->stat_max[i] = 
     752                                modify_stat_value(stats[i], bonus); 
     753                } 
     754        } 
     755         
     756        /* Gold is inversely proportional to cost */ 
     757        p_ptr->au = p_ptr->au_birth = (50 * points_left) + 100; 
     758 
     759        /* Update bonuses, hp, etc. */ 
     760        get_bonuses(); 
     761 
     762        /* Tell the UI about all this stuff that's changed. */ 
     763        event_signal(EVENT_GOLD); 
     764        event_signal(EVENT_AC); 
     765        event_signal(EVENT_HP); 
     766        event_signal(EVENT_STATS); 
    707767} 
    708 /* =================================================== */ 
    709  
    710 /* gender/race/classs menu selector */ 
    711  
    712 /* 
    713  * Display additional information about each race during the selection. 
    714  */ 
    715 static void race_aux_hook(int race, void *db, const region *reg) 
     768 
     769 
     770/* 
     771 * Due to its relative complexity, point based birth has been split off into 
     772 * this function.  'reset' is TRUE if we're entering from earlier in the 
     773 * birth process - i.e. we want to start a character from scratch rather  
     774 * than having another go at one already chosen. 
     775 */ 
     776static enum birth_stage do_point_based(bool reset) 
     777
     778        game_command cmd = { CMD_NULL }; 
     779         
     780        int i, j; 
     781        int stats[A_MAX]; 
     782 
     783        int points_spent[A_MAX]; 
     784        int points_left; 
     785 
     786        /* If the first command is BIRTH_BACK, we step back a stage, so this 
     787           is a reasonable default. */ 
     788        enum birth_stage next_stage = BIRTH_ROLLER_CHOICE; 
     789 
     790        if (reset) 
     791        { 
     792                /* Roll for base hitpoints, age/height/weight and social class */ 
     793                get_extra(); 
     794                get_ahw(); 
     795                get_history(); 
     796        } 
     797 
     798        /* Signal that we're entering the point based birth arena. */ 
     799        event_signal_birthstage(BIRTH_POINTBASED, NULL); 
     800         
     801        /* Calculate and signal initial stats and points totals. */ 
     802        points_left = MAX_BIRTH_POINTS; 
     803 
     804        for (i = 0; i < A_MAX; i++) 
     805        { 
     806                /* Initial stats are all 10 and costs or zero */ 
     807                stats[i] = 10; 
     808                points_spent[i] = 0; 
     809 
     810                /* If not resetting, we use the stored stat values from p_ptr 
     811                   to simulate buying the stats, just using the same method as 
     812                   when the BUY_STAT command is received. */ 
     813                if (!reset) 
     814                { 
     815                        for (j = 10; j < p_ptr->stat_birth[i]; j++) 
     816                        { 
     817                                int stat_cost = birth_stat_costs[j + 1]; 
     818                                stats[i]++; 
     819                                points_spent[i] += stat_cost; 
     820                                points_left -= stat_cost; 
     821                        } 
     822                } 
     823        } 
     824 
     825        /* Use the new "birth stat" values to work out the "other" 
     826           stat values (i.e. after modifiers) and tell the UI things have  
     827           changed. */ 
     828        recalculate_stats(stats, points_left); 
     829        event_signal_birthstats(points_spent, points_left);      
     830         
     831        /* Then on to the interactive part - two ways to leave */ 
     832        while (cmd.command != CMD_ACCEPT_STATS &&  
     833                   cmd.command != CMD_BIRTH_BACK) 
     834        {                
     835                cmd = get_birth_command(); 
     836                 
     837                /* If BIRTH_BACK isn't the first command, or we didn't start 
     838                   by resetting the stats, we redo this stage rather than actually 
     839                   stepping back. */ 
     840                if (cmd.command != CMD_BIRTH_BACK || !reset) 
     841                        next_stage = BIRTH_POINTBASED; 
     842 
     843                switch (cmd.command) 
     844                { 
     845                        case CMD_BUY_STAT: 
     846                        { 
     847                                /* The choice is the index of the stat in the list to "buy" */ 
     848                                int choice = cmd.params.choice; 
     849                                if (choice >= A_MAX || choice < 0) continue; 
     850 
     851                                /* Can't increase stats past a "base" of 18 */ 
     852                                if (stats[choice] < 18) 
     853                                { 
     854                                        /* Get the cost of buying the extra point (beyond what 
     855                                           it has already cost to get this far). */ 
     856                                        int stat_cost = birth_stat_costs[stats[choice] + 1]; 
     857 
     858                                        if (stat_cost <= points_left) 
     859                                        { 
     860                                                stats[choice]++; 
     861                                                points_spent[choice] += stat_cost; 
     862                                                points_left -= stat_cost; 
     863                                 
     864                                                /* Tell the UI the new points situation. */ 
     865                                                event_signal_birthstats(points_spent, points_left); 
     866 
     867                                                /* Recalculate everything that's changed because 
     868                                                   the stat has changed, and inform the UI. */ 
     869                                                recalculate_stats(stats, points_left); 
     870                                        } 
     871                                } 
     872 
     873                                break; 
     874                        } 
     875                         
     876                        case CMD_SELL_STAT: 
     877                        { 
     878                                /* The choice is the index of the stat in the list to "sell" */ 
     879                                int choice = cmd.params.choice; 
     880                                if (choice >= A_MAX || choice < 0) continue; 
     881 
     882                                /* We can't "sell" stats below the base of 10. */ 
     883                                if (stats[choice] > 10) 
     884                                { 
     885                                        int stat_cost = birth_stat_costs[stats[choice]]; 
     886                                         
     887                                        stats[choice]--; 
     888                                        points_spent[choice] -= stat_cost; 
     889                                        points_left += stat_cost; 
     890                                         
     891                                        /* Tell the UI the new points situation. */ 
     892                                        event_signal_birthstats(points_spent, points_left); 
     893 
     894                                        /* Recalculate everything that's changed because 
     895                                           the stat has changed, and inform the UI. */ 
     896                                        recalculate_stats(stats, points_left); 
     897                                }                                
     898                                break; 
     899                        } 
     900 
     901                        case CMD_ACCEPT_STATS: 
     902                        { 
     903                                next_stage = BIRTH_NAME_CHOICE; 
     904                                break; 
     905                        } 
     906                } 
     907        } 
     908 
     909        return next_stage; 
     910
     911         
     912 
     913enum birth_questions 
     914
     915        BQ_METHOD = 0, 
     916        BQ_SEX, 
     917        BQ_RACE, 
     918        BQ_CLASS, 
     919        BQ_ROLLER, 
     920        MAX_BIRTH_QUESTIONS 
     921}; 
     922 
     923enum birth_methods 
     924
     925        BM_NORMAL_BIRTH = 0, 
     926        BM_QUICKSTART, 
     927        MAX_BIRTH_METHODS 
     928}; 
     929 
     930enum birth_rollers 
     931
     932        BR_POINTBASED = 0, 
     933        BR_AUTOROLLER, 
     934        BR_NORMAL, 
     935        MAX_BIRTH_ROLLERS 
     936}; 
     937 
     938/* 
     939 * Create a new character. 
     940 * 
     941 * Note that we may be called with "junk" leftover in the various 
     942 * fields, so we must be sure to clear them first. 
     943 */ 
     944void player_birth(bool quickstart_allowed) 
    716945{ 
    717946        int i; 
    718         char s[50]; 
    719  
    720         if (race == z_info->p_max) return; 
    721  
    722         /* Display relevant details. */ 
    723         for (i = 0; i < A_MAX; i++) 
    724         { 
    725                 strnfmt(s, sizeof(s), "%s%+d", stat_names_reduced[i], 
    726                         p_info[race].r_adj[i]); 
    727                 Term_putstr(RACE_AUX_COL, TABLE_ROW + i, -1, TERM_WHITE, s); 
    728         } 
    729  
    730         strnfmt(s, sizeof(s), "Hit die: %d ", p_info[race].r_mhp); 
    731         Term_putstr(RACE_AUX_COL, TABLE_ROW + A_MAX, -1, TERM_WHITE, s); 
    732         strnfmt(s, sizeof(s), "Experience: %d%% ", p_info[race].r_exp); 
    733         Term_putstr(RACE_AUX_COL, TABLE_ROW + A_MAX + 1, -1, TERM_WHITE, s); 
    734         strnfmt(s, sizeof(s), "Infravision: %d ft ", p_info[race].infra * 10); 
    735         Term_putstr(RACE_AUX_COL, TABLE_ROW + A_MAX + 2, -1, TERM_WHITE, s); 
    736 
    737  
    738  
    739 /* 
    740  * Display additional information about each class during the selection. 
    741  */ 
    742 static void class_aux_hook(int class_idx, void *db, const region *loc) 
    743 
    744         int i; 
    745         char s[128]; 
    746  
    747         if (class_idx == z_info->c_max) return; 
    748  
    749         /* Display relevant details. */ 
    750         for (i = 0; i < A_MAX; i++) 
    751         { 
    752                 strnfmt(s, sizeof(s), "%s%+d", stat_names_reduced[i], 
    753                         c_info[class_idx].c_adj[i]); 
    754                 Term_putstr(CLASS_AUX_COL, TABLE_ROW + i, -1, TERM_WHITE, s); 
    755         } 
    756  
    757         strnfmt(s, sizeof(s), "Hit die: %d ", c_info[class_idx].c_mhp); 
    758         Term_putstr(CLASS_AUX_COL, TABLE_ROW + A_MAX, -1, TERM_WHITE, s); 
    759         strnfmt(s, sizeof(s), "Experience: %d%% ", c_info[class_idx].c_exp); 
    760         Term_putstr(CLASS_AUX_COL, TABLE_ROW + A_MAX + 1, -1, TERM_WHITE, s); 
    761 
    762  
    763  
    764 static region gender_region = {SEX_COL, TABLE_ROW, 15, -2}; 
    765 static region race_region = {RACE_COL, TABLE_ROW, 15, -2}; 
    766 static region class_region = {CLASS_COL, TABLE_ROW, 19, -2}; 
    767 static region roller_region = {44, TABLE_ROW, 21, -2}; 
    768  
    769  
    770 /* Event handler implementation */ 
    771 static bool handler_aux(char cmd, int oid, byte *val, int max, int mask, cptr topic) 
    772 
    773         if (cmd == '\xff' || cmd == '\r') { 
    774                 *val = oid; 
    775         } 
    776         else if (cmd == '*') { 
    777                 for(;;)  
    778                 { 
    779                         oid = rand_int(max); 
    780                         *val = oid; 
    781                         if(mask & (1L << oid)) break; 
    782                 } 
    783         } 
    784         else if (cmd == '=')  
    785         { 
    786                 do_cmd_options(); 
    787                 return FALSE; 
    788         } 
    789         else if (cmd == KTRL('X'))  
    790         { 
    791                 quit(NULL); 
    792         } 
    793         else if (cmd == '?') { 
    794                 char buf[80]; 
    795                 strnfmt(buf, sizeof(buf), "%s#%s", "birth.txt", topic); 
    796                 screen_save(); 
    797                 show_file(buf, NULL, 0, 0); 
    798                 screen_load(); 
    799                 return FALSE; 
    800         } 
    801         else return FALSE; 
    802  
    803         sp_ptr = &sex_info[p_ptr->psex]; 
    804         rp_ptr = &p_info[p_ptr->prace]; 
    805         cp_ptr = &c_info[p_ptr->pclass]; 
    806         mp_ptr = &cp_ptr->spells; 
    807         return TRUE; 
    808 
    809  
    810 /* GENDER */ 
    811 /* Display a gender */ 
    812 static void display_gender(menu_type *menu, int oid, bool cursor, 
    813                            int row, int col, int width) 
    814 
    815         byte attr = curs_attrs[CURS_KNOWN][0 != cursor]; 
    816         c_put_str(attr, sex_info[oid].title, row, col); 
    817 
    818  
    819 static bool gender_handler(char cmd, void *db, int oid) 
    820 
    821         return handler_aux(cmd, oid, &p_ptr->psex, SEX_MALE+1, 
    822                                                         0xffffffff, sex_info[oid].title); 
    823 
    824  
    825 /* RACE */ 
    826 static void display_race(menu_type *menu, int oid, bool cursor, 
    827                          int row, int col, int width) 
    828 
    829         byte attr = curs_attrs[CURS_KNOWN][0 != cursor]; 
    830         c_put_str(attr, p_name + p_info[oid].name, row, col); 
    831 
    832  
    833 static bool race_handler(char cmd, void *db, int oid) 
    834 
    835         return handler_aux(cmd, oid, &p_ptr->prace, z_info->p_max, 
    836                                                         0xffffffff, p_name + p_info[oid].name); 
    837 
    838  
    839 /* CLASS */ 
    840 static void display_class(menu_type *menu, int oid, bool cursor, 
    841                           int row, int col, int width) 
    842 
    843         byte attr = curs_attrs[0 != (rp_ptr->choice & (1L << oid))][0 != cursor]; 
    844         c_put_str(attr, c_name + c_info[oid].name, row, col); 
    845 
    846  
    847 static bool class_handler(char cmd, void *db, int oid) 
    848 
    849         return handler_aux(cmd, oid, &p_ptr->pclass, z_info->c_max, 
    850                                                         (rp_ptr->choice), c_name + c_info[oid].name); 
    851 
    852  
    853 /* ROLLER */ 
    854 static void display_roller(menu_type *menu, int oid, bool cursor, 
    855                            int row, int col, int width) 
    856 
    857         byte attr = curs_attrs[CURS_KNOWN][0 != cursor]; 
    858         const char *str; 
    859  
    860         if (oid == 0) 
    861                 str = "Point-based"; 
    862         else if (oid == 1) 
    863                 str = "Autoroller"; 
    864         else 
    865                 str = "Standard roller"; 
    866  
    867         c_prt(attr, str, row, col); 
    868 
    869  
    870  
    871 static byte roller_type = 0; 
    872 #define ROLLER_POINT    0 
    873 #define ROLLER_AUTO     1 
    874 #define ROLLER_STD      2 
    875  
    876 static bool roller_handler(char cmd, void *db, int oid) 
    877 
    878         if (cmd == '\xff' || cmd == '\r') 
    879         { 
    880                 roller_type = oid; 
    881                 return TRUE; 
    882         } 
    883         else if (cmd == '*') 
    884         { 
    885                 roller_type = 2; 
    886                 return TRUE; 
    887         } 
    888         else if(cmd == '=') 
    889                 do_cmd_options(); 
    890         else if(cmd == KTRL('X')) 
    891                 quit(NULL); 
    892         else if(cmd == '?') { 
    893                 char buf[80]; 
    894                 char *str; 
    895  
    896                 if (oid == 0) 
    897                         str = "Point-based"; 
    898                 else if (oid == 1) 
    899                         str = "Autoroller"; 
    900                 else 
    901                         str = "Standard roller"; 
    902  
    903                 strnfmt(buf, sizeof(buf), "%s#%s", "birth.txt", str); 
    904                 screen_save(); 
    905                 show_file(buf, NULL, 0, 0); 
    906                 screen_load(); 
    907         } 
    908  
    909         return FALSE; 
    910 
    911  
    912  
    913 static const menu_iter menu_defs[] = 
    914 
    915         { MN_NULL, 0, 0, display_gender, gender_handler }, 
    916         { MN_NULL, 0, 0, display_race, race_handler }, 
    917         { MN_NULL, 0, 0, display_class, class_handler }, 
    918         { MN_NULL, 0, 0, display_roller, roller_handler }, 
    919 }; 
    920  
    921 /* Menu display and selector */ 
    922  
    923 #define ASEX 0 
    924 #define ARACE 1 
    925 #define ACLASS 2 
    926 #define AROLL 3 
    927  
    928  
    929  
    930 static bool choose_character(bool start_at_end) 
    931 
    932         int i = 0; 
    933  
    934         const region *regions[] = { &gender_region, &race_region, &class_region, &roller_region }; 
    935         byte *values[4]; /* { &p_ptr->psex, &p_ptr->prace, &p_ptr->pclass }; */ 
    936         int limits[4]; /* { SEX_MALE +1, z_info->p_max, z_info->c_max }; */ 
    937  
    938         menu_type menu; 
    939  
    940         const char *hints[] = 
    941         { 
     947        game_command cmd = { CMD_NULL, 0 }; 
     948 
     949 
     950   /* 
     951        * The last character displayed, to allow the user to flick between two. 
     952        * We rely on prev.age being zero to determine whether there is a stored 
     953        * character or not, so initialise it here. 
     954        */ 
     955        birther prev = { 0 }; 
     956 
     957        /*  
     958         * If quickstart is allowed, we store the old character in this, 
     959         * to allow for it to be reloaded if we step back that far in the 
     960         * birth process. 
     961         */ 
     962        birther quickstart_prev = { 0 }; 
     963 
     964        enum birth_stage next_stage = BIRTH_METHOD_CHOICE; 
     965        enum birth_stage stage = BIRTH_METHOD_CHOICE; 
     966        enum birth_stage last_stage = BIRTH_METHOD_CHOICE; 
     967        enum birth_stage roller_choice = BIRTH_METHOD_CHOICE; 
     968 
     969        int roller_mins[A_MAX]; 
     970 
     971        /* Set up our "hints" for each birth question */ 
     972        const char *hints[MAX_BIRTH_QUESTIONS] = { 
     973                "Quickstart lets you make a new character based on your old one.", 
    942974                "Your 'sex' does not have any significant gameplay effects.", 
    943975                "Your 'race' determines various intrinsic factors and bonuses.", 
     
    945977                "Your choice of character generation.  Point-based is recommended." 
    946978        }; 
     979 
     980        /* Set up the list of choices for each birth question. */ 
     981        const char *quickstart_choices[MAX_BIRTH_METHODS] = {  
     982                "Normal birth process",  
     983                "Quickstart"  
     984        }; 
     985 
     986        const char *roller_choices[MAX_BIRTH_ROLLERS] = {  
     987                "Point-based",  
     988                "Autoroller",  
     989                "Standard roller"  
     990        }; 
     991 
     992        /* These require setting up in a loop later, so just allocate memory for 
     993           arrays of char * for now */ 
     994        const char *sex_choices[MAX_SEXES]; 
     995 
     996        const char **race_choices = mem_alloc(z_info->p_max * sizeof *race_choices); 
     997        const char **class_choices = mem_alloc(z_info->c_max * sizeof *class_choices); 
     998 
     999        /* For a couple of the options we have extra "help text" as well as the 
     1000           hints, */ 
     1001        const char **race_help = mem_alloc(z_info->p_max * sizeof *race_help); 
     1002        const char **class_help = mem_alloc(z_info->c_max * sizeof *class_help); 
     1003 
     1004 
     1005        /* Set up the choices for sex, race and class questions. */ 
     1006        for (i = 0; i < MAX_SEXES; i++) 
     1007                sex_choices[i] = sex_info[i].title; 
     1008 
     1009        for (i = 0; i < z_info->p_max; i++) 
     1010                race_choices[i] = p_name + p_info[i].name; 
     1011 
     1012        for (i = 0; i < z_info->c_max; i++) 
     1013                class_choices[i] = c_name + c_info[i].name; 
     1014 
     1015        /* Set up extra help text for race and class questions (basically just 
     1016           lists of various bonuses you get for each race or class). */ 
     1017        for (i = 0; i < z_info->p_max; i++) 
     1018        { 
     1019                int j; 
     1020                char *s; 
     1021                size_t end;  
     1022 
     1023                /* Race help text consists of: */ 
     1024                int bufsize =  
     1025                        sizeof "STR: xx\n" * A_MAX +  
     1026                        sizeof "Hit die: xx\n" + 
     1027                        sizeof "Experience: xxx%\n" + 
     1028                        sizeof "Infravision: xx ft\0"; 
     1029 
     1030                /* Allocate enough memory to hold that text for this race choice */ 
     1031                race_help[i] = mem_alloc(bufsize * sizeof *race_help[i]); 
     1032 
     1033                /* Set 's' up as a shorthand for race_choices[i] to make later lines 
     1034                   readable, and make sure it is an empty string. Note we cast away 
     1035                   the "constness" of rece_help[i] while we build the string, 
     1036                   safe because we know where it points (writable memory). */ 
     1037                s = (char *) race_help[i]; 
     1038                s[0] = '\0'; end = 0; 
     1039 
     1040                /* For each stat, concatanate a "STR: xx" type sequence to the 
     1041                   end of the help string. */ 
     1042                for (j = 0; j < A_MAX; j++)  
     1043                {   
     1044                        strnfcat(s, bufsize, &end, "%s%+d\n",  
     1045                                         stat_names_reduced[j], p_info[i].r_adj[j]);  
     1046                } 
     1047 
     1048                /* Concatenate all other bonuses to the end of the help string. */ 
     1049                strnfcat(s, bufsize, &end, "Hit die: %d\n", p_info[i].r_mhp); 
     1050                strnfcat(s, bufsize, &end, "Experience: %d%%\n", p_info[i].r_exp); 
     1051                strnfcat(s, bufsize, &end, "Infravision: %d ft", p_info[i].infra * 10); 
     1052        } 
     1053 
     1054        for (i = 0; i < z_info->c_max; i++) 
     1055        { 
     1056                int j; 
     1057                char *s; 
     1058                size_t end; 
     1059 
     1060                /* Class help text consists of: */ 
     1061                int bufsize =  
     1062                        sizeof "STR: xx\n" * A_MAX +  
     1063                        sizeof "Hit die: xx\n" + 
     1064                        sizeof "Experience: xxx%\n"; 
     1065 
     1066                /* Allocate enough memory to hold that text for this race choice */ 
     1067                class_help[i] = mem_alloc(bufsize * sizeof *class_help[i]); 
     1068 
     1069                /* Set 's' up as a shorthand for race_choices[i] to make later lines 
     1070                   readable, and make sure it is an empty string. Note we cast away 
     1071                   the "constness" of rece_help[i] while we build the string, 
     1072                   safe because we know where it points (writable memory). */ 
     1073                s = (char *) class_help[i]; 
     1074                s[0] = '\0'; end = 0; 
     1075 
     1076                /* For each stat, concatanate a "STR: xx" type sequence to the 
     1077                   end of the help string. */ 
     1078                for (j = 0; j < A_MAX; j++)  
     1079                {   
     1080                        strnfcat(s, bufsize, &end, "%s%+d\n",  
     1081                                         stat_names_reduced[j], c_info[i].c_adj[j]);  
     1082                } 
     1083 
     1084                /* Concatenate all other bonuses to the end of the help string. */ 
     1085                strnfcat(s, bufsize, &end, "Hit die: %d\n", c_info[i].c_mhp);    
     1086                strnfcat(s, bufsize, &end, "Experience: %d%%", c_info[i].c_exp); 
     1087        } 
     1088 
     1089        /* If there's a quickstart character, store it here. */ 
     1090        if (quickstart_allowed) 
     1091                save_roller_data(&quickstart_prev); 
     1092 
     1093        /* Now we're ready to start the interactive birth process. */ 
     1094        event_signal(EVENT_ENTER_BIRTH); 
     1095 
     1096        /* "stage" just keeps track of where we're up to in the (somewhat tortuous) 
     1097           process - the stages are laid out in approximately the right order in  
     1098           the switch statement below. */ 
     1099        stage = BIRTH_METHOD_CHOICE; 
     1100 
     1101        /* There are two ways to leave - with a working character or by quitting */ 
     1102        while (stage != BIRTH_COMPLETE) 
     1103        { 
     1104                switch (stage) 
     1105                { 
     1106                        /*  
     1107                         * First stage is to determine the birth method - currently 
     1108                         * simply whether to use quickstart or do the full birth  
     1109                         * process. 
     1110                         */ 
     1111                        case BIRTH_METHOD_CHOICE: 
     1112                        { 
     1113                                /* Set