Fix the tutorial not loading
[crawl:crawl.git] / crawl-ref / source / newgame.cc
1 /**
2  * @file
3  * @brief Functions used when starting a new game.
4 **/
5
6 #include "AppHdr.h"
7
8 #include "newgame.h"
9
10 #include "cio.h"
11 #include "command.h"
12 #include "database.h"
13 #include "files.h"
14 #include "hints.h"
15 #include "initfile.h"
16 #include "itemname.h"
17 #include "itemprop.h"
18 #include "jobs.h"
19 #include "libutil.h"
20 #include "macro.h"
21 #include "makeitem.h"
22 #include "maps.h"
23 #include "menu.h"
24 #include "monster.h"
25 #include "newgame_def.h"
26 #include "ng-input.h"
27 #include "ng-restr.h"
28 #include "options.h"
29 #include "random.h"
30 #include "species.h"
31 #include "state.h"
32 #include "stuff.h"
33
34 #ifdef USE_TILE_LOCAL
35 #include "tilereg-crt.h"
36 #endif
37
38 static void _choose_gamemode_map(newgame_def* ng, newgame_def* ng_choice,
39                                  const newgame_def& defaults);
40 static bool _choose_weapon(newgame_def* ng, newgame_def* ng_choice,
41                           const newgame_def& defaults);
42
43 ////////////////////////////////////////////////////////////////////////
44 // Remember player's startup options
45 //
46
47 newgame_def::newgame_def()
48     : name(), type(GAME_TYPE_NORMAL),
49       species(SP_UNKNOWN), job(JOB_UNKNOWN),
50       weapon(WPN_UNKNOWN),
51       fully_random(false)
52 {
53 }
54
55 void newgame_def::clear_character()
56 {
57     species  = SP_UNKNOWN;
58     job      = JOB_UNKNOWN;
59     weapon   = WPN_UNKNOWN;
60 }
61
62 enum MenuOptions
63 {
64     M_QUIT = -1,
65     M_ABORT = -2,
66     M_APTITUDES  = -3,
67     M_HELP = -4,
68     M_VIABLE = -5,
69     M_RANDOM = -6,
70     M_VIABLE_CHAR = -7,
71     M_RANDOM_CHAR = -8,
72     M_DEFAULT_CHOICE = -9,
73 };
74
75 static bool _is_random_species(species_type sp)
76 {
77     return sp == SP_RANDOM || sp == SP_VIABLE;
78 }
79
80 static bool _is_random_job(job_type job)
81 {
82     return job == JOB_RANDOM || job == JOB_VIABLE;
83 }
84
85 static bool _is_random_choice(const newgame_def& choice)
86 {
87     return _is_random_species(choice.species)
88            && _is_random_job(choice.job);
89 }
90
91 static bool _is_random_viable_choice(const newgame_def& choice)
92 {
93     return _is_random_choice(choice) &&
94            (choice.job == JOB_VIABLE || choice.species == SP_VIABLE);
95 }
96
97 static bool _char_defined(const newgame_def& ng)
98 {
99     return ng.species != SP_UNKNOWN && ng.job != JOB_UNKNOWN;
100 }
101
102 static string _char_description(const newgame_def& ng)
103 {
104     if (_is_random_viable_choice(ng))
105         return "Viable character";
106     else if (_is_random_choice(ng))
107         return "Random character";
108     else if (_is_random_job(ng.job))
109     {
110         const string j = (ng.job == JOB_RANDOM ? "Random " : "Viable ");
111         return j + species_name(ng.species);
112     }
113     else if (_is_random_species(ng.species))
114     {
115         const string s = (ng.species == SP_RANDOM ? "Random " : "Viable ");
116         return s + get_job_name(ng.job);
117     }
118     else
119         return species_name(ng.species) + " " + get_job_name(ng.job);
120 }
121
122 static string _welcome(const newgame_def* ng)
123 {
124     string text;
125     if (ng->species != SP_UNKNOWN)
126         text = species_name(ng->species);
127     if (ng->job != JOB_UNKNOWN)
128     {
129         if (!text.empty())
130             text += " ";
131         text += get_job_name(ng->job);
132     }
133     if (!ng->name.empty())
134     {
135         if (!text.empty())
136             text = " the " + text;
137         text = ng->name + text;
138     }
139     else if (!text.empty())
140         text = "unnamed " + text;
141     if (!text.empty())
142         text = ", " + text;
143     text = "Welcome" + text + ".";
144     return text;
145 }
146
147 static void _print_character_info(const newgame_def* ng)
148 {
149     clrscr();
150     textcolor(BROWN);
151     cprintf("%s\n", _welcome(ng).c_str());
152 }
153
154 #ifdef ASSERTS
155 static bool _species_is_undead(const species_type speci)
156 {
157     return speci == SP_MUMMY || speci == SP_GHOUL || speci == SP_VAMPIRE;
158 }
159 #endif
160
161 undead_state_type get_undead_state(const species_type sp)
162 {
163     switch (sp)
164     {
165     case SP_MUMMY:
166         return US_UNDEAD;
167     case SP_GHOUL:
168         return US_HUNGRY_DEAD;
169     case SP_VAMPIRE:
170         return US_SEMI_UNDEAD;
171     default:
172         ASSERT(!_species_is_undead(sp));
173         return US_ALIVE;
174     }
175 }
176
177 void choose_tutorial_character(newgame_def* ng_choice)
178 {
179     ng_choice->species = SP_HIGH_ELF;
180     ng_choice->job = JOB_FIGHTER;
181     ng_choice->weapon = WPN_FLAIL;
182 }
183
184 static void _resolve_species(newgame_def* ng, const newgame_def* ng_choice)
185 {
186     // Don't overwrite existing species.
187     if (ng->species != SP_UNKNOWN)
188         return;
189
190     switch (ng_choice->species)
191     {
192     case SP_UNKNOWN:
193         ng->species = SP_UNKNOWN;
194         return;
195
196     case SP_VIABLE:
197     {
198         int good_choices = 0;
199         for (int i = 0; i < ng_num_species(); i++)
200         {
201             species_type sp = get_species(i);
202             if (is_good_combination(sp, ng->job, false, true)
203                 && one_chance_in(++good_choices))
204             {
205                 ng->species = sp;
206             }
207         }
208         if (good_choices)
209             return;
210     }
211         // intentional fall-through
212     case SP_RANDOM:
213         if (ng->job == JOB_UNKNOWN)
214         {
215             // any valid species will do
216             do
217                 ng->species = get_species(random2(ng_num_species()));
218             while (!is_species_valid_choice(ng->species));
219         }
220         else
221         {
222             // Pick a random legal character.
223             int good_choices = 0;
224             for (int i = 0; i < ng_num_species(); i++)
225             {
226                 species_type sp = get_species(i);
227                 if (is_good_combination(sp, ng->job, false, false)
228                     && one_chance_in(++good_choices))
229                 {
230                     ng->species = sp;
231                 }
232             }
233             if (!good_choices)
234                 end(1, false, "Failed to find legal species.");
235         }
236         return;
237
238     default:
239         ng->species = ng_choice->species;
240         return;
241     }
242 }
243
244 static void _resolve_job(newgame_def* ng, const newgame_def* ng_choice)
245 {
246     if (ng->job != JOB_UNKNOWN)
247         return;
248
249     switch (ng_choice->job)
250     {
251     case JOB_UNKNOWN:
252         ng->job = JOB_UNKNOWN;
253         return;
254
255     case JOB_VIABLE:
256     {
257         int good_choices = 0;
258         for (int i = 0; i < NUM_JOBS; i++)
259         {
260             job_type job = job_type(i);
261             if (is_good_combination(ng->species, job, true, true)
262                 && one_chance_in(++good_choices))
263             {
264                 ng->job = job;
265             }
266         }
267         if (good_choices)
268             return;
269     }
270         // intentional fall-through
271     case JOB_RANDOM:
272         if (ng->species == SP_UNKNOWN)
273         {
274             // any valid job will do
275             do
276                 ng->job = job_type(random2(NUM_JOBS));
277             while (!is_job_valid_choice(ng->job));
278         }
279         else
280         {
281             // Pick a random legal character.
282             int good_choices = 0;
283             for (int i = 0; i < NUM_JOBS; i++)
284             {
285                 job_type job = job_type(i);
286                 if (is_good_combination(ng->species, job, true, false)
287                     && one_chance_in(++good_choices))
288                 {
289                     ASSERT(is_job_valid_choice(job));
290                     ng->job = job;
291                 }
292             }
293             if (!good_choices)
294                 end(1, false, "Failed to find legal background.");
295         }
296         return;
297
298     default:
299         ng->job = ng_choice->job;
300         return;
301     }
302 }
303
304 static void _resolve_species_job(newgame_def* ng, const newgame_def* ng_choice)
305 {
306     // Since recommendations are no longer bidirectional, pick one of
307     // species or job to start.
308     if (coinflip())
309     {
310         _resolve_species(ng, ng_choice);
311         _resolve_job(ng, ng_choice);
312     }
313     else
314     {
315         _resolve_job(ng, ng_choice);
316         _resolve_species(ng, ng_choice);
317     }
318 }
319
320 static string _highlight_pattern(const newgame_def* ng)
321 {
322     if (ng->species != SP_UNKNOWN)
323         return species_name(ng->species) + "  ";
324
325     if (ng->job == JOB_UNKNOWN)
326         return "";
327
328     string ret;
329     for (int i = 0; i < ng_num_species(); ++i)
330     {
331         const species_type species = get_species(i);
332         if (!is_species_valid_choice(species))
333             continue;
334
335         if (is_good_combination(species, ng->job, false, true))
336             ret += species_name(species) + "  |";
337     }
338
339     if (ret != "")
340         ret.resize(ret.size() - 1);
341     return ret;
342 }
343
344 static void _prompt_species(newgame_def* ng, newgame_def* ng_choice,
345                             const newgame_def& defaults);
346 static void _prompt_job(newgame_def* ng, newgame_def* ng_choice,
347                         const newgame_def& defaults);
348
349 static void _choose_species_job(newgame_def* ng, newgame_def* ng_choice,
350                                 const newgame_def& defaults)
351 {
352     _resolve_species_job(ng, ng_choice);
353
354     while (ng_choice->species == SP_UNKNOWN || ng_choice->job == JOB_UNKNOWN)
355     {
356         // Slightly non-obvious behaviour here is due to the fact that
357         // both _prompt_species and _prompt_job can ask for an entirely
358         // random character to be rolled. They will reset relevant fields
359         // in *ng for this purpose.
360         if (ng_choice->species == SP_UNKNOWN)
361             _prompt_species(ng, ng_choice, defaults);
362         _resolve_species_job(ng, ng_choice);
363         if (ng_choice->job == JOB_UNKNOWN)
364             _prompt_job(ng, ng_choice, defaults);
365         _resolve_species_job(ng, ng_choice);
366     }
367
368     if (!job_allowed(ng->species, ng->job))
369     {
370         // Either an invalid combination was passed in through options,
371         // or we messed up.
372         end(1, false,
373             "Incompatible species and background specified in options file.");
374     }
375 }
376
377 // For completely random combinations (!, #, or Options.game.fully_random)
378 // reroll characters until the player accepts one of them or quits.
379 static bool _reroll_random(newgame_def* ng)
380 {
381     clrscr();
382
383     string specs = chop_string(species_name(ng->species), 79, false);
384
385     cprintf("You are a%s %s %s.\n",
386             (is_vowel(specs[0])) ? "n" : "", specs.c_str(),
387             get_job_name(ng->job));
388
389     cprintf("\nDo you want to play this combination? (ynq) [y]");
390     char c = getchm();
391     if (key_is_escape(c) || toalower(c) == 'q')
392     {
393 #ifdef USE_TILE_WEB
394         tiles.send_exit_reason("cancel");
395 #endif
396         game_ended();
397     }
398     return toalower(c) == 'n';
399 }
400
401 static void _choose_char(newgame_def* ng, newgame_def* choice,
402                          newgame_def defaults)
403 {
404     const newgame_def ng_reset = *ng;
405
406     if (ng->type == GAME_TYPE_TUTORIAL)
407         choose_tutorial_character(choice);
408     else if (ng->type == GAME_TYPE_HINTS)
409         pick_hints(choice);
410
411 #if defined(DGAMELAUNCH) && defined(TOURNEY)
412     // Apologies to non-public servers.
413     if (ng->type == GAME_TYPE_NORMAL)
414     {
415         if (!yesno("Trunk games don't count for the tournament, you want "
416                    TOURNEY ". Play trunk anyway? (Y/N)", false, 'n'))
417         {
418 #ifdef USE_TILE_WEB
419             tiles.send_exit_reason("cancel");
420 #endif
421             game_ended();
422         }
423     }
424 #endif
425
426     while (true)
427     {
428         _choose_species_job(ng, choice, defaults);
429
430         if (choice->fully_random && _reroll_random(ng))
431         {
432             *ng = ng_reset;
433             continue;
434         }
435
436         if (_choose_weapon(ng, choice, defaults))
437         {
438             // We're done!
439             return;
440         }
441
442         // Else choose again, name and type stays same.
443         defaults = *choice;
444         *ng = ng_reset;
445         *choice = ng_reset;
446     }
447 }
448
449 // Read a choice of game into ng.
450 // Returns false if a game (with name ng->name) should
451 // be restored instead of starting a new character.
452 bool choose_game(newgame_def* ng, newgame_def* choice,
453                  const newgame_def& defaults)
454 {
455     clrscr();
456
457     // XXX: this should be somewhere else.
458     if (!crawl_state.startup_errors.empty()
459         && !Options.suppress_startup_errors)
460     {
461         crawl_state.show_startup_errors();
462         clrscr();
463     }
464
465     textcolor(LIGHTGREY);
466
467     ng->name = choice->name;
468     ng->type = choice->type;
469     ng->map  = choice->map;
470
471     if (ng->type == GAME_TYPE_SPRINT
472      || ng->type == GAME_TYPE_ZOTDEF
473      || ng->type == GAME_TYPE_TUTORIAL)
474     {
475         _choose_gamemode_map(ng, choice, defaults);
476     }
477
478     _choose_char(ng, choice, defaults);
479
480     // Set these again, since _mark_fully_random may reset *ng.
481     ng->name = choice->name;
482     ng->type = choice->type;
483
484 #ifndef DGAMELAUNCH
485     // New: pick name _after_ character choices.
486     if (choice->name.empty())
487     {
488         clrscr();
489
490         string specs = chop_string(species_name(ng->species), 79, false);
491
492         cprintf("You are a%s %s %s.\n",
493                 (is_vowel(specs[0])) ? "n" : "", specs.c_str(),
494                 get_job_name(ng->job));
495
496         enter_player_name(choice);
497         ng->name = choice->name;
498         ng->filename = get_save_filename(choice->name);
499
500         if (save_exists(ng->filename))
501         {
502             cprintf("\nDo you really want to overwrite your old game? ");
503             char c = getchm();
504             if (c != 'Y' && c != 'y')
505                 return true;
506         }
507     }
508 #endif
509
510     if (ng->name.empty())
511         end(1, false, "No player name specified.");
512
513     ASSERT(is_good_name(ng->name, false, false)
514            && job_allowed(ng->species, ng->job)
515            && ng->type != NUM_GAME_TYPE);
516
517     write_newgame_options_file(*choice);
518
519     return false;
520 }
521
522 // Set ng_choice to defaults without overwriting name and game type.
523 static void _set_default_choice(newgame_def* ng, newgame_def* ng_choice,
524                                 const newgame_def& defaults)
525 {
526     // Reset *ng so _resolve_species_job will work properly.
527     ng->clear_character();
528
529     const string name = ng_choice->name;
530     const game_type type   = ng_choice->type;
531     *ng_choice = defaults;
532     ng_choice->name = name;
533     ng_choice->type = type;
534 }
535
536 static void _mark_fully_random(newgame_def* ng, newgame_def* ng_choice,
537                                bool viable)
538 {
539     // Reset *ng so _resolve_species_job will work properly.
540     ng->clear_character();
541
542     ng_choice->fully_random = true;
543     if (viable)
544     {
545         ng_choice->species = SP_VIABLE;
546         ng_choice->job = JOB_VIABLE;
547     }
548     else
549     {
550         ng_choice->species = SP_RANDOM;
551         ng_choice->job = JOB_RANDOM;
552     }
553 }
554
555 /**
556  * Helper function for _choose_species
557  * Constructs the menu screen
558  */
559 static const int COLUMN_WIDTH = 25;
560 static const int X_MARGIN = 4;
561 static const int CHAR_DESC_START_Y = 16;
562 static const int CHAR_DESC_HEIGHT = 3;
563 static const int SPECIAL_KEYS_START_Y = CHAR_DESC_START_Y
564                                         + CHAR_DESC_HEIGHT + 1;
565
566 static void _construct_species_menu(const newgame_def* ng,
567                                     const newgame_def& defaults,
568                                     MenuFreeform* menu)
569 {
570     ASSERT(menu != NULL);
571     int items_in_column = 0;
572     for (int i = 0; i < NUM_SPECIES; ++i)
573         if (is_species_valid_choice((species_type)i))
574             items_in_column++;
575     items_in_column = (items_in_column + 2) / 3;
576     // Construct the menu, 3 columns
577     TextItem* tmp = NULL;
578     string text;
579     coord_def min_coord(0,0);
580     coord_def max_coord(0,0);
581
582     for (int i = 0, pos = 0; i < ng_num_species(); ++i, ++pos)
583     {
584         const species_type species = get_species(i);
585         if (!is_species_valid_choice(species)
586             || (ng->job != JOB_UNKNOWN
587                 && species_allowed(ng->job, species) == CC_BANNED))
588         {
589             --pos;
590             continue;
591         }
592
593         tmp = new TextItem();
594         text.clear();
595
596         if (ng->job == JOB_UNKNOWN)
597         {
598             tmp->set_fg_colour(LIGHTGRAY);
599             tmp->set_highlight_colour(BLUE);
600         }
601         else if (species_allowed(ng->job, species) == CC_RESTRICTED)
602         {
603             tmp->set_fg_colour(DARKGRAY);
604             tmp->set_highlight_colour(BLUE);
605         }
606         else
607         {
608             tmp->set_fg_colour(WHITE);
609             tmp->set_highlight_colour(GREEN);
610         }
611         text = index_to_letter(pos);
612         text += " - ";
613         text += species_name(species);
614         tmp->set_text(text);
615         ASSERT(pos < items_in_column * 3);
616         min_coord.x = X_MARGIN + (pos / items_in_column) * COLUMN_WIDTH;
617         min_coord.y = 3 + pos % items_in_column;
618         max_coord.x = min_coord.x + text.size();
619         max_coord.y = min_coord.y + 1;
620         tmp->set_bounds(min_coord, max_coord);
621
622         tmp->add_hotkey(index_to_letter(pos));
623         tmp->set_id(species);
624         tmp->set_description_text(unwrap_desc(getGameStartDescription(species_name(species))));
625         menu->attach_item(tmp);
626         tmp->set_visible(true);
627         if (defaults.species == species)
628             menu->set_active_item(tmp);
629     }
630
631     // Add all the special button entries
632     tmp = new TextItem();
633     tmp->set_text("+ - Viable species");
634     min_coord.x = X_MARGIN;
635     min_coord.y = SPECIAL_KEYS_START_Y;
636     max_coord.x = min_coord.x + tmp->get_text().size();
637     max_coord.y = min_coord.y + 1;
638     tmp->set_bounds(min_coord, max_coord);
639     tmp->set_fg_colour(BROWN);
640     tmp->add_hotkey('+');
641     // If the player has a job chosen, use VIABLE, otherwise use RANDOM
642     if (ng->job != JOB_UNKNOWN)
643         tmp->set_id(M_VIABLE);
644     else
645         tmp->set_id(M_RANDOM);
646     tmp->set_highlight_colour(BLUE);
647     tmp->set_description_text("Picks a random viable species based on your current job choice.");
648     menu->attach_item(tmp);
649     tmp->set_visible(true);
650
651     tmp = new TextItem();
652     tmp->set_text("# - Viable character");
653     min_coord.x = X_MARGIN;
654     min_coord.y = SPECIAL_KEYS_START_Y + 1;
655     max_coord.x = min_coord.x + tmp->get_text().size();
656     max_coord.y = min_coord.y + 1;
657     tmp->set_bounds(min_coord, max_coord);
658     tmp->set_fg_colour(BROWN);
659     tmp->add_hotkey('#');
660     tmp->set_id(M_VIABLE_CHAR);
661     tmp->set_highlight_colour(BLUE);
662     tmp->set_description_text("Shuffles through random viable character combinations "
663                               "until you accept one.");
664     menu->attach_item(tmp);
665     tmp->set_visible(true);
666
667     tmp = new TextItem();
668     tmp->set_text("% - List aptitudes");
669     min_coord.x = X_MARGIN;
670     min_coord.y = SPECIAL_KEYS_START_Y + 2;
671     max_coord.x = min_coord.x + tmp->get_text().size();
672     max_coord.y = min_coord.y + 1;
673     tmp->set_bounds(min_coord, max_coord);
674     tmp->set_fg_colour(BROWN);
675     tmp->add_hotkey('%');
676     tmp->set_id(M_APTITUDES);
677     tmp->set_description_text("Lists the numerical skill train aptitudes for all races.");
678     tmp->set_highlight_colour(BLUE);
679     menu->attach_item(tmp);
680     tmp->set_visible(true);
681
682     tmp = new TextItem();
683     tmp->set_text("? - Help");
684     min_coord.x = X_MARGIN;
685     min_coord.y = SPECIAL_KEYS_START_Y + 3;
686     max_coord.x = min_coord.x + tmp->get_text().size();
687     max_coord.y = min_coord.y + 1;
688     tmp->set_bounds(min_coord, max_coord);
689     tmp->set_fg_colour(BROWN);
690     tmp->add_hotkey('?');
691     tmp->set_id(M_HELP);
692     tmp->set_highlight_colour(BLUE);
693     tmp->set_description_text("Opens the help screen.");
694     menu->attach_item(tmp);
695     tmp->set_visible(true);
696
697     tmp = new TextItem();
698     tmp->set_text("* - Random species");
699     min_coord.x = X_MARGIN + COLUMN_WIDTH;
700     min_coord.y = SPECIAL_KEYS_START_Y;
701     max_coord.x = min_coord.x + tmp->get_text().size();
702     max_coord.y = min_coord.y + 1;
703     tmp->set_bounds(min_coord, max_coord);
704     tmp->set_fg_colour(BROWN);
705     tmp->add_hotkey('*');
706     tmp->set_id(M_RANDOM);
707     tmp->set_highlight_colour(BLUE);
708     tmp->set_description_text("Picks a random species.");
709     menu->attach_item(tmp);
710     tmp->set_visible(true);
711
712     tmp = new TextItem();
713     tmp->set_text("! - Random character");
714     min_coord.x = X_MARGIN + COLUMN_WIDTH;
715     min_coord.y = SPECIAL_KEYS_START_Y + 1;
716     max_coord.x = min_coord.x + tmp->get_text().size();
717     max_coord.y = min_coord.y + 1;
718     tmp->set_bounds(min_coord, max_coord);
719     tmp->set_fg_colour(BROWN);
720     tmp->add_hotkey('!');
721     tmp->set_id(M_RANDOM_CHAR);
722     tmp->set_highlight_colour(BLUE);
723     tmp->set_description_text("Shuffles through random character combinations "
724                               "until you accept one.");
725     menu->attach_item(tmp);
726     tmp->set_visible(true);
727
728     // Adjust the end marker to align the - because Space text is longer by 4
729     tmp = new TextItem();
730     if (ng->job != JOB_UNKNOWN)
731     {
732         tmp->set_text("Space - Change background");
733         tmp->set_description_text("Lets you change your background choice.");
734     }
735     else
736     {
737         tmp->set_text("Space - Pick background first");
738         tmp->set_description_text("Lets you pick your background first.");
739     }
740     min_coord.x = X_MARGIN + COLUMN_WIDTH - 4;
741     min_coord.y = SPECIAL_KEYS_START_Y + 2;
742     max_coord.x = min_coord.x + tmp->get_text().size();
743     max_coord.y = min_coord.y + 1;
744     tmp->set_bounds(min_coord, max_coord);
745     tmp->set_fg_colour(BROWN);
746     tmp->add_hotkey(' ');
747     tmp->set_id(M_ABORT);
748     tmp->set_highlight_colour(BLUE);
749     menu->attach_item(tmp);
750     tmp->set_visible(true);
751
752     if (_char_defined(defaults))
753     {
754         string tmp_string = "Tab - ";
755         tmp_string += _char_description(defaults).c_str();
756         // Adjust the end marker to aling the - because
757         // Tab text is longer by 2
758         tmp = new TextItem();
759         tmp->set_text(tmp_string);
760         min_coord.x = X_MARGIN + COLUMN_WIDTH - 2;
761         min_coord.y = SPECIAL_KEYS_START_Y + 3;
762         max_coord.x = min_coord.x + tmp->get_text().size();
763         max_coord.y = min_coord.y + 1;
764         tmp->set_bounds(min_coord, max_coord);
765         tmp->set_fg_colour(BROWN);
766         tmp->add_hotkey('\t');
767         tmp->set_id(M_DEFAULT_CHOICE);
768         tmp->set_highlight_colour(BLUE);
769         tmp->set_description_text("Play a new game with your previous choice.");
770         menu->attach_item(tmp);
771         tmp->set_visible(true);
772     }
773 }
774
775 // Prompt the player for a choice of species.
776 // ng should be const, but we need to reset it for _resolve_species_job
777 // to work correctly in view of fully random characters.
778 static void _prompt_species(newgame_def* ng, newgame_def* ng_choice,
779                             const newgame_def& defaults)
780 {
781     PrecisionMenu menu;
782     menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT);
783     MenuFreeform* freeform = new MenuFreeform();
784     freeform->init(coord_def(0,0), coord_def(get_number_of_cols(),
785                    get_number_of_lines()), "freeform");
786     menu.attach_object(freeform);
787     menu.set_active_object(freeform);
788
789     int keyn;
790
791     clrscr();
792
793     // TODO: attach these to the menu in a NoSelectTextItem
794     textcolor(BROWN);
795     cprintf("%s", _welcome(ng).c_str());
796
797     textcolor(YELLOW);
798     cprintf(" Please select your species.");
799
800     _construct_species_menu(ng, defaults, freeform);
801     MenuDescriptor* descriptor = new MenuDescriptor(&menu);
802     descriptor->init(coord_def(X_MARGIN, CHAR_DESC_START_Y),
803                      coord_def(get_number_of_cols(), CHAR_DESC_START_Y
804                                                      + CHAR_DESC_HEIGHT),
805                      "descriptor");
806     menu.attach_object(descriptor);
807
808     BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu);
809     highlighter->init(coord_def(0,0), coord_def(0,0), "highlighter");
810     menu.attach_object(highlighter);
811
812     // Did we have a previous species?
813     if (menu.get_active_item() == NULL)
814         freeform->activate_first_item();
815
816 #ifdef USE_TILE_LOCAL
817     tiles.get_crt()->attach_menu(&menu);
818 #endif
819
820     freeform->set_visible(true);
821     descriptor->set_visible(true);
822     highlighter->set_visible(true);
823
824     textcolor(LIGHTGREY);
825     // Poll input until we have a conclusive escape or pick
826     while (true)
827     {
828         menu.draw_menu();
829
830         keyn = getch_ck();
831
832         // First process all the menu entries available
833         if (!menu.process_key(keyn))
834         {
835             // Process all the other keys that are not assigned to the menu
836             switch (keyn)
837             {
838             case 'X':
839                 cprintf("\nGoodbye!");
840 #ifdef USE_TILE_WEB
841                 tiles.send_exit_reason("cancel");
842 #endif
843                 end(0);
844                 return;
845             CASE_ESCAPE
846 #ifdef USE_TILE_WEB
847                 tiles.send_exit_reason("cancel");
848 #endif
849                 game_ended();
850             case CK_BKSP:
851                 ng_choice->species = SP_UNKNOWN;
852                 return;
853             default:
854                 // if we get this far, we did not get a significant selection
855                 // from the menu, nor did we get an escape character
856                 // continue the while loop from the beginning and poll a new key
857                 continue;
858             }
859         }
860         // We have had a significant input key event
861         // construct the return vector
862         vector<MenuItem*> selection = menu.get_selected_items();
863         if (!selection.empty())
864         {
865             // we have a selection!
866             // we only care about the first selection (there should be only one)
867             int selection_key = selection.at(0)->get_id();
868
869             bool viable = false;
870             switch (selection_key)
871             {
872             case M_VIABLE_CHAR:
873                 viable = true;
874                 // intentional fall-through
875             case M_RANDOM_CHAR:
876                 _mark_fully_random(ng, ng_choice, viable);
877                 return;
878             case M_DEFAULT_CHOICE:
879                 if (_char_defined(defaults))
880                 {
881                     _set_default_choice(ng, ng_choice, defaults);
882                     return;
883                 }
884                 else
885                 {
886                     // ignore Tab because we don't have previous start options
887                     continue;
888                 }
889             case M_ABORT:
890                 ng->species = ng_choice->species = SP_UNKNOWN;
891                 ng->job     = ng_choice->job     = JOB_UNKNOWN;
892                 return;
893             case M_HELP:
894                  // access to the help files
895                 list_commands('1');
896                 return _prompt_species(ng, ng_choice, defaults);
897             case M_APTITUDES:
898                 list_commands('%', false, _highlight_pattern(ng));
899                 return _prompt_species(ng, ng_choice, defaults);
900             case M_VIABLE:
901                 ng_choice->species = SP_VIABLE;
902                 return;
903             case M_RANDOM:
904                 ng_choice->species = SP_RANDOM;
905                 return;
906             default:
907                 // we have a species selection
908                 species_type species = static_cast<species_type> (selection_key);
909                 if (ng->job == JOB_UNKNOWN
910                     || species_allowed(ng->job, species) != CC_BANNED)
911                 {
912                     ng_choice->species = species;
913                     return;
914                 }
915                 else
916                     continue;
917             }
918         }
919     }
920 }
921
922 void job_group::attach(const newgame_def* ng, const newgame_def& defaults,
923                        MenuFreeform* menu, menu_letter &letter)
924 {
925     TextItem* tmp = new NoSelectTextItem();
926     string text;
927     tmp->set_text(name);
928     tmp->set_fg_colour(LIGHTBLUE);
929     coord_def min_coord(2 + position.x, 3 + position.y);
930     coord_def max_coord(min_coord.x + width, min_coord.y + 1);
931     tmp->set_bounds(min_coord, max_coord);
932     menu->attach_item(tmp);
933     tmp->set_visible(true);
934
935     for (unsigned int i = 0; i < ARRAYSZ(jobs); ++i)
936     {
937         job_type &job = jobs[i];
938         if (job == JOB_UNKNOWN)
939             break;
940
941         if (ng->species != SP_UNKNOWN
942             && job_allowed(ng->species, job) == CC_BANNED)
943         {
944             continue;
945         }
946
947         tmp = new TextItem();
948         if (ng->species == SP_UNKNOWN)
949         {
950             tmp->set_fg_colour(LIGHTGRAY);
951             tmp->set_highlight_colour(BLUE);
952         }
953         else if(job_allowed(ng->species, job) == CC_RESTRICTED)
954         {
955             tmp->set_fg_colour(DARKGRAY);
956             tmp->set_highlight_colour(BLUE);
957         }
958         else
959         {
960             tmp->set_fg_colour(WHITE);
961             tmp->set_highlight_colour(GREEN);
962         }
963
964         text = letter;
965         text += " - ";
966         text += get_job_name(job);
967         tmp->set_text(text);
968         ++min_coord.y;
969         ++max_coord.y;
970         tmp->set_bounds(min_coord, max_coord);
971         tmp->add_hotkey(letter++);
972         tmp->set_id(job);
973         tmp->set_description_text(unwrap_desc(getGameStartDescription(get_job_name(job))));
974         menu->attach_item(tmp);
975         tmp->set_visible(true);
976         if (defaults.job == job)
977             menu->set_active_item(tmp);
978     }
979 }
980
981 /**
982  * Helper for _choose_job
983  * constructs the menu used and highlights the previous job if there is one
984  */
985 static void _construct_backgrounds_menu(const newgame_def* ng,
986                                         const newgame_def& defaults,
987                                         MenuFreeform* menu)
988 {
989     job_group jobs_order[] =
990     {
991         {
992             "Warrior",
993             coord_def(0, 0), 15,
994             {JOB_FIGHTER, JOB_GLADIATOR, JOB_MONK, JOB_HUNTER, JOB_ASSASSIN,
995              JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN}
996         },
997         {
998             "Adventurer",
999             coord_def(0, 7), 15,
1000             {JOB_ARTIFICER, JOB_WANDERER, JOB_UNKNOWN, JOB_UNKNOWN,
1001              JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN}
1002         },
1003         {
1004             "Zealot",
1005             coord_def(15, 0), 20,
1006             {JOB_BERSERKER, JOB_ABYSSAL_KNIGHT, JOB_CHAOS_KNIGHT,
1007              JOB_DEATH_KNIGHT, JOB_HEALER, JOB_UNKNOWN, JOB_UNKNOWN,
1008              JOB_UNKNOWN, JOB_UNKNOWN}
1009         },
1010         {
1011             "Warrior-mage",
1012             coord_def(35, 0), 21,
1013             {JOB_SKALD, JOB_TRANSMUTER, JOB_WARPER, JOB_ARCANE_MARKSMAN,
1014              JOB_ENCHANTER, JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN}
1015         },
1016         {
1017             "Mage",
1018             coord_def(56, 0), 22,
1019             {JOB_WIZARD, JOB_CONJURER, JOB_SUMMONER, JOB_NECROMANCER,
1020              JOB_FIRE_ELEMENTALIST, JOB_ICE_ELEMENTALIST,
1021              JOB_AIR_ELEMENTALIST, JOB_EARTH_ELEMENTALIST, JOB_VENOM_MAGE}
1022         }
1023     };
1024
1025     menu_letter letter = 'a';
1026     for (unsigned int i = 0; i < ARRAYSZ(jobs_order); ++i)
1027         jobs_order[i].attach(ng, defaults, menu, letter);
1028
1029     // Add all the special button entries
1030     TextItem* tmp = new TextItem();
1031     tmp->set_text("+ - Viable background");
1032     coord_def min_coord = coord_def(X_MARGIN, SPECIAL_KEYS_START_Y);
1033     coord_def max_coord = coord_def(min_coord.x + tmp->get_text().size(),
1034                                     min_coord.y + 1);
1035     tmp->set_bounds(min_coord, max_coord);
1036     tmp->set_fg_colour(BROWN);
1037     tmp->add_hotkey('+');
1038     // If the player has species chosen, use VIABLE, otherwise use RANDOM
1039     if (ng->species != SP_UNKNOWN)
1040         tmp->set_id(M_VIABLE);
1041     else
1042         tmp->set_id(M_RANDOM);
1043     tmp->set_highlight_colour(BLUE);
1044     tmp->set_description_text("Picks a random viable background based on your current species choice.");
1045     menu->attach_item(tmp);
1046     tmp->set_visible(true);
1047
1048     tmp = new TextItem();
1049     tmp->set_text("# - Viable character");
1050     min_coord.x = X_MARGIN;
1051     min_coord.y = SPECIAL_KEYS_START_Y + 1;
1052     max_coord.x = min_coord.x + tmp->get_text().size();
1053     max_coord.y = min_coord.y + 1;
1054     tmp->set_bounds(min_coord, max_coord);
1055     tmp->set_fg_colour(BROWN);
1056     tmp->add_hotkey('#');
1057     tmp->set_id(M_VIABLE_CHAR);
1058     tmp->set_highlight_colour(BLUE);
1059     tmp->set_description_text("Shuffles through random viable character combinations "
1060                               "until you accept one.");
1061     menu->attach_item(tmp);
1062     tmp->set_visible(true);
1063
1064     tmp = new TextItem();
1065     tmp->set_text("% - List aptitudes");
1066     min_coord.x = X_MARGIN;
1067     min_coord.y = SPECIAL_KEYS_START_Y + 2;
1068     max_coord.x = min_coord.x + tmp->get_text().size();
1069     max_coord.y = min_coord.y + 1;
1070     tmp->set_bounds(min_coord, max_coord);
1071     tmp->set_fg_colour(BROWN);
1072     tmp->add_hotkey('%');
1073     tmp->set_id(M_APTITUDES);
1074     tmp->set_highlight_colour(BLUE);
1075     tmp->set_description_text("Lists the numerical skill train aptitudes for all races.");
1076     menu->attach_item(tmp);
1077     tmp->set_visible(true);
1078
1079     tmp = new TextItem();
1080     tmp->set_text("? - Help");
1081     min_coord.x = X_MARGIN;
1082     min_coord.y = SPECIAL_KEYS_START_Y + 3;
1083     max_coord.x = min_coord.x + tmp->get_text().size();
1084     max_coord.y = min_coord.y + 1;
1085     tmp->set_bounds(min_coord, max_coord);
1086     tmp->set_fg_colour(BROWN);
1087     tmp->add_hotkey('?');
1088     tmp->set_id(M_HELP);
1089     tmp->set_highlight_colour(BLUE);
1090     tmp->set_description_text("Opens the help screen.");
1091     menu->attach_item(tmp);
1092     tmp->set_visible(true);
1093
1094     tmp = new TextItem();
1095     tmp->set_text("* - Random background");
1096     min_coord.x = X_MARGIN + COLUMN_WIDTH;
1097     min_coord.y = SPECIAL_KEYS_START_Y;
1098     max_coord.x = min_coord.x + tmp->get_text().size();
1099     max_coord.y = min_coord.y + 1;
1100     tmp->set_bounds(min_coord, max_coord);
1101     tmp->set_fg_colour(BROWN);
1102     tmp->add_hotkey('*');
1103     tmp->set_id(M_RANDOM);
1104     tmp->set_highlight_colour(BLUE);
1105     tmp->set_description_text("Picks a random background.");
1106     menu->attach_item(tmp);
1107     tmp->set_visible(true);
1108
1109     tmp = new TextItem();
1110     tmp->set_text("! - Random character");
1111     min_coord.x = X_MARGIN + COLUMN_WIDTH;
1112     min_coord.y = SPECIAL_KEYS_START_Y + 1;
1113     max_coord.x = min_coord.x + tmp->get_text().size();
1114     max_coord.y = min_coord.y + 1;
1115     tmp->set_bounds(min_coord, max_coord);
1116     tmp->set_fg_colour(BROWN);
1117     tmp->add_hotkey('!');
1118     tmp->set_id(M_RANDOM_CHAR);
1119     tmp->set_highlight_colour(BLUE);
1120     tmp->set_description_text("Shuffles through random character combinations "
1121                               "until you accept one.");
1122     menu->attach_item(tmp);
1123     tmp->set_visible(true);
1124
1125     // Adjust the end marker to align the - because Space text is longer by 4
1126     tmp = new TextItem();
1127     if (ng->species != SP_UNKNOWN)
1128     {
1129         tmp->set_text("Space - Change species");
1130         tmp->set_description_text("Lets you change your species choice.");
1131     }
1132     else
1133     {
1134         tmp->set_text("Space - Pick species first");
1135         tmp->set_description_text("Lets you pick your species first.");
1136
1137     }
1138     min_coord.x = X_MARGIN + COLUMN_WIDTH - 4;
1139     min_coord.y = SPECIAL_KEYS_START_Y + 2;
1140     max_coord.x = min_coord.x + tmp->get_text().size();
1141     max_coord.y = min_coord.y + 1;
1142     tmp->set_bounds(min_coord, max_coord);
1143     tmp->set_fg_colour(BROWN);
1144     tmp->add_hotkey(' ');
1145     tmp->set_id(M_ABORT);
1146     tmp->set_highlight_colour(BLUE);
1147     menu->attach_item(tmp);
1148     tmp->set_visible(true);
1149
1150     if (_char_defined(defaults))
1151     {
1152         // Adjust the end marker to align the - because
1153         // Tab text is longer by 2
1154         tmp = new TextItem();
1155         tmp->set_text("Tab - " + _char_description(defaults));
1156         min_coord.x = X_MARGIN + COLUMN_WIDTH - 2;
1157         min_coord.y = SPECIAL_KEYS_START_Y + 3;
1158         max_coord.x = min_coord.x + tmp->get_text().size();
1159         max_coord.y = min_coord.y + 1;
1160         tmp->set_bounds(min_coord, max_coord);
1161         tmp->set_fg_colour(BROWN);
1162         tmp->add_hotkey('\t');
1163         tmp->set_id(M_DEFAULT_CHOICE);
1164         tmp->set_highlight_colour(BLUE);
1165         tmp->set_description_text("Play a new game with your previous choice.");
1166         menu->attach_item(tmp);
1167         tmp->set_visible(true);
1168     }
1169 }
1170
1171 /**
1172  * _prompt_job menu
1173  * Saves the choice to ng_choice, doesn't resolve random choices.
1174  *
1175  * ng should be const, but we need to reset it for _resolve_species_job
1176  * to work correctly in view of fully random characters.
1177  */
1178 static void _prompt_job(newgame_def* ng, newgame_def* ng_choice,
1179                         const newgame_def& defaults)
1180 {
1181     PrecisionMenu menu;
1182     menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT);
1183     MenuFreeform* freeform = new MenuFreeform();
1184     freeform->init(coord_def(0,0), coord_def(get_number_of_cols() + 1,
1185                    get_number_of_lines() + 1), "freeform");
1186     menu.attach_object(freeform);
1187     menu.set_active_object(freeform);
1188
1189     int keyn;
1190
1191     clrscr();
1192
1193     // TODO: attach these to the menu in a NoSelectTextItem
1194     textcolor(BROWN);
1195     cprintf("%s", _welcome(ng).c_str());
1196
1197     textcolor(YELLOW);
1198     cprintf(" Please select your background.");
1199
1200     _construct_backgrounds_menu(ng, defaults, freeform);
1201     MenuDescriptor* descriptor = new MenuDescriptor(&menu);
1202     descriptor->init(coord_def(X_MARGIN, CHAR_DESC_START_Y),
1203                      coord_def(get_number_of_cols(), CHAR_DESC_START_Y + 3),
1204                      "descriptor");
1205     menu.attach_object(descriptor);
1206
1207     BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu);
1208     highlighter->init(coord_def(0,0), coord_def(0,0), "highlighter");
1209     menu.attach_object(highlighter);
1210
1211     // Did we have a previous background?
1212     if (menu.get_active_item() == NULL)
1213         freeform->activate_first_item();
1214
1215 #ifdef USE_TILE_LOCAL
1216     tiles.get_crt()->attach_menu(&menu);
1217 #endif
1218
1219     freeform->set_visible(true);
1220     descriptor->set_visible(true);
1221     highlighter->set_visible(true);
1222
1223     textcolor(LIGHTGREY);
1224
1225     // Poll input until we have a conclusive escape or pick
1226     while (true)
1227     {
1228         menu.draw_menu();
1229
1230         keyn = getch_ck();
1231
1232         // First process all the menu entries available
1233         if (!menu.process_key(keyn))
1234         {
1235             // Process all the other keys that are not assigned to the menu
1236             switch (keyn)
1237             {
1238             case 'X':
1239                 cprintf("\nGoodbye!");
1240 #ifdef USE_TILE_WEB
1241                 tiles.send_exit_reason("cancel");
1242 #endif
1243                 end(0);
1244                 return;
1245             CASE_ESCAPE
1246 #ifdef USE_TILE_WEB
1247                 tiles.send_exit_reason("cancel");
1248 #endif
1249                 game_ended();
1250             case CK_BKSP:
1251                 ng_choice->job = JOB_UNKNOWN;
1252                 return;
1253             default:
1254                 // if we get this far, we did not get a significant selection
1255                 // from the menu, nor did we get an escape character
1256                 // continue the while loop from the beginning and poll a new key
1257                 continue;
1258             }
1259         }
1260         // We have had a significant input key event
1261         // construct the return vector
1262         vector<MenuItem*> selection = menu.get_selected_items();
1263         if (!selection.empty())
1264         {
1265             // we have a selection!
1266             // we only care about the first selection (there should be only one)
1267             int selection_key = selection.at(0)->get_id();
1268
1269             bool viable = false;
1270             switch (selection_key)
1271             {
1272             case M_VIABLE_CHAR:
1273                 viable = true;
1274                 // intentional fall-through
1275             case M_RANDOM_CHAR:
1276                 _mark_fully_random(ng, ng_choice, viable);
1277                 return;
1278             case M_DEFAULT_CHOICE:
1279                 if (_char_defined(defaults))
1280                 {
1281                     _set_default_choice(ng, ng_choice, defaults);
1282                     return;
1283                 }
1284                 else
1285                 {
1286                     // ignore default because we don't have previous start options
1287                     continue;
1288                 }
1289             case M_ABORT:
1290                 ng->species = ng_choice->species = SP_UNKNOWN;
1291                 ng->job     = ng_choice->job     = JOB_UNKNOWN;
1292                 return;
1293             case M_HELP:
1294                  // access to the help files
1295                 list_commands('2');
1296                 return _prompt_job(ng, ng_choice, defaults);
1297             case M_APTITUDES:
1298                 list_commands('%', false, _highlight_pattern(ng));
1299                 return _prompt_job(ng, ng_choice, defaults);
1300             case M_VIABLE:
1301                 ng_choice->job = JOB_VIABLE;
1302                 return;
1303             case M_RANDOM:
1304                 ng_choice->job = JOB_RANDOM;
1305                 return;
1306             default:
1307                 // we have a job selection
1308                 job_type job = static_cast<job_type> (selection_key);
1309                 if (ng->species == SP_UNKNOWN
1310                     || job_allowed(ng->species, job) != CC_BANNED)
1311                 {
1312                     ng_choice->job = job;
1313                     return;
1314                 }
1315                 else
1316                 {
1317                     selection.at(0)->select(false);
1318                     continue;
1319                 }
1320             }
1321         }
1322     }
1323 }
1324
1325 typedef pair<weapon_type, char_choice_restriction> weapon_choice;
1326
1327 static weapon_type _fixup_weapon(weapon_type wp,
1328                                  const vector<weapon_choice>& weapons)
1329 {
1330     if (wp == WPN_UNKNOWN || wp == WPN_RANDOM || wp == WPN_VIABLE)
1331         return wp;
1332     for (unsigned int i = 0; i < weapons.size(); ++i)
1333         if (wp == weapons[i].first)
1334             return wp;
1335     return WPN_UNKNOWN;
1336 }
1337
1338 static void _construct_weapon_menu(const newgame_def* ng,
1339                                    const weapon_type& defweapon,
1340                                    const vector<weapon_choice>& weapons,
1341                                    MenuFreeform* menu)
1342 {
1343     static const int ITEMS_START_Y = 5;
1344     TextItem* tmp = NULL;
1345     string text;
1346     coord_def min_coord(0,0);
1347     coord_def max_coord(0,0);
1348
1349     for (unsigned int i = 0; i < weapons.size(); ++i)
1350     {
1351         tmp = new TextItem();
1352         text.clear();
1353
1354         if (weapons[i].second == CC_UNRESTRICTED)
1355         {
1356             tmp->set_fg_colour(WHITE);
1357             tmp->set_highlight_colour(GREEN);
1358         }
1359         else
1360         {
1361             tmp->set_fg_colour(LIGHTGRAY);
1362             tmp->set_highlight_colour(BLUE);
1363         }
1364         const char letter = 'a' + i;
1365         tmp->add_hotkey(letter);
1366         tmp->set_id(weapons[i].first);
1367
1368         text += letter;
1369         text += " - ";
1370         switch (weapons[i].first)
1371         {
1372         case WPN_UNARMED:
1373             text += "claws";
1374             break;
1375         case WPN_THROWN:
1376             if (species_can_throw_large_rocks(ng->species))
1377                 text += "large rocks";
1378             else if (species_size(ng->species, PSIZE_TORSO) <= SIZE_SMALL)
1379                 text += "tomahawks";
1380             else
1381                 text += "javelins";
1382             break;
1383         default:
1384             text += weapon_base_name(weapons[i].first);
1385             break;
1386         }
1387         // Fill to column width to give extra padding for the highlight
1388         text.append(COLUMN_WIDTH - text.size() - 1 , ' ');
1389         tmp->set_text(text);
1390
1391         min_coord.x = X_MARGIN;
1392         min_coord.y = ITEMS_START_Y + i;
1393         max_coord.x = min_coord.x + text.size();
1394         max_coord.y = min_coord.y + 1;
1395         tmp->set_bounds(min_coord, max_coord);
1396
1397         menu->attach_item(tmp);
1398         tmp->set_visible(true);
1399         // Is this item our default weapon?
1400         if (weapons[i].first == defweapon)
1401             menu->set_active_item(tmp);
1402     }
1403     // Add all the special button entries
1404     tmp = new TextItem();
1405     tmp->set_text("+ - Viable random choice");
1406     min_coord.x = X_MARGIN;
1407     min_coord.y = SPECIAL_KEYS_START_Y;
1408     max_coord.x = min_coord.x + tmp->get_text().size();
1409     max_coord.y = min_coord.y + 1;
1410     tmp->set_bounds(min_coord, max_coord);
1411     tmp->set_fg_colour(BROWN);
1412     tmp->add_hotkey('+');
1413     tmp->set_id(M_VIABLE);
1414     tmp->set_highlight_colour(BLUE);
1415     tmp->set_description_text("Picks a random viable weapon");
1416     menu->attach_item(tmp);
1417     tmp->set_visible(true);
1418
1419     tmp = new TextItem();
1420     tmp->set_text("% - List aptitudes");
1421     min_coord.x = X_MARGIN;
1422     min_coord.y = SPECIAL_KEYS_START_Y + 1;
1423     max_coord.x = min_coord.x + tmp->get_text().size();
1424     max_coord.y = min_coord.y + 1;
1425     tmp->set_bounds(min_coord, max_coord);
1426     tmp->set_fg_colour(BROWN);
1427     tmp->add_hotkey('%');
1428     tmp->set_id(M_APTITUDES);
1429     tmp->set_highlight_colour(BLUE);
1430     tmp->set_description_text("Lists the numerical skill train aptitudes for all races");
1431     menu->attach_item(tmp);
1432     tmp->set_visible(true);
1433
1434     tmp = new TextItem();
1435     tmp->set_text("? - Help");
1436     min_coord.x = X_MARGIN;
1437     min_coord.y = SPECIAL_KEYS_START_Y + 2;
1438     max_coord.x = min_coord.x + tmp->get_text().size();
1439     max_coord.y = min_coord.y + 1;
1440     tmp->set_bounds(min_coord, max_coord);
1441     tmp->set_fg_colour(BROWN);
1442     tmp->add_hotkey('?');
1443     tmp->set_id(M_HELP);
1444     tmp->set_highlight_colour(BLUE);
1445     tmp->set_description_text("Opens the help screen");
1446     menu->attach_item(tmp);
1447     tmp->set_visible(true);
1448
1449     tmp = new TextItem();
1450     tmp->set_text("* - Random weapon");
1451     min_coord.x = X_MARGIN + COLUMN_WIDTH;
1452     min_coord.y = SPECIAL_KEYS_START_Y;
1453     max_coord.x = min_coord.x + tmp->get_text().size();
1454     max_coord.y = min_coord.y + 1;
1455     tmp->set_bounds(min_coord, max_coord);
1456     tmp->set_fg_colour(BROWN);
1457     tmp->add_hotkey('*');
1458     tmp->set_id(WPN_RANDOM);
1459     tmp->set_highlight_colour(BLUE);
1460     tmp->set_description_text("Picks a random weapon");
1461     menu->attach_item(tmp);
1462     tmp->set_visible(true);
1463
1464     // Adjust the end marker to align the - because Bksp text is longer by 3
1465     tmp = new TextItem();
1466     tmp->set_text("Bksp - Return to character menu");
1467     tmp->set_description_text("Lets you return back to Character choice menu");
1468     min_coord.x = X_MARGIN + COLUMN_WIDTH - 3;
1469     min_coord.y = SPECIAL_KEYS_START_Y + 1;
1470     max_coord.x = min_coord.x + tmp->get_text().size();
1471     max_coord.y = min_coord.y + 1;
1472     tmp->set_bounds(min_coord, max_coord);
1473     tmp->set_fg_colour(BROWN);
1474     tmp->add_hotkey(CK_BKSP);
1475     tmp->set_id(M_ABORT);
1476     tmp->set_highlight_colour(BLUE);
1477     menu->attach_item(tmp);
1478     tmp->set_visible(true);
1479
1480     if (defweapon != WPN_UNKNOWN)
1481     {
1482         text.clear();
1483         text = "Tab - ";
1484
1485         text += defweapon == WPN_RANDOM  ? "Random" :
1486                 defweapon == WPN_VIABLE  ? "Viable" :
1487                 defweapon == WPN_UNARMED ? "claws"  :
1488                 weapon_base_name(defweapon);
1489
1490         // Adjust the end marker to aling the - because
1491         // Tab text is longer by 2
1492         tmp = new TextItem();
1493         tmp->set_text(text);
1494         min_coord.x = X_MARGIN + COLUMN_WIDTH - 2;
1495         min_coord.y = SPECIAL_KEYS_START_Y + 2;
1496         max_coord.x = min_coord.x + tmp->get_text().size();
1497         max_coord.y = min_coord.y + 1;
1498         tmp->set_bounds(min_coord, max_coord);
1499         tmp->set_fg_colour(BROWN);
1500         tmp->add_hotkey('\t');
1501         tmp->set_id(M_DEFAULT_CHOICE);
1502         tmp->set_highlight_colour(BLUE);
1503         tmp->set_description_text("Select your old weapon");
1504         menu->attach_item(tmp);
1505         tmp->set_visible(true);
1506     }
1507 }
1508
1509 /**
1510  * Returns false if user escapes
1511  */
1512 static bool _prompt_weapon(const newgame_def* ng, newgame_def* ng_choice,
1513                            const newgame_def& defaults,
1514                            const vector<weapon_choice>& weapons)
1515 {
1516     PrecisionMenu menu;
1517     menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT);
1518     MenuFreeform* freeform = new MenuFreeform();
1519     freeform->init(coord_def(1,1), coord_def(get_number_of_cols(),
1520                    get_number_of_lines()), "freeform");
1521     menu.attach_object(freeform);
1522     menu.set_active_object(freeform);
1523
1524     weapon_type defweapon = _fixup_weapon(defaults.weapon, weapons);
1525
1526     _construct_weapon_menu(ng, defweapon, weapons, freeform);
1527
1528     BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu);
1529     highlighter->init(coord_def(0,0), coord_def(0,0), "highlighter");
1530     menu.attach_object(highlighter);
1531
1532     // Did we have a previous weapon?
1533     if (menu.get_active_item() == NULL)
1534         freeform->activate_first_item();
1535     _print_character_info(ng); // calls clrscr() so needs to be before attach()
1536
1537 #ifdef USE_TILE_LOCAL
1538     tiles.get_crt()->attach_menu(&menu);
1539 #endif
1540
1541     freeform->set_visible(true);
1542     highlighter->set_visible(true);
1543
1544     textcolor(CYAN);
1545     cprintf("\nYou have a choice of weapons:  ");
1546
1547     while (true)
1548     {
1549         menu.draw_menu();
1550
1551         int keyn = getch_ck();
1552
1553         // First process menu entries
1554         if (!menu.process_key(keyn))
1555         {
1556             // Process all the keys that are not attached to items
1557             switch (keyn)
1558             {
1559             case 'X':
1560                 cprintf("\nGoodbye!");
1561 #ifdef USE_TILE_WEB
1562                 tiles.send_exit_reason("cancel");
1563 #endif
1564                 end(0);
1565                 break;
1566             case ' ':
1567             CASE_ESCAPE
1568                 return false;
1569             default:
1570                 // if we get this far, we did not get a significant selection
1571                 // from the menu, nor did we get an escape character
1572                 // continue the while loop from the beginning and poll a new key
1573                 continue;
1574             }
1575         }
1576         // We have a significant key input!
1577         // Construct selection vector
1578         vector<MenuItem*> selection = menu.get_selected_items();
1579         // There should only be one selection, otherwise something broke
1580         if (selection.size() != 1)
1581         {
1582             // poll a new key
1583             continue;
1584         }
1585
1586         // Get the stored id from the selection
1587         int selection_ID = selection.at(0)->get_id();
1588         switch (selection_ID)
1589         {
1590         case M_ABORT:
1591             return false;
1592         case M_APTITUDES:
1593             list_commands('%', false, _highlight_pattern(ng));
1594             return _prompt_weapon(ng, ng_choice, defaults, weapons);
1595         case M_HELP:
1596             list_commands('?');
1597             return _prompt_weapon(ng, ng_choice, defaults, weapons);
1598         case M_DEFAULT_CHOICE:
1599             if (defweapon != WPN_UNKNOWN)
1600             {
1601                 ng_choice->weapon = defweapon;
1602                 return true;
1603             }
1604             // No default weapon defined.
1605             // This case should never happen in those cases but just in case
1606             continue;
1607         case M_VIABLE:
1608             ng_choice->weapon = WPN_VIABLE;
1609             return true;
1610         case M_RANDOM:
1611             ng_choice->weapon = WPN_RANDOM;
1612             return true;
1613         default:
1614             // We got an item selection
1615             ng_choice->weapon = static_cast<weapon_type> (selection_ID);
1616             return true;
1617         }
1618     }
1619     // This should never happen
1620     return false;
1621 }
1622
1623 static vector<weapon_choice> _get_weapons(const newgame_def* ng)
1624 {
1625     vector<weapon_choice> weapons;
1626     if (ng->job == JOB_HUNTER || ng->job == JOB_ARCANE_MARKSMAN)
1627     {
1628         weapon_type startwep[4] = { WPN_THROWN, WPN_SLING, WPN_BOW,
1629                                     WPN_CROSSBOW };
1630
1631         for (int i = 0; i < 4; i++)
1632         {
1633             weapon_choice wp;
1634             wp.first = startwep[i];
1635
1636             wp.second = weapon_restriction(wp.first, *ng);
1637             if (wp.second != CC_BANNED)
1638                 weapons.push_back(wp);
1639         }
1640     }
1641     else
1642     {
1643         weapon_type startwep[7] = { WPN_UNARMED, WPN_SHORT_SWORD, WPN_MACE,
1644                                     WPN_HAND_AXE, WPN_SPEAR, WPN_FALCHION,
1645                                     WPN_QUARTERSTAFF};
1646         for (int i = 0; i < 7; ++i)
1647         {
1648             weapon_choice wp;
1649             wp.first = startwep[i];
1650
1651             switch (wp.first)
1652             {
1653             case WPN_UNARMED:
1654                 if (!species_has_claws(ng->species))
1655                     continue;
1656                 break;
1657             case WPN_SHORT_SWORD:
1658                 // Fighters and gladiators get cutlasses.
1659                 if (ng->job == JOB_GLADIATOR || ng->job == JOB_FIGHTER)
1660                     wp.first = WPN_CUTLASS;
1661                 break;
1662             case WPN_MACE:
1663                 // Fighters and gladiators get flails.
1664                 if (ng->job == JOB_GLADIATOR || ng->job == JOB_FIGHTER)
1665                     wp.first = WPN_FLAIL;
1666                 break;
1667             case WPN_HAND_AXE:
1668                 // Non-little fighters and gladiators get war axes.
1669                 if (ng->job == JOB_GLADIATOR || ng->job == JOB_FIGHTER
1670                       && species_size(ng->species, PSIZE_BODY) >= SIZE_SMALL)
1671                 {
1672                     wp.first = WPN_WAR_AXE;
1673                 }
1674                 break;
1675             case WPN_SPEAR:
1676                 // Non-small fighters and gladiators get tridents.
1677                 if (ng->job == JOB_GLADIATOR || ng->job == JOB_FIGHTER
1678                       && species_size(ng->species, PSIZE_BODY) >= SIZE_MEDIUM)
1679                 {
1680                     wp.first = WPN_TRIDENT;
1681                 }
1682                 break;
1683             case WPN_FALCHION:
1684                 // Non-little fighters and gladiators get long swords.
1685                 if (ng->job == JOB_GLADIATOR || ng->job == JOB_FIGHTER
1686                       && species_size(ng->species, PSIZE_BODY) >= SIZE_SMALL)
1687                 {
1688                     wp.first = WPN_LONG_SWORD;
1689                 }
1690                 break;
1691             default:
1692                 break;
1693             }
1694
1695             wp.second = weapon_restriction(wp.first, *ng);
1696             if (wp.second != CC_BANNED)
1697                 weapons.push_back(wp);
1698         }
1699     }
1700     return weapons;
1701 }
1702
1703 static void _resolve_weapon(newgame_def* ng, newgame_def* ng_choice,
1704                             const vector<weapon_choice>& weapons)
1705 {
1706     switch (ng_choice->weapon)
1707     {
1708     case WPN_UNKNOWN:
1709         ng->weapon = WPN_UNKNOWN;
1710         return;
1711
1712     case WPN_VIABLE:
1713     {
1714         int good_choices = 0;
1715         for (unsigned int i = 0; i < weapons.size(); i++)
1716         {
1717             if (weapons[i].second == CC_UNRESTRICTED
1718                 && one_chance_in(++good_choices))
1719             {
1720                 ng->weapon = weapons[i].first;
1721             }
1722         }
1723         if (good_choices)
1724             return;
1725     }
1726         // intentional fall-through
1727     case WPN_RANDOM:
1728         ng->weapon = weapons[random2(weapons.size())].first;
1729         return;
1730
1731     default:
1732         // Check this is a legal choice, in case it came
1733         // through command line options.
1734         ng->weapon = _fixup_weapon(ng_choice->weapon, weapons);
1735         if (ng->weapon == WPN_UNKNOWN)
1736         {
1737             // Either an invalid combination was passed in through options,
1738             // or we messed up.
1739             end(1, false,
1740                 "Incompatible weapon specified in options file.");
1741         }
1742         return;
1743     }
1744 }
1745
1746 // Returns false if aborted, else an actual weapon choice
1747 // is written to ng->weapon for the jobs that call
1748 // _update_weapon() later.
1749 static bool _choose_weapon(newgame_def* ng, newgame_def* ng_choice,
1750                            const newgame_def& defaults)
1751 {
1752     // No weapon use at all.  The actual item will be removed later.
1753     if (ng->species == SP_FELID)
1754         return true;
1755
1756     switch (ng->job)
1757     {
1758     case JOB_FIGHTER:
1759     case JOB_GLADIATOR:
1760     case JOB_BERSERKER:
1761     case JOB_CHAOS_KNIGHT:
1762     case JOB_DEATH_KNIGHT:
1763     case JOB_ABYSSAL_KNIGHT:
1764     case JOB_SKALD:
1765     case JOB_WARPER:
1766     case JOB_HUNTER:
1767     case JOB_ARCANE_MARKSMAN:
1768         break;
1769     default:
1770         return true;
1771     }
1772
1773     vector<weapon_choice> weapons = _get_weapons(ng);
1774
1775     ASSERT(!weapons.empty());
1776     if (weapons.size() == 1)
1777     {
1778         ng->weapon = ng_choice->weapon = weapons[0].first;
1779         return true;
1780     }
1781
1782     if (ng_choice->weapon == WPN_UNKNOWN
1783         && !_prompt_weapon(ng, ng_choice, defaults, weapons))
1784     {
1785         return false;
1786     }
1787
1788     _resolve_weapon(ng, ng_choice, weapons);
1789     return true;
1790 }
1791
1792 static void _construct_gamemode_map_menu(const mapref_vector& maps,
1793                                          const newgame_def& defaults,
1794                                          MenuFreeform* menu)
1795 {
1796     static const int ITEMS_START_Y = 5;
1797     static const int MENU_COLUMN_WIDTH = get_number_of_cols();
1798     TextItem* tmp = NULL;
1799     string text;
1800     coord_def min_coord(0,0);
1801     coord_def max_coord(0,0);
1802     bool activate_next = false;
1803
1804     unsigned int padding_width = 0;
1805     for (int i = 0; i < static_cast<int> (maps.size()); i++)
1806     {
1807         padding_width = max<int>(padding_width,
1808                                  strwidth(maps.at(i)->desc_or_name()));
1809     }
1810     padding_width += 4; // Count the letter and " - "
1811     padding_width = min<int>(padding_width, MENU_COLUMN_WIDTH - 1);
1812
1813     for (int i = 0; i < static_cast<int> (maps.size()); i++)
1814     {
1815         tmp = new TextItem();
1816         text.clear();
1817
1818         tmp->set_fg_colour(LIGHTGREY);
1819         tmp->set_highlight_colour(GREEN);
1820
1821         const char letter = 'a' + i;
1822         text += letter;
1823         text += " - ";
1824
1825         text += maps[i]->desc_or_name();
1826         text = chop_string(text, padding_width);
1827
1828         tmp->set_text(text);
1829         tmp->add_hotkey(letter);
1830         tmp->set_id(i); // ID corresponds to location in vector
1831
1832         min_coord.x = X_MARGIN;
1833         min_coord.y = ITEMS_START_Y + i;
1834         max_coord.x = min_coord.x + text.size();
1835         max_coord.y = min_coord.y + 1;
1836         tmp->set_bounds(min_coord, max_coord);
1837
1838         menu->attach_item(tmp);
1839         tmp->set_visible(true);
1840
1841         if (activate_next)
1842         {
1843             menu->set_active_item(tmp);
1844             activate_next = false;
1845         }
1846         // Is this item our default map?
1847         else if (defaults.map == maps[i]->name)
1848         {
1849             if (crawl_state.last_game_won)
1850                 activate_next = true;
1851             else
1852                 menu->set_active_item(tmp);
1853         }
1854     }
1855
1856     // Don't overwhelm new players with aptitudes or the full list of commands!
1857     if (!crawl_state.game_is_tutorial())
1858     {
1859         tmp = new TextItem();
1860         tmp->set_text("% - List aptitudes");
1861         min_coord.x = X_MARGIN;
1862         min_coord.y = SPECIAL_KEYS_START_Y + 1;
1863         max_coord.x = min_coord.x + tmp->get_text().size();
1864         max_coord.y = min_coord.y + 1;
1865         tmp->set_bounds(min_coord, max_coord);
1866         tmp->set_fg_colour(BROWN);
1867         tmp->add_hotkey('%');
1868         tmp->set_id(M_APTITUDES);
1869         tmp->set_highlight_colour(LIGHTGRAY);
1870         tmp->set_description_text("Lists the numerical skill train aptitudes for all races");
1871         menu->attach_item(tmp);
1872         tmp->set_visible(true);
1873
1874         tmp = new TextItem();
1875         tmp->set_text("? - Help");
1876         min_coord.x = X_MARGIN;
1877         min_coord.y = SPECIAL_KEYS_START_Y + 2;
1878         max_coord.x = min_coord.x + tmp->get_text().size();
1879         max_coord.y = min_coord.y + 1;
1880         tmp->set_bounds(min_coord, max_coord);
1881         tmp->set_fg_colour(BROWN);
1882         tmp->add_hotkey('?');
1883         tmp->set_id(M_HELP);
1884         tmp->set_highlight_colour(LIGHTGRAY);
1885         tmp->set_description_text("Opens the help screen");
1886         menu->attach_item(tmp);
1887         tmp->set_visible(true);
1888
1889         tmp = new TextItem();
1890         tmp->set_text("* - Random map");
1891         min_coord.x = X_MARGIN + COLUMN_WIDTH;
1892         min_coord.y = SPECIAL_KEYS_START_Y + 1;
1893         max_coord.x = min_coord.x + tmp->get_text().size();
1894         max_coord.y = min_coord.y + 1;
1895         tmp->set_bounds(min_coord, max_coord);
1896         tmp->set_fg_colour(BROWN);
1897         tmp->add_hotkey('*');
1898         tmp->set_id(M_RANDOM);
1899         tmp->set_highlight_colour(LIGHTGRAY);
1900         tmp->set_description_text("Picks a random sprint map");
1901         menu->attach_item(tmp);
1902         tmp->set_visible(true);
1903     }
1904
1905     // TODO: let players escape back to first screen menu
1906     // Adjust the end marker to align the - because Bksp text is longer by 3
1907     //tmp = new TextItem();
1908     //tmp->set_text("Bksp - Return to character menu");
1909     //tmp->set_description_text("Lets you return back to Character choice menu");
1910     //min_coord.x = X_MARGIN + COLUMN_WIDTH - 3;
1911     //min_coord.y = SPECIAL_KEYS_START_Y + 1;
1912     //max_coord.x = min_coord.x + tmp->get_text().size();
1913     //max_coord.y = min_coord.y + 1;
1914     //tmp->set_bounds(min_coord, max_coord);
1915     //tmp->set_fg_colour(BROWN);
1916     //tmp->add_hotkey(CK_BKSP);
1917     //tmp->set_id(M_ABORT);
1918     //tmp->set_highlight_colour(LIGHTGRAY);
1919     //menu->attach_item(tmp);
1920     //tmp->set_visible(true);
1921
1922     // Only add tab entry if we have a previous map choice
1923     if (crawl_state.game_is_sprint() && !defaults.map.empty()
1924         && defaults.type == GAME_TYPE_SPRINT && _char_defined(defaults))
1925     {
1926         tmp = new TextItem();
1927         text.clear();
1928         text += "Tab - ";
1929         text += defaults.map;
1930
1931         // Adjust the end marker to align the - because
1932         // Tab text is longer by 2
1933         tmp->set_text(text);
1934         min_coord.x = X_MARGIN + COLUMN_WIDTH - 2;
1935         min_coord.y = SPECIAL_KEYS_START_Y + 2;
1936         max_coord.x = min_coord.x + tmp->get_text().size();
1937         max_coord.y = min_coord.y + 1;
1938         tmp->set_bounds(min_coord, max_coord);
1939         tmp->set_fg_colour(BROWN);
1940         tmp->add_hotkey('\t');
1941         tmp->set_id(M_DEFAULT_CHOICE);
1942         tmp->set_highlight_colour(LIGHTGRAY);
1943         tmp->set_description_text("Select your previous sprint map and character");
1944         menu->attach_item(tmp);
1945         tmp->set_visible(true);
1946     }
1947 }
1948
1949 // Compare two maps by their ORDER: header, falling back to desc or name if equal.
1950 static bool _cmp_map_by_order(const map_def* m1, const map_def* m2)
1951 {
1952     return m1->order < m2->order
1953            || m1->order == m2->order && m1->desc_or_name() < m2->desc_or_name();
1954 }
1955
1956 static void _prompt_gamemode_map(newgame_def* ng, newgame_def* ng_choice,
1957                                  const newgame_def& defaults,
1958                                  mapref_vector maps)
1959 {
1960     PrecisionMenu menu;
1961     menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT);
1962     MenuFreeform* freeform = new MenuFreeform();
1963     freeform->init(coord_def(1,1), coord_def(get_number_of_cols(),
1964                    get_number_of_lines()), "freeform");
1965     menu.attach_object(freeform);
1966     menu.set_active_object(freeform);
1967
1968     sort(maps.begin(), maps.end(), _cmp_map_by_order);
1969     _construct_gamemode_map_menu(maps, defaults, freeform);
1970
1971     BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu);
1972     highlighter->init(coord_def(0,0), coord_def(0,0), "highlighter");
1973     menu.attach_object(highlighter);
1974
1975     // Did we have a previous sprint map?
1976     if (menu.get_active_item() == NULL)
1977         freeform->activate_first_item();
1978
1979     _print_character_info(ng); // calls clrscr() so needs to be before attach()
1980
1981 #ifdef USE_TILE_LOCAL
1982     tiles.get_crt()->attach_menu(&menu);
1983 #endif
1984
1985     freeform->set_visible(true);
1986     highlighter->set_visible(true);
1987
1988     textcolor(CYAN);
1989     cprintf("\nYou have a choice of %s:\n\n",
1990             ng_choice->type == GAME_TYPE_TUTORIAL ? "lessons"
1991                                                   : "maps");
1992
1993     while (true)
1994     {
1995         menu.draw_menu();
1996
1997         int keyn = getch_ck();
1998
1999         // First process menu entries
2000         if (!menu.process_key(keyn))
2001         {
2002             // Process all the keys that are not attached to items
2003             switch (keyn)
2004             {
2005             case 'X':
2006                 cprintf("\nGoodbye!");
2007 #ifdef USE_TILE_WEB
2008                 tiles.send_exit_reason("cancel");
2009 #endif
2010                 end(0);
2011                 break;
2012             CASE_ESCAPE
2013 #ifdef USE_TILE_WEB
2014                 tiles.send_exit_reason("cancel");
2015 #endif
2016                 game_ended();
2017                 break;
2018             case ' ':
2019                 return;
2020             default:
2021                 // if we get this far, we did not get a significant selection
2022                 // from the menu, nor did we get an escape character
2023                 // continue the while loop from the beginning and poll a new key
2024                 continue;
2025             }
2026         }
2027         // We have a significant key input!
2028         // Construct selection vector
2029         vector<MenuItem*> selection = menu.get_selected_items();
2030         // There should only be one selection, otherwise something broke
2031         if (selection.size() != 1)
2032         {
2033             // poll a new key
2034             continue;
2035         }
2036
2037         // Get the stored id from the selection
2038         int selection_ID = selection.at(0)->get_id();
2039         switch (selection_ID)
2040         {
2041         case M_ABORT:
2042             // TODO: fix
2043             return;
2044         case M_APTITUDES:
2045             list_commands('%', false, _highlight_pattern(ng));
2046             return _prompt_gamemode_map(ng, ng_choice, defaults, maps);
2047         case M_HELP:
2048             list_commands('?');
2049             return _prompt_gamemode_map(ng, ng_choice, defaults, maps);
2050         case M_DEFAULT_CHOICE:
2051             _set_default_choice(ng, ng_choice, defaults);
2052             return;
2053         case M_RANDOM:
2054             // FIXME setting this to "random" is broken
2055             ng_choice->map.clear();
2056             return;
2057         default:
2058             // We got an item selection
2059             ng_choice->map = maps.at(selection_ID)->name;
2060             return;
2061         }
2062     }
2063 }
2064
2065 static void _resolve_gamemode_map(newgame_def* ng, const newgame_def* ng_choice,
2066                                   const mapref_vector& maps)
2067 {
2068     if (ng_choice->map == "random" || ng_choice->map.empty())
2069         ng->map = maps[random2(maps.size())]->name;
2070     else
2071         ng->map = ng_choice->map;
2072 }
2073
2074 static void _choose_gamemode_map(newgame_def* ng, newgame_def* ng_choice,
2075                                  const newgame_def& defaults)
2076 {
2077     // Sprint, otherwise Tutorial.
2078     const bool is_sprint = (ng_choice->type == GAME_TYPE_SPRINT);
2079
2080     const string type_name = gametype_to_str(ng_choice->type);
2081
2082     const mapref_vector maps = find_maps_for_tag(type_name);
2083
2084     if (maps.empty())
2085         end(1, true, "No %s maps found.", type_name.c_str());
2086
2087     if (ng_choice->map.empty())
2088     {
2089         if (is_sprint
2090             && ng_choice->type == !crawl_state.sprint_map.empty())
2091         {
2092             ng_choice->map = crawl_state.sprint_map;
2093         }
2094         else if (maps.size() > 1)
2095             _prompt_gamemode_map(ng, ng_choice, defaults, maps);
2096         else
2097             ng_choice->map = maps[0]->name;
2098     }
2099
2100     _resolve_gamemode_map(ng, ng_choice, maps);
2101 }