Apply the new ground_level method.
[crawl.git] / crawl-ref / source / newgame.cc
blobfabc5573699abbeb70711c07f40de017bc2f2df5
1 /*
2 * File: newgame.cc
3 * Summary: Functions used when starting a new game.
4 * Written by: Linley Henzell
5 */
7 #include "AppHdr.h"
9 #include "newgame.h"
11 #include "cio.h"
12 #include "command.h"
13 #include "database.h"
14 #include "files.h"
15 #include "hints.h"
16 #include "initfile.h"
17 #include "itemname.h"
18 #include "itemprop.h"
19 #include "jobs.h"
20 #include "macro.h"
21 #include "makeitem.h"
22 #include "maps.h"
23 #include "menu.h"
24 #include "ng-input.h"
25 #include "ng-restr.h"
26 #include "options.h"
27 #include "random.h"
28 #include "religion.h"
29 #include "species.h"
30 #include "sprint.h"
31 #include "state.h"
32 #include "stuff.h"
33 #include "tutorial.h"
35 #ifdef USE_TILE
36 #include "tilereg-crt.h"
37 #endif
39 static void _choose_gamemode_map(newgame_def* ng, newgame_def* ng_choice,
40 const newgame_def& defaults);
41 static bool _choose_weapon(newgame_def* ng, newgame_def* ng_choice,
42 const newgame_def& defaults);
43 static bool _choose_book(newgame_def* ng, newgame_def* ng_choice,
44 const newgame_def& defaults);
45 static bool _choose_god(newgame_def* ng, newgame_def* ng_choice,
46 const newgame_def& defaults);
47 static bool _choose_wand(newgame_def* ng, newgame_def* ng_choice,
48 const newgame_def& defaults);
50 ////////////////////////////////////////////////////////////////////////
51 // Remember player's startup options
54 newgame_def::newgame_def()
55 : name(), type(GAME_TYPE_NORMAL),
56 species(SP_UNKNOWN), job(JOB_UNKNOWN),
57 weapon(WPN_UNKNOWN), book(SBT_NONE),
58 religion(GOD_NO_GOD), wand(SWT_NO_SELECTION),
59 fully_random(false)
63 void newgame_def::clear_character()
65 species = SP_UNKNOWN;
66 job = JOB_UNKNOWN;
67 weapon = WPN_UNKNOWN;
68 book = SBT_NONE;
69 religion = GOD_NO_GOD;
70 wand = SWT_NO_SELECTION;
73 enum MenuOptions
75 M_QUIT = -1,
76 M_ABORT = -2,
77 M_APTITUDES = -3,
78 M_HELP = -4,
79 M_VIABLE = -5,
80 M_RANDOM = -6,
81 M_VIABLE_CHAR = -7,
82 M_RANDOM_CHAR = -8,
83 M_DEFAULT_CHOICE = -9,
86 static bool _is_random_species(species_type sp)
88 return (sp == SP_RANDOM || sp == SP_VIABLE);
91 static bool _is_random_job(job_type job)
93 return (job == JOB_RANDOM || job == JOB_VIABLE);
96 static bool _is_random_choice(const newgame_def& choice)
98 return (_is_random_species(choice.species)
99 && _is_random_job(choice.job));
102 static bool _is_random_viable_choice(const newgame_def& choice)
104 return (_is_random_choice(choice) &&
105 (choice.job == JOB_VIABLE || choice.species == SP_VIABLE));
108 static bool _char_defined(const newgame_def& ng)
110 return (ng.species != SP_UNKNOWN && ng.job != JOB_UNKNOWN);
113 static std::string _char_description(const newgame_def& ng)
115 if (_is_random_viable_choice(ng))
116 return "Viable character";
117 else if (_is_random_choice(ng))
118 return "Random character";
119 else if (_is_random_job(ng.job))
121 const std::string j = (ng.job == JOB_RANDOM ? "Random " : "Viable ");
122 return (j + species_name(ng.species));
124 else if (_is_random_species(ng.species))
126 const std::string s = (ng.species == SP_RANDOM ? "Random "
127 : "Viable ");
128 return (s + get_job_name(ng.job));
130 else
131 return (species_name(ng.species) + " " + get_job_name(ng.job));
134 static std::string _welcome(const newgame_def* ng)
136 std::string text;
137 if (ng->species != SP_UNKNOWN)
138 text = species_name(ng->species);
139 if (ng->job != JOB_UNKNOWN)
141 if (!text.empty())
142 text += " ";
143 text += get_job_name(ng->job);
145 if (!ng->name.empty())
147 if (!text.empty())
148 text = " the " + text;
149 text = ng->name + text;
151 else if (!text.empty())
152 text = "unnamed " + text;
153 if (!text.empty())
154 text = ", " + text;
155 text = "Welcome" + text + ".";
156 return (text);
159 static void _print_character_info(const newgame_def* ng)
161 clrscr();
162 textcolor(BROWN);
163 cprintf("%s\n", _welcome(ng).c_str());
166 // Determines if a species is valid. If 'display' is true, returns if
167 // the species is displayable in the new game screen - this is
168 // primarily used to suppress the display of the draconian variants.
169 static bool _is_species_valid_choice(species_type species)
171 if (species < 0 || species > NUM_SPECIES)
172 return (false);
174 if (species >= SP_ELF) // These are all invalid.
175 return (false);
177 // Non-base draconians cannot be selected either.
178 if (species >= SP_RED_DRACONIAN && species < SP_BASE_DRACONIAN)
179 return (false);
181 return (true);
184 #ifdef ASSERTS
185 static bool _species_is_undead(const species_type speci)
187 return (speci == SP_MUMMY || speci == SP_GHOUL || speci == SP_VAMPIRE);
189 #endif
191 undead_state_type get_undead_state(const species_type sp)
193 switch (sp)
195 case SP_MUMMY:
196 return US_UNDEAD;
197 case SP_GHOUL:
198 return US_HUNGRY_DEAD;
199 case SP_VAMPIRE:
200 return US_SEMI_UNDEAD;
201 default:
202 ASSERT(!_species_is_undead(sp));
203 return (US_ALIVE);
207 void choose_tutorial_character(newgame_def* ng_choice)
209 ng_choice->species = SP_HIGH_ELF;
210 ng_choice->job = JOB_FIGHTER;
211 ng_choice->weapon = WPN_MACE;
214 static void _resolve_species(newgame_def* ng, const newgame_def* ng_choice)
216 // Don't overwrite existing species.
217 if (ng->species != SP_UNKNOWN)
218 return;
220 switch (ng_choice->species)
222 case SP_UNKNOWN:
223 ng->species = SP_UNKNOWN;
224 return;
226 case SP_VIABLE:
228 int good_choices = 0;
229 for (int i = 0; i < ng_num_species(); i++)
231 species_type sp = get_species(i);
232 if (is_good_combination(sp, ng->job, true)
233 && one_chance_in(++good_choices))
235 ng->species = sp;
238 if (good_choices)
239 return;
241 // intentional fall-through
242 case SP_RANDOM:
243 if (ng->job == JOB_UNKNOWN)
245 // any species will do
246 ng->species = get_species(random2(ng_num_species()));
248 else
250 // Pick a random legal character.
251 int good_choices = 0;
252 for (int i = 0; i < ng_num_species(); i++)
254 species_type sp = get_species(i);
255 if (is_good_combination(sp, ng->job, false)
256 && one_chance_in(++good_choices))
258 ng->species = sp;
261 if (!good_choices)
262 end(1, false, "Failed to find legal species.");
264 return;
266 default:
267 ng->species = ng_choice->species;
268 return;
272 static void _resolve_job(newgame_def* ng, const newgame_def* ng_choice)
274 if (ng->job != JOB_UNKNOWN)
275 return;
277 switch (ng_choice->job)
279 case JOB_UNKNOWN:
280 ng->job = JOB_UNKNOWN;
281 return;
283 case JOB_VIABLE:
285 int good_choices = 0;
286 for (int i = 0; i < ng_num_jobs(); i++)
288 job_type job = get_job(i);
289 if (is_good_combination(ng->species, job, true)
290 && one_chance_in(++good_choices))
292 ng->job = job;
295 if (good_choices)
296 return;
298 // intentional fall-through
299 case JOB_RANDOM:
300 if (ng->species == SP_UNKNOWN)
302 // any job will do
303 ng->job = get_job(random2(ng_num_jobs()));
305 else
307 // Pick a random legal character.
308 int good_choices = 0;
309 for (int i = 0; i < ng_num_jobs(); i++)
311 job_type job = get_job(i);
312 if (is_good_combination(ng->species, job, false)
313 && one_chance_in(++good_choices))
315 ng->job = job;
318 if (!good_choices)
319 end(1, false, "Failed to find legal background.");
321 return;
323 default:
324 ng->job = ng_choice->job;
325 return;
329 static void _resolve_species_job(newgame_def* ng, const newgame_def* ng_choice)
331 _resolve_species(ng, ng_choice);
332 _resolve_job(ng, ng_choice);
335 static std::string _highlight_pattern(const newgame_def* ng)
337 if (ng->species != SP_UNKNOWN)
338 return species_name(ng->species) + " ";
340 if (ng->job == JOB_UNKNOWN)
341 return "";
343 std::string ret;
344 for (int i = 0; i < ng_num_species(); ++i)
346 const species_type species = get_species(i);
347 if (!_is_species_valid_choice(species))
348 continue;
350 if (is_good_combination(species, ng->job, true))
351 ret += species_name(species) + " |";
354 ret.resize(ret.size() - 1);
355 return ret;
358 static void _prompt_species(newgame_def* ng, newgame_def* ng_choice,
359 const newgame_def& defaults);
360 static void _prompt_job(newgame_def* ng, newgame_def* ng_choice,
361 const newgame_def& defaults);
363 static void _choose_species_job(newgame_def* ng, newgame_def* ng_choice,
364 const newgame_def& defaults)
366 _resolve_species_job(ng, ng_choice);
368 while (ng_choice->species == SP_UNKNOWN || ng_choice->job == JOB_UNKNOWN)
370 // Slightly non-obvious behaviour here is due to the fact that
371 // both _prompt_species and _prompt_job can ask for an entirely
372 // random character to be rolled. They will reset relevant fields
373 // in *ng for this purpose.
374 if (ng_choice->species == SP_UNKNOWN)
375 _prompt_species(ng, ng_choice, defaults);
376 _resolve_species_job(ng, ng_choice);
377 if (ng_choice->job == JOB_UNKNOWN)
378 _prompt_job(ng, ng_choice, defaults);
379 _resolve_species_job(ng, ng_choice);
382 if (!job_allowed(ng->species, ng->job))
384 // Either an invalid combination was passed in through options,
385 // or we messed up.
386 end(1, false,
387 "Incompatible species and background specified in options file.");
391 // For completely random combinations (!, #, or Options.game.fully_random)
392 // reroll characters until the player accepts one of them or quits.
393 static bool _reroll_random(newgame_def* ng)
395 clrscr();
397 std::string specs = species_name(ng->species);
398 if (specs.length() > 79)
399 specs = specs.substr(0, 79);
401 cprintf("You are a%s %s %s.\n",
402 (is_vowel(specs[0])) ? "n" : "", specs.c_str(),
403 get_job_name(ng->job));
405 cprintf("\nDo you want to play this combination? (ynq) [y]");
406 char c = getchm();
407 if (key_is_escape(c) || tolower(c) == 'q')
408 game_ended();
409 return (tolower(c) == 'n');
412 static void _choose_char(newgame_def* ng, newgame_def* choice,
413 newgame_def defaults)
415 const newgame_def ng_reset = *ng;
417 if (ng->type == GAME_TYPE_TUTORIAL)
418 choose_tutorial_character(choice);
419 else if (ng->type == GAME_TYPE_HINTS)
420 pick_hints(choice);
422 while (true)
424 _choose_species_job(ng, choice, defaults);
426 if (choice->fully_random && _reroll_random(ng))
428 *ng = ng_reset;
429 continue;
432 if (_choose_weapon(ng, choice, defaults)
433 && _choose_book(ng, choice, defaults)
434 && _choose_god(ng, choice, defaults)
435 && _choose_wand(ng, choice, defaults))
437 // We're done!
438 return;
441 // Else choose again, name and type stays same.
442 defaults = *choice;
443 *ng = ng_reset;
444 *choice = ng_reset;
448 // Read a choice of game into ng.
449 // Returns false if a game (with name ng->name) should
450 // be restored instead of starting a new character.
451 bool choose_game(newgame_def* ng, newgame_def* choice,
452 const newgame_def& defaults)
454 clrscr();
456 // XXX: this should be somewhere else.
457 if (!crawl_state.startup_errors.empty()
458 && !Options.suppress_startup_errors)
460 crawl_state.show_startup_errors();
461 clrscr();
464 textcolor(LIGHTGREY);
466 ng->name = choice->name;
467 ng->type = choice->type;
468 ng->map = choice->map;
470 if (ng->type == GAME_TYPE_SPRINT || ng->type == GAME_TYPE_TUTORIAL)
471 _choose_gamemode_map(ng, choice, defaults);
473 _choose_char(ng, choice, defaults);
475 // Set these again, since _mark_fully_random may reset *ng.
476 ng->name = choice->name;
477 ng->type = choice->type;
479 #ifndef DGAMELAUNCH
480 // New: pick name _after_ character choices.
481 if (choice->name.empty())
483 clrscr();
485 std::string specs = species_name(ng->species);
486 if (specs.length() > 79)
487 specs = specs.substr(0, 79);
489 cprintf("You are a%s %s %s.\n",
490 (is_vowel(specs[0])) ? "n" : "", specs.c_str(),
491 get_job_name(ng->job));
493 enter_player_name(choice);
494 ng->name = choice->name;
496 if (save_exists(choice->name))
498 cprintf("\nDo you really want to overwrite your old game? ");
499 char c = getchm();
500 if (c != 'Y' && c != 'y')
501 return (true);
504 #endif
506 if (ng->name.empty())
507 end(1, false, "No player name specified.");
509 ASSERT(is_good_name(ng->name, false, false)
510 && job_allowed(ng->species, ng->job)
511 && ng->type != NUM_GAME_TYPE);
513 write_newgame_options_file(*choice);
515 return (false);
518 int start_to_book(int firstbook, int booktype)
520 switch (firstbook)
522 case BOOK_MINOR_MAGIC_I:
523 switch (booktype)
525 case SBT_FIRE:
526 return (BOOK_MINOR_MAGIC_I);
528 case SBT_COLD:
529 return (BOOK_MINOR_MAGIC_II);
531 case SBT_SUMM:
532 return (BOOK_MINOR_MAGIC_III);
534 default:
535 return (-1);
538 case BOOK_CONJURATIONS_I:
539 switch (booktype)
541 case SBT_FIRE:
542 return (BOOK_CONJURATIONS_I);
544 case SBT_COLD:
545 return (BOOK_CONJURATIONS_II);
547 default:
548 return (-1);
551 default:
552 return (-1);
556 void make_rod(item_def &item, stave_type rod_type, int ncharges)
558 item.base_type = OBJ_STAVES;
559 item.sub_type = rod_type;
560 item.quantity = 1;
561 item.special = you.item_description[IDESC_STAVES][rod_type];
562 item.colour = BROWN;
564 init_rod_mp(item, ncharges);
567 // Set ng_choice to defaults without overwriting name and game type.
568 static void _set_default_choice(newgame_def* ng, newgame_def* ng_choice,
569 const newgame_def& defaults)
571 // Reset *ng so _resolve_species_job will work properly.
572 ng->clear_character();
574 const std::string name = ng_choice->name;
575 const game_type type = ng_choice->type;
576 *ng_choice = defaults;
577 ng_choice->name = name;
578 ng_choice->type = type;
581 static void _mark_fully_random(newgame_def* ng, newgame_def* ng_choice,
582 bool viable)
584 // Reset *ng so _resolve_species_job will work properly.
585 ng->clear_character();
587 ng_choice->fully_random = true;
588 if (viable)
590 ng_choice->species = SP_VIABLE;
591 ng_choice->job = JOB_VIABLE;
593 else
595 ng_choice->species = SP_RANDOM;
596 ng_choice->job = JOB_RANDOM;
601 * Helper function for _choose_species
602 * Constructs the menu screen
604 static const int COLUMN_WIDTH = 25;
605 static const int X_MARGIN = 4;
606 static const int CHAR_DESC_START_Y = 17;
607 static const int SPECIAL_KEYS_START_Y = CHAR_DESC_START_Y + 3;
608 static void _construct_species_menu(const newgame_def* ng,
609 const newgame_def& defaults,
610 MenuFreeform* menu)
612 ASSERT(menu != NULL);
613 static const int ITEMS_IN_COLUMN = 8;
614 // Construct the menu, 3 columns
615 TextItem* tmp = NULL;
616 std::string text;
617 coord_def min_coord(0,0);
618 coord_def max_coord(0,0);
620 for (int i = 0; i < ng_num_species(); ++i)
622 const species_type species = get_species(i);
623 if (!_is_species_valid_choice(species))
624 continue;
626 tmp = new TextItem();
627 text.clear();
629 if (ng->job == JOB_UNKNOWN
630 || job_allowed(species, ng->job) == CC_UNRESTRICTED)
632 tmp->set_fg_colour(LIGHTGRAY);
633 tmp->set_highlight_colour(GREEN);
635 else
637 tmp->set_fg_colour(DARKGRAY);
638 tmp->set_highlight_colour(YELLOW);
640 if (ng->job != JOB_UNKNOWN
641 && job_allowed(species, ng->job) == CC_BANNED)
643 text = " ";
644 text += species_name(species);
645 text += " N/A";
646 tmp->set_fg_colour(DARKGRAY);
647 tmp->set_highlight_colour(RED);
649 else
651 text = index_to_letter(i);
652 text += " - ";
653 text += species_name(species);
655 // Fill to column width - 1
656 text.append(COLUMN_WIDTH - text.size() - 1 , ' ');
657 tmp->set_text(text);
658 min_coord.x = X_MARGIN + (i / ITEMS_IN_COLUMN) * COLUMN_WIDTH;
659 min_coord.y = 3 + i % ITEMS_IN_COLUMN;
660 max_coord.x = min_coord.x + text.size();
661 max_coord.y = min_coord.y + 1;
662 tmp->set_bounds(min_coord, max_coord);
664 tmp->add_hotkey(index_to_letter(i));
665 tmp->set_id(species);
666 tmp->set_description_text(getGameStartDescription(species_name(species)));
667 menu->attach_item(tmp);
668 tmp->set_visible(true);
669 if (defaults.species == species)
671 menu->set_active_item(tmp);
675 // Add all the special button entries
676 tmp = new TextItem();
677 tmp->set_text("+ - Viable species");
678 min_coord.x = X_MARGIN;
679 min_coord.y = SPECIAL_KEYS_START_Y;
680 max_coord.x = min_coord.x + tmp->get_text().size();
681 max_coord.y = min_coord.y + 1;
682 tmp->set_bounds(min_coord, max_coord);
683 tmp->set_fg_colour(BROWN);
684 tmp->add_hotkey('+');
685 // If the player has a job chosen, use VIABLE, otherwise use RANDOM
686 if (ng->job != JOB_UNKNOWN)
688 tmp->set_id(M_VIABLE);
690 else
692 tmp->set_id(M_RANDOM);
694 tmp->set_highlight_colour(LIGHTGRAY);
695 tmp->set_description_text("Picks a random viable species based on your current job choice");
696 menu->attach_item(tmp);
697 tmp->set_visible(true);
699 tmp = new TextItem();
700 tmp->set_text("# - Viable character");
701 min_coord.x = X_MARGIN;
702 min_coord.y = SPECIAL_KEYS_START_Y + 1;
703 max_coord.x = min_coord.x + tmp->get_text().size();
704 max_coord.y = min_coord.y + 1;
705 tmp->set_bounds(min_coord, max_coord);
706 tmp->set_fg_colour(BROWN);
707 tmp->add_hotkey('#');
708 tmp->set_id(M_VIABLE_CHAR);
709 tmp->set_highlight_colour(LIGHTGRAY);
710 tmp->set_description_text("Shuffles through random viable character combinations "
711 "until you accept one");
712 menu->attach_item(tmp);
713 tmp->set_visible(true);
715 tmp = new TextItem();
716 tmp->set_text("% - List aptitudes");
717 min_coord.x = X_MARGIN;
718 min_coord.y = SPECIAL_KEYS_START_Y + 2;
719 max_coord.x = min_coord.x + tmp->get_text().size();
720 max_coord.y = min_coord.y + 1;
721 tmp->set_bounds(min_coord, max_coord);
722 tmp->set_fg_colour(BROWN);
723 tmp->add_hotkey('%');
724 tmp->set_id(M_APTITUDES);
725 tmp->set_description_text("Lists the numerical skill train aptitudes for all races");
726 tmp->set_highlight_colour(LIGHTGRAY);
727 menu->attach_item(tmp);
728 tmp->set_visible(true);
730 tmp = new TextItem();
731 tmp->set_text("? - Help");
732 min_coord.x = X_MARGIN;
733 min_coord.y = SPECIAL_KEYS_START_Y + 3;
734 max_coord.x = min_coord.x + tmp->get_text().size();
735 max_coord.y = min_coord.y + 1;
736 tmp->set_bounds(min_coord, max_coord);
737 tmp->set_fg_colour(BROWN);
738 tmp->add_hotkey('?');
739 tmp->set_id(M_HELP);
740 tmp->set_highlight_colour(LIGHTGRAY);
741 tmp->set_description_text("Opens the help screen");
742 menu->attach_item(tmp);
743 tmp->set_visible(true);
745 tmp = new TextItem();
746 tmp->set_text("* - Random species");
747 min_coord.x = X_MARGIN + COLUMN_WIDTH;
748 min_coord.y = SPECIAL_KEYS_START_Y;
749 max_coord.x = min_coord.x + tmp->get_text().size();
750 max_coord.y = min_coord.y + 1;
751 tmp->set_bounds(min_coord, max_coord);
752 tmp->set_fg_colour(BROWN);
753 tmp->add_hotkey('*');
754 tmp->set_id(M_RANDOM);
755 tmp->set_highlight_colour(LIGHTGRAY);
756 tmp->set_description_text("Picks a random species");
757 menu->attach_item(tmp);
758 tmp->set_visible(true);
760 tmp = new TextItem();
761 tmp->set_text("! - Random character");
762 min_coord.x = X_MARGIN + COLUMN_WIDTH;
763 min_coord.y = SPECIAL_KEYS_START_Y + 1;
764 max_coord.x = min_coord.x + tmp->get_text().size();
765 max_coord.y = min_coord.y + 1;
766 tmp->set_bounds(min_coord, max_coord);
767 tmp->set_fg_colour(BROWN);
768 tmp->add_hotkey('!');
769 tmp->set_id(M_RANDOM_CHAR);
770 tmp->set_highlight_colour(LIGHTGRAY);
771 tmp->set_description_text("Shuffles through random character combinations "
772 "until you accept one");
773 menu->attach_item(tmp);
774 tmp->set_visible(true);
776 // Adjust the end marker to align the - because Space text is longer by 4
777 tmp = new TextItem();
778 if (ng->job != JOB_UNKNOWN)
780 tmp->set_text("Space - Change background");
781 tmp->set_description_text("Lets you change your background choice");
783 else
785 tmp->set_text("Space - Pick background first");
786 tmp->set_description_text("Lets you pick your background first");
788 min_coord.x = X_MARGIN + COLUMN_WIDTH - 4;
789 min_coord.y = SPECIAL_KEYS_START_Y + 2;
790 max_coord.x = min_coord.x + tmp->get_text().size();
791 max_coord.y = min_coord.y + 1;
792 tmp->set_bounds(min_coord, max_coord);
793 tmp->set_fg_colour(BROWN);
794 tmp->add_hotkey(' ');
795 tmp->set_id(M_ABORT);
796 tmp->set_highlight_colour(LIGHTGRAY);
797 menu->attach_item(tmp);
798 tmp->set_visible(true);
800 if (_char_defined(defaults))
802 std::string tmp_string = "Tab - ";
803 tmp_string += _char_description(defaults).c_str();
804 // Adjust the end marker to aling the - because
805 // Tab text is longer by 2
806 tmp = new TextItem();
807 tmp->set_text(tmp_string);
808 min_coord.x = X_MARGIN + COLUMN_WIDTH - 2;
809 min_coord.y = SPECIAL_KEYS_START_Y + 3;
810 max_coord.x = min_coord.x + tmp->get_text().size();
811 max_coord.y = min_coord.y + 1;
812 tmp->set_bounds(min_coord, max_coord);
813 tmp->set_fg_colour(BROWN);
814 tmp->add_hotkey('\t');
815 tmp->set_id(M_DEFAULT_CHOICE);
816 tmp->set_highlight_colour(LIGHTGRAY);
817 tmp->set_description_text("Play a new game with your previous choice");
818 menu->attach_item(tmp);
819 tmp->set_visible(true);
823 // Prompt the player for a choice of species.
824 // ng should be const, but we need to reset it for _resolve_species_job
825 // to work correctly in view of fully random characters.
826 static void _prompt_species(newgame_def* ng, newgame_def* ng_choice,
827 const newgame_def& defaults)
829 PrecisionMenu menu;
830 menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT);
831 MenuFreeform* freeform = new MenuFreeform();
832 freeform->init(coord_def(0,0), coord_def(get_number_of_cols(),
833 get_number_of_lines()), "freeform");
834 menu.attach_object(freeform);
835 menu.set_active_object(freeform);
837 int keyn;
839 clrscr();
841 // TODO: attach these to the menu in a NoSelectTextItem
842 textcolor(BROWN);
843 cprintf("%s", _welcome(ng).c_str());
845 textcolor(YELLOW);
846 cprintf(" Please select your species.");
848 _construct_species_menu(ng, defaults, freeform);
849 MenuDescriptor* descriptor = new MenuDescriptor(&menu);
850 descriptor->init(coord_def(X_MARGIN, CHAR_DESC_START_Y),
851 coord_def(get_number_of_cols(), CHAR_DESC_START_Y + 2),
852 "descriptor");
853 menu.attach_object(descriptor);
855 BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu);
856 highlighter->init(coord_def(0,0), coord_def(0,0), "highlighter");
857 menu.attach_object(highlighter);
859 // Did we have a previous species?
860 if (menu.get_active_item() == NULL)
862 freeform->activate_first_item();
865 #ifdef USE_TILE
866 tiles.get_crt()->attach_menu(&menu);
867 #endif
869 freeform->set_visible(true);
870 descriptor->set_visible(true);
871 highlighter->set_visible(true);
873 textcolor(LIGHTGREY);
874 // Poll input until we have a conclusive escape or pick
875 while (true)
877 menu.draw_menu();
879 keyn = getch_ck();
881 // First process all the menu entries available
882 if (!menu.process_key(keyn))
884 // Process all the other keys that are not assigned to the menu
885 switch (keyn)
887 case 'X':
888 cprintf("\nGoodbye!");
889 end(0);
890 return;
891 CASE_ESCAPE
892 game_ended();
893 case CK_BKSP:
894 ng_choice->species = SP_UNKNOWN;
895 return;
896 default:
897 // if we get this far, we did not get a significant selection
898 // from the menu, nor did we get an escape character
899 // continue the while loop from the beginning and poll a new key
900 continue;
903 // We have had a significant input key event
904 // construct the return vector
905 std::vector<MenuItem*> selection = menu.get_selected_items();
906 if (selection.size() > 0)
908 // we have a selection!
909 // we only care about the first selection (there should be only one)
910 int selection_key = selection.at(0)->get_id();
912 bool viable = false;
913 switch (selection_key)
915 case M_VIABLE_CHAR:
916 viable = true;
917 // intentional fall-through
918 case M_RANDOM_CHAR:
919 _mark_fully_random(ng, ng_choice, viable);
920 return;
921 case M_DEFAULT_CHOICE:
922 if (_char_defined(defaults))
924 _set_default_choice(ng, ng_choice, defaults);
925 return;
927 else
929 // ignore Tab because we don't have previous start options
930 continue;
932 case M_ABORT:
933 ng->species = ng_choice->species = SP_UNKNOWN;
934 ng->job = ng_choice->job = JOB_UNKNOWN;
935 return;
936 case M_HELP:
937 // access to the help files
938 list_commands('1');
939 return _prompt_species(ng, ng_choice, defaults);
940 case M_APTITUDES:
941 list_commands('%', false, _highlight_pattern(ng));
942 return _prompt_species(ng, ng_choice, defaults);
943 case M_VIABLE:
944 ng_choice->species = SP_VIABLE;
945 return;
946 case M_RANDOM:
947 ng_choice->species = SP_RANDOM;
948 return;
949 default:
950 // we have a species selection
951 species_type species = static_cast<species_type> (selection_key);
952 if (ng->job == JOB_UNKNOWN
953 || job_allowed(species, ng->job) != CC_BANNED)
955 ng_choice->species = species;
956 return;
958 else
960 continue;
968 * Helper for _choose_job
969 * constructs the menu used and highlights the previous job if there is one
971 static void _construct_backgrounds_menu(const newgame_def* ng,
972 const newgame_def& defaults,
973 MenuFreeform* menu)
975 static const int ITEMS_IN_COLUMN = 10;
976 // Construct the menu, 3 columns
977 TextItem* tmp = NULL;
978 std::string text;
979 coord_def min_coord(0,0);
980 coord_def max_coord(0,0);
982 for (int i = 0; i < ng_num_jobs(); ++i)
984 const job_type job = get_job(i);
985 tmp = new TextItem();
986 text.clear();
988 if (ng->species == SP_UNKNOWN
989 || job_allowed(ng->species, job) == CC_UNRESTRICTED)
991 tmp->set_fg_colour(LIGHTGRAY);
992 tmp->set_highlight_colour(GREEN);
994 else
996 tmp->set_fg_colour(DARKGRAY);
997 tmp->set_highlight_colour(YELLOW);
999 if (ng->species != SP_UNKNOWN
1000 && job_allowed(ng->species, job) == CC_BANNED)
1002 text = " ";
1003 text += get_job_name(job);
1004 text += " N/A";
1005 tmp->set_fg_colour(DARKGRAY);
1006 tmp->set_highlight_colour(RED);
1008 else
1010 text = index_to_letter(i);
1011 text += " - ";
1012 text += get_job_name(job);
1014 // fill the text entry to end of column - 1
1015 text.append(COLUMN_WIDTH - text.size() - 1 , ' ');
1016 tmp->set_text(text);
1017 min_coord.x = X_MARGIN + (i / ITEMS_IN_COLUMN) * COLUMN_WIDTH;
1018 min_coord.y = 3 + i % ITEMS_IN_COLUMN;
1019 max_coord.x = min_coord.x + tmp->get_text().size();
1020 max_coord.y = min_coord.y + 1;
1021 tmp->set_bounds(min_coord, max_coord);
1023 tmp->add_hotkey(index_to_letter(i));
1024 tmp->set_id(job);
1025 tmp->set_description_text(getGameStartDescription(get_job_name(job)));
1027 menu->attach_item(tmp);
1028 tmp->set_visible(true);
1029 if (defaults.job == job)
1031 menu->set_active_item(tmp);
1035 // Add all the special button entries
1036 tmp = new TextItem();
1037 tmp->set_text("+ - Viable background");
1038 min_coord.x = X_MARGIN;
1039 min_coord.y = SPECIAL_KEYS_START_Y;
1040 max_coord.x = min_coord.x + tmp->get_text().size();
1041 max_coord.y = min_coord.y + 1;
1042 tmp->set_bounds(min_coord, max_coord);
1043 tmp->set_fg_colour(BROWN);
1044 tmp->add_hotkey('+');
1045 // If the player has species chosen, use VIABLE, otherwise use RANDOM
1046 if (ng->species != SP_UNKNOWN)
1048 tmp->set_id(M_VIABLE);
1050 else
1052 tmp->set_id(M_RANDOM);
1054 tmp->set_highlight_colour(LIGHTGRAY);
1055 tmp->set_description_text("Picks a random viable background based on your current species choice");
1056 menu->attach_item(tmp);
1057 tmp->set_visible(true);
1059 tmp = new TextItem();
1060 tmp->set_text("# - Viable character");
1061 min_coord.x = X_MARGIN;
1062 min_coord.y = SPECIAL_KEYS_START_Y + 1;
1063 max_coord.x = min_coord.x + tmp->get_text().size();
1064 max_coord.y = min_coord.y + 1;
1065 tmp->set_bounds(min_coord, max_coord);
1066 tmp->set_fg_colour(BROWN);
1067 tmp->add_hotkey('#');
1068 tmp->set_id(M_VIABLE_CHAR);
1069 tmp->set_highlight_colour(LIGHTGRAY);
1070 tmp->set_description_text("Shuffles through random viable character combinations "
1071 "until you accept one");
1072 menu->attach_item(tmp);
1073 tmp->set_visible(true);
1075 tmp = new TextItem();
1076 tmp->set_text("% - List aptitudes");
1077 min_coord.x = X_MARGIN;
1078 min_coord.y = SPECIAL_KEYS_START_Y + 2;
1079 max_coord.x = min_coord.x + tmp->get_text().size();
1080 max_coord.y = min_coord.y + 1;
1081 tmp->set_bounds(min_coord, max_coord);
1082 tmp->set_fg_colour(BROWN);
1083 tmp->add_hotkey('%');
1084 tmp->set_id(M_APTITUDES);
1085 tmp->set_highlight_colour(LIGHTGRAY);
1086 tmp->set_description_text("Lists the numerical skill train aptitudes for all races");
1087 menu->attach_item(tmp);
1088 tmp->set_visible(true);
1090 tmp = new TextItem();
1091 tmp->set_text("? - Help");
1092 min_coord.x = X_MARGIN;
1093 min_coord.y = SPECIAL_KEYS_START_Y + 3;
1094 max_coord.x = min_coord.x + tmp->get_text().size();
1095 max_coord.y = min_coord.y + 1;
1096 tmp->set_bounds(min_coord, max_coord);
1097 tmp->set_fg_colour(BROWN);
1098 tmp->add_hotkey('?');
1099 tmp->set_id(M_HELP);
1100 tmp->set_highlight_colour(LIGHTGRAY);
1101 tmp->set_description_text("Opens the help screen");
1102 menu->attach_item(tmp);
1103 tmp->set_visible(true);
1105 tmp = new TextItem();
1106 tmp->set_text("* - Random background");
1107 min_coord.x = X_MARGIN + COLUMN_WIDTH;
1108 min_coord.y = SPECIAL_KEYS_START_Y;
1109 max_coord.x = min_coord.x + tmp->get_text().size();
1110 max_coord.y = min_coord.y + 1;
1111 tmp->set_bounds(min_coord, max_coord);
1112 tmp->set_fg_colour(BROWN);
1113 tmp->add_hotkey('*');
1114 tmp->set_id(M_RANDOM);
1115 tmp->set_highlight_colour(LIGHTGRAY);
1116 tmp->set_description_text("Picks a random background");
1117 menu->attach_item(tmp);
1118 tmp->set_visible(true);
1120 tmp = new TextItem();
1121 tmp->set_text("! - Random character");
1122 min_coord.x = X_MARGIN + COLUMN_WIDTH;
1123 min_coord.y = SPECIAL_KEYS_START_Y + 1;
1124 max_coord.x = min_coord.x + tmp->get_text().size();
1125 max_coord.y = min_coord.y + 1;
1126 tmp->set_bounds(min_coord, max_coord);
1127 tmp->set_fg_colour(BROWN);
1128 tmp->add_hotkey('!');
1129 tmp->set_id(M_RANDOM_CHAR);
1130 tmp->set_highlight_colour(LIGHTGRAY);
1131 tmp->set_description_text("Shuffles through random character combinations "
1132 "until you accept one");
1133 menu->attach_item(tmp);
1134 tmp->set_visible(true);
1136 // Adjust the end marker to align the - because Space text is longer by 4
1137 tmp = new TextItem();
1138 if (ng->species != SP_UNKNOWN)
1140 tmp->set_text("Space - Change species");
1141 tmp->set_description_text("Lets you change your species choice");
1143 else
1145 tmp->set_text("Space - Pick species first");
1146 tmp->set_description_text("Lets you pick your species first");
1149 min_coord.x = X_MARGIN + COLUMN_WIDTH - 4;
1150 min_coord.y = SPECIAL_KEYS_START_Y + 2;
1151 max_coord.x = min_coord.x + tmp->get_text().size();
1152 max_coord.y = min_coord.y + 1;
1153 tmp->set_bounds(min_coord, max_coord);
1154 tmp->set_fg_colour(BROWN);
1155 tmp->add_hotkey(' ');
1156 tmp->set_id(M_ABORT);
1157 tmp->set_highlight_colour(LIGHTGRAY);
1158 menu->attach_item(tmp);
1159 tmp->set_visible(true);
1161 if (_char_defined(defaults))
1163 text.clear();
1164 text = "Tab - ";
1165 text += _char_description(defaults).c_str();
1166 // Adjust the end marker to aling the - because
1167 // Tab text is longer by 2
1168 tmp = new TextItem();
1169 tmp->set_text(text);
1170 min_coord.x = X_MARGIN + COLUMN_WIDTH - 2;
1171 min_coord.y = SPECIAL_KEYS_START_Y + 3;
1172 max_coord.x = min_coord.x + tmp->get_text().size();
1173 max_coord.y = min_coord.y + 1;
1174 tmp->set_bounds(min_coord, max_coord);
1175 tmp->set_fg_colour(BROWN);
1176 tmp->add_hotkey('\t');
1177 tmp->set_id(M_DEFAULT_CHOICE);
1178 tmp->set_highlight_colour(LIGHTGRAY);
1179 tmp->set_description_text("Play a new game with your previous choice");
1180 menu->attach_item(tmp);
1181 tmp->set_visible(true);
1186 * _prompt_job menu
1187 * Saves the choice to ng_choice, doesn't resolve random choices.
1189 * ng should be const, but we need to reset it for _resolve_species_job
1190 * to work correctly in view of fully random characters.
1192 static void _prompt_job(newgame_def* ng, newgame_def* ng_choice,
1193 const newgame_def& defaults)
1195 PrecisionMenu menu;
1196 menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT);
1197 MenuFreeform* freeform = new MenuFreeform();
1198 freeform->init(coord_def(0,0), coord_def(get_number_of_cols(),
1199 get_number_of_lines()), "freeform");
1200 menu.attach_object(freeform);
1201 menu.set_active_object(freeform);
1203 int keyn;
1205 clrscr();
1207 // TODO: attach these to the menu in a NoSelectTextItem
1208 textcolor(BROWN);
1209 cprintf("%s", _welcome(ng).c_str());
1211 textcolor(YELLOW);
1212 cprintf(" Please select your background.");
1214 _construct_backgrounds_menu(ng, defaults, freeform);
1215 MenuDescriptor* descriptor = new MenuDescriptor(&menu);
1216 descriptor->init(coord_def(X_MARGIN, CHAR_DESC_START_Y),
1217 coord_def(get_number_of_cols(), CHAR_DESC_START_Y + 2),
1218 "descriptor");
1219 menu.attach_object(descriptor);
1221 BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu);
1222 highlighter->init(coord_def(0,0), coord_def(0,0), "highlighter");
1223 menu.attach_object(highlighter);
1225 // Did we have a previous background?
1226 if (menu.get_active_item() == NULL)
1228 freeform->activate_first_item();
1231 #ifdef USE_TILE
1232 tiles.get_crt()->attach_menu(&menu);
1233 #endif
1235 freeform->set_visible(true);
1236 descriptor->set_visible(true);
1237 highlighter->set_visible(true);
1239 textcolor(LIGHTGREY);
1241 // Poll input until we have a conclusive escape or pick
1242 while (true)
1244 menu.draw_menu();
1246 keyn = getch_ck();
1248 // First process all the menu entries available
1249 if (!menu.process_key(keyn))
1251 // Process all the other keys that are not assigned to the menu
1252 switch (keyn)
1254 case 'X':
1255 cprintf("\nGoodbye!");
1256 end(0);
1257 return;
1258 CASE_ESCAPE
1259 game_ended();
1260 case CK_BKSP:
1261 ng_choice->job = JOB_UNKNOWN;
1262 return;
1263 default:
1264 // if we get this far, we did not get a significant selection
1265 // from the menu, nor did we get an escape character
1266 // continue the while loop from the beginning and poll a new key
1267 continue;
1270 // We have had a significant input key event
1271 // construct the return vector
1272 std::vector<MenuItem*> selection = menu.get_selected_items();
1273 if (selection.size() > 0)
1275 // we have a selection!
1276 // we only care about the first selection (there should be only one)
1277 int selection_key = selection.at(0)->get_id();
1279 bool viable = false;
1280 switch (selection_key)
1282 case M_VIABLE_CHAR:
1283 viable = true;
1284 // intentional fall-through
1285 case M_RANDOM_CHAR:
1286 _mark_fully_random(ng, ng_choice, viable);
1287 return;
1288 case M_DEFAULT_CHOICE:
1289 if (_char_defined(defaults))
1291 _set_default_choice(ng, ng_choice, defaults);
1292 return;
1294 else
1296 // ignore default because we don't have previous start options
1297 continue;
1299 case M_ABORT:
1300 ng->species = ng_choice->species = SP_UNKNOWN;
1301 ng->job = ng_choice->job = JOB_UNKNOWN;
1302 return;
1303 case M_HELP:
1304 // access to the help files
1305 list_commands('1');
1306 return _prompt_job(ng, ng_choice, defaults);
1307 case M_APTITUDES:
1308 list_commands('%', false, _highlight_pattern(ng));
1309 return _prompt_job(ng, ng_choice, defaults);
1310 case M_VIABLE:
1311 ng_choice->job = JOB_VIABLE;
1312 return;
1313 case M_RANDOM:
1314 ng_choice->job = JOB_RANDOM;
1315 return;
1316 default:
1317 // we have a job selection
1318 job_type job = static_cast<job_type> (selection_key);
1319 if (ng->species == SP_UNKNOWN
1320 || job_allowed(ng->species, job) != CC_BANNED)
1322 ng_choice->job = job;
1323 return;
1325 else
1327 continue;
1334 typedef std::pair<weapon_type, char_choice_restriction> weapon_choice;
1336 static weapon_type _fixup_weapon(weapon_type wp,
1337 const std::vector<weapon_choice>& weapons)
1339 if (wp == WPN_UNKNOWN || wp == WPN_RANDOM || wp == WPN_VIABLE)
1340 return (wp);
1341 for (unsigned int i = 0; i < weapons.size(); ++i)
1342 if (wp == weapons[i].first)
1343 return (wp);
1344 return (WPN_UNKNOWN);
1347 static void _construct_weapon_menu(const weapon_type& defweapon,
1348 const std::vector<weapon_choice>& weapons,
1349 MenuFreeform* menu)
1351 static const int ITEMS_START_Y = 5;
1352 TextItem* tmp = NULL;
1353 std::string text;
1354 coord_def min_coord(0,0);
1355 coord_def max_coord(0,0);
1357 for (unsigned int i = 0; i < weapons.size(); ++i)
1359 tmp = new TextItem();
1360 text.clear();
1362 if (weapons[i].second == CC_UNRESTRICTED)
1364 tmp->set_fg_colour(LIGHTGRAY);
1365 tmp->set_highlight_colour(GREEN);
1367 else
1369 tmp->set_fg_colour(DARKGRAY);
1370 tmp->set_highlight_colour(YELLOW);
1372 const char letter = 'a' + i;
1373 tmp->add_hotkey(letter);
1374 tmp->set_id(weapons[i].first);
1376 text += letter;
1377 text += " - ";
1378 text += weapons[i].first == WPN_UNARMED
1379 ? "claws" : weapon_base_name(weapons[i].first);
1380 // Fill to column width to give extra padding for the highlight
1381 text.append(COLUMN_WIDTH - text.size() - 1 , ' ');
1382 tmp->set_text(text);
1384 min_coord.x = X_MARGIN;
1385 min_coord.y = ITEMS_START_Y + i;
1386 max_coord.x = min_coord.x + text.size();
1387 max_coord.y = min_coord.y + 1;
1388 tmp->set_bounds(min_coord, max_coord);
1390 menu->attach_item(tmp);
1391 tmp->set_visible(true);
1392 // Is this item our default weapon?
1393 if (weapons[i].first == defweapon)
1395 menu->set_active_item(tmp);
1398 // Add all the special button entries
1399 tmp = new TextItem();
1400 tmp->set_text("+ - Viable random choice");
1401 min_coord.x = X_MARGIN;
1402 min_coord.y = SPECIAL_KEYS_START_Y;
1403 max_coord.x = min_coord.x + tmp->get_text().size();
1404 max_coord.y = min_coord.y + 1;
1405 tmp->set_bounds(min_coord, max_coord);
1406 tmp->set_fg_colour(BROWN);
1407 tmp->add_hotkey('+');
1408 tmp->set_id(M_VIABLE);
1409 tmp->set_highlight_colour(LIGHTGRAY);
1410 tmp->set_description_text("Picks a random viable weapon");
1411 menu->attach_item(tmp);
1412 tmp->set_visible(true);
1414 tmp = new TextItem();
1415 tmp->set_text("% - List aptitudes");
1416 min_coord.x = X_MARGIN;
1417 min_coord.y = SPECIAL_KEYS_START_Y + 1;
1418 max_coord.x = min_coord.x + tmp->get_text().size();
1419 max_coord.y = min_coord.y + 1;
1420 tmp->set_bounds(min_coord, max_coord);
1421 tmp->set_fg_colour(BROWN);
1422 tmp->add_hotkey('%');
1423 tmp->set_id(M_APTITUDES);
1424 tmp->set_highlight_colour(LIGHTGRAY);
1425 tmp->set_description_text("Lists the numerical skill train aptitudes for all races");
1426 menu->attach_item(tmp);
1427 tmp->set_visible(true);
1429 tmp = new TextItem();
1430 tmp->set_text("? - Help");
1431 min_coord.x = X_MARGIN;
1432 min_coord.y = SPECIAL_KEYS_START_Y + 2;
1433 max_coord.x = min_coord.x + tmp->get_text().size();
1434 max_coord.y = min_coord.y + 1;
1435 tmp->set_bounds(min_coord, max_coord);
1436 tmp->set_fg_colour(BROWN);
1437 tmp->add_hotkey('?');
1438 tmp->set_id(M_HELP);
1439 tmp->set_highlight_colour(LIGHTGRAY);
1440 tmp->set_description_text("Opens the help screen");
1441 menu->attach_item(tmp);
1442 tmp->set_visible(true);
1444 tmp = new TextItem();
1445 tmp->set_text("* - Random weapon");
1446 min_coord.x = X_MARGIN + COLUMN_WIDTH;
1447 min_coord.y = SPECIAL_KEYS_START_Y;
1448 max_coord.x = min_coord.x + tmp->get_text().size();
1449 max_coord.y = min_coord.y + 1;
1450 tmp->set_bounds(min_coord, max_coord);
1451 tmp->set_fg_colour(BROWN);
1452 tmp->add_hotkey('*');
1453 tmp->set_id(WPN_RANDOM);
1454 tmp->set_highlight_colour(LIGHTGRAY);
1455 tmp->set_description_text("Picks a random weapon");
1456 menu->attach_item(tmp);
1457 tmp->set_visible(true);
1459 // Adjust the end marker to align the - because Bksp text is longer by 3
1460 tmp = new TextItem();
1461 tmp->set_text("Bksp - Return to character menu");
1462 tmp->set_description_text("Lets you return back to Character choice menu");
1463 min_coord.x = X_MARGIN + COLUMN_WIDTH - 3;
1464 min_coord.y = SPECIAL_KEYS_START_Y + 1;
1465 max_coord.x = min_coord.x + tmp->get_text().size();
1466 max_coord.y = min_coord.y + 1;
1467 tmp->set_bounds(min_coord, max_coord);
1468 tmp->set_fg_colour(BROWN);
1469 tmp->add_hotkey(CK_BKSP);
1470 tmp->set_id(M_ABORT);
1471 tmp->set_highlight_colour(LIGHTGRAY);
1472 menu->attach_item(tmp);
1473 tmp->set_visible(true);
1475 if (defweapon != WPN_UNKNOWN)
1477 text.clear();
1478 text = "Tab - ";
1480 text += defweapon == WPN_RANDOM ? "Random" :
1481 defweapon == WPN_VIABLE ? "Viable" :
1482 defweapon == WPN_UNARMED ? "claws" :
1483 weapon_base_name(defweapon);
1485 // Adjust the end marker to aling the - because
1486 // Tab text is longer by 2
1487 tmp = new TextItem();
1488 tmp->set_text(text);
1489 min_coord.x = X_MARGIN + COLUMN_WIDTH - 2;
1490 min_coord.y = SPECIAL_KEYS_START_Y + 2;
1491 max_coord.x = min_coord.x + tmp->get_text().size();
1492 max_coord.y = min_coord.y + 1;
1493 tmp->set_bounds(min_coord, max_coord);
1494 tmp->set_fg_colour(BROWN);
1495 tmp->add_hotkey('\t');
1496 tmp->set_id(M_DEFAULT_CHOICE);
1497 tmp->set_highlight_colour(LIGHTGRAY);
1498 tmp->set_description_text("Select your old weapon");
1499 menu->attach_item(tmp);
1500 tmp->set_visible(true);
1505 * Returns false if user escapes
1507 static bool _prompt_weapon(const newgame_def* ng, newgame_def* ng_choice,
1508 const newgame_def& defaults,
1509 const std::vector<weapon_choice>& weapons)
1511 PrecisionMenu menu;
1512 menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT);
1513 MenuFreeform* freeform = new MenuFreeform();
1514 freeform->init(coord_def(1,1), coord_def(get_number_of_cols(),
1515 get_number_of_lines()), "freeform");
1516 menu.attach_object(freeform);
1517 menu.set_active_object(freeform);
1519 weapon_type defweapon = _fixup_weapon(defaults.weapon, weapons);
1521 _construct_weapon_menu(defweapon, weapons, freeform);
1523 BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu);
1524 highlighter->init(coord_def(0,0), coord_def(0,0), "highlighter");
1525 menu.attach_object(highlighter);
1527 // Did we have a previous weapon?
1528 if (menu.get_active_item() == NULL)
1530 freeform->activate_first_item();
1532 _print_character_info(ng); // calls clrscr() so needs to be before attach()
1534 #ifdef USE_TILE
1535 tiles.get_crt()->attach_menu(&menu);
1536 #endif
1538 freeform->set_visible(true);
1539 highlighter->set_visible(true);
1541 textcolor(CYAN);
1542 cprintf("\nYou have a choice of weapons: ");
1544 while (true)
1546 menu.draw_menu();
1548 int keyn = getch_ck();
1550 // First process menu entries
1551 if (!menu.process_key(keyn))
1553 // Process all the keys that are not attached to items
1554 switch (keyn)
1556 case 'X':
1557 cprintf("\nGoodbye!");
1558 end(0);
1559 break;
1560 case ' ':
1561 CASE_ESCAPE
1562 return false;
1563 default:
1564 // if we get this far, we did not get a significant selection
1565 // from the menu, nor did we get an escape character
1566 // continue the while loop from the beginning and poll a new key
1567 continue;
1570 // We have a significant key input!
1571 // Construct selection vector
1572 std::vector<MenuItem*> selection = menu.get_selected_items();
1573 // There should only be one selection, otherwise something broke
1574 if (selection.size() != 1)
1576 // poll a new key
1577 continue;
1580 // Get the stored id from the selection
1581 int selection_ID = selection.at(0)->get_id();
1582 switch (selection_ID)
1584 case M_ABORT:
1585 return false;
1586 case M_APTITUDES:
1587 list_commands('%', false, _highlight_pattern(ng));
1588 return _prompt_weapon(ng, ng_choice, defaults, weapons);
1589 case M_HELP:
1590 list_commands('?');
1591 return _prompt_weapon(ng, ng_choice, defaults, weapons);
1592 case M_DEFAULT_CHOICE:
1593 if (defweapon != WPN_UNKNOWN)
1595 ng_choice->weapon = defweapon;
1596 return true;
1598 // No default weapon defined.
1599 // This case should never happen in those cases but just in case
1600 continue;
1601 case M_VIABLE:
1602 ng_choice->weapon = WPN_VIABLE;
1603 return true;
1604 case M_RANDOM:
1605 ng_choice->weapon = WPN_RANDOM;
1606 return true;
1607 default:
1608 // We got an item selection
1609 ng_choice->weapon = static_cast<weapon_type> (selection_ID);
1610 return true;
1613 // This should never happen
1614 return false;
1617 static std::vector<weapon_choice> _get_weapons(const newgame_def* ng)
1619 std::vector<weapon_choice> weapons;
1621 weapon_type startwep[6] = { WPN_UNARMED, WPN_SHORT_SWORD, WPN_MACE,
1622 WPN_HAND_AXE, WPN_SPEAR, WPN_FALCHION };
1623 for (int i = 0; i < 6; ++i)
1625 weapon_choice wp;
1626 wp.first = startwep[i];
1628 switch (wp.first)
1630 case WPN_UNARMED:
1631 if (ng->job == JOB_GLADIATOR || !species_has_claws(ng->species))
1632 continue;
1633 break;
1634 case WPN_SPEAR:
1635 // Non-small gladiators and merfolk get tridents.
1636 if (ng->job == JOB_GLADIATOR
1637 && species_size(ng->species, PSIZE_BODY) >= SIZE_MEDIUM
1638 || ng->species == SP_MERFOLK)
1640 wp.first = WPN_TRIDENT;
1642 break;
1643 case WPN_MACE:
1644 if (ng->species == SP_OGRE)
1645 wp.first = WPN_ANKUS;
1646 break;
1647 default:
1648 break;
1650 wp.second = weapon_restriction(wp.first, *ng);
1651 if (wp.second != CC_BANNED)
1652 weapons.push_back(wp);
1654 return weapons;
1657 static void _resolve_weapon(newgame_def* ng, newgame_def* ng_choice,
1658 const std::vector<weapon_choice>& weapons)
1660 switch (ng_choice->weapon)
1662 case WPN_UNKNOWN:
1663 ng->weapon = WPN_UNKNOWN;
1664 return;
1666 case WPN_VIABLE:
1668 int good_choices = 0;
1669 for (unsigned int i = 0; i < weapons.size(); i++)
1671 if (weapons[i].second == CC_UNRESTRICTED
1672 && one_chance_in(++good_choices))
1674 ng->weapon = weapons[i].first;
1677 if (good_choices)
1678 return;
1680 // intentional fall-through
1681 case WPN_RANDOM:
1682 ng->weapon = weapons[random2(weapons.size())].first;
1683 return;
1685 default:
1686 // Check this is a legal choice, in case it came
1687 // through command line options.
1688 ng->weapon = _fixup_weapon(ng_choice->weapon, weapons);
1689 if (ng->weapon == WPN_UNKNOWN)
1691 // Either an invalid combination was passed in through options,
1692 // or we messed up.
1693 end(1, false,
1694 "Incompatible weapon specified in options file.");
1696 return;
1700 // Returns false if aborted, else an actual weapon choice
1701 // is written to ng->weapon for the jobs that call
1702 // _update_weapon() later.
1703 static bool _choose_weapon(newgame_def* ng, newgame_def* ng_choice,
1704 const newgame_def& defaults)
1706 // No weapon use at all. The actual item will be removed later.
1707 if (ng->species == SP_CAT)
1708 return (true);
1710 switch (ng->job)
1712 case JOB_FIGHTER:
1713 case JOB_GLADIATOR:
1714 case JOB_CHAOS_KNIGHT:
1715 case JOB_CRUSADER:
1716 case JOB_REAVER:
1717 case JOB_WARPER:
1718 break;
1719 default:
1720 return (true);
1723 std::vector<weapon_choice> weapons = _get_weapons(ng);
1725 ASSERT(!weapons.empty());
1726 if (weapons.size() == 1)
1728 ng->weapon = ng_choice->weapon = weapons[0].first;
1729 return (true);
1732 if (ng_choice->weapon == WPN_UNKNOWN)
1733 if (!_prompt_weapon(ng, ng_choice, defaults, weapons))
1734 return (false);
1736 _resolve_weapon(ng, ng_choice, weapons);
1737 return (true);
1740 static startup_book_type _fixup_book(startup_book_type book, int numbooks)
1742 if (book == SBT_RANDOM)
1743 return (SBT_RANDOM);
1744 else if (book == SBT_VIABLE)
1745 return (SBT_VIABLE);
1746 else if (book >= 0 && book < numbooks)
1747 return (book);
1748 else
1749 return (SBT_NONE);
1752 static std::string _startup_book_name(startup_book_type book)
1754 switch (book)
1756 case SBT_FIRE:
1757 return "Fire";
1758 case SBT_COLD:
1759 return "Cold";
1760 case SBT_SUMM:
1761 return "Summoning";
1762 case SBT_RANDOM:
1763 return "Random";
1764 case SBT_VIABLE:
1765 return "Viable";
1766 default:
1767 return "Buggy";
1771 static void _construct_book_menu(const newgame_def& ng,
1772 const book_type& firstbook,
1773 const startup_book_type& defbook,
1774 int numbooks,
1775 MenuFreeform* menu)
1777 static const int ITEMS_START_Y = 5;
1778 TextItem* tmp = NULL;
1779 std::string text;
1780 coord_def min_coord(0,0);
1781 coord_def max_coord(0,0);
1783 item_def book;
1784 book.base_type = OBJ_BOOKS;
1785 book.quantity = 1;
1786 book.plus = 0;
1787 book.plus2 = 0;
1788 book.special = 1;
1790 for (int i = 0; i < numbooks; ++i)
1792 tmp = new TextItem();
1793 text.clear();
1795 book.sub_type = firstbook + i;
1796 startup_book_type sb = static_cast<startup_book_type>(i);
1797 if (book_restriction(sb, ng) == CC_UNRESTRICTED)
1799 tmp->set_fg_colour(LIGHTGREY);
1800 tmp->set_highlight_colour(GREEN);
1802 else
1804 tmp->set_fg_colour(DARKGREY);
1805 tmp->set_highlight_colour(YELLOW);
1807 const char letter = 'a' + i;
1808 text += letter;
1809 text += " - ";
1810 text += book.name(DESC_PLAIN, false, true);
1812 //book name is longer than COLUMN_WIDTH
1813 //text.append(COLUMN_WIDTH - text.size() - 1 , ' ');
1814 tmp->set_text(text);
1816 tmp->add_hotkey(letter);
1817 tmp->set_id(sb);
1819 min_coord.x = X_MARGIN;
1820 min_coord.y = ITEMS_START_Y + i;
1821 max_coord.x = min_coord.x + text.size();
1822 max_coord.y = min_coord.y + 1;
1823 tmp->set_bounds(min_coord, max_coord);
1825 menu->attach_item(tmp);
1826 tmp->set_visible(true);
1827 // Is this item our default book?
1828 if (sb == defbook)
1830 menu->set_active_item(tmp);
1835 // Add all the special button entries
1836 tmp = new TextItem();
1837 tmp->set_text("+ - Viable random choice");
1838 min_coord.x = X_MARGIN;
1839 min_coord.y = SPECIAL_KEYS_START_Y;
1840 max_coord.x = min_coord.x + tmp->get_text().size();
1841 max_coord.y = min_coord.y + 1;
1842 tmp->set_bounds(min_coord, max_coord);
1843 tmp->set_fg_colour(BROWN);
1844 tmp->add_hotkey('+');
1845 tmp->set_id(M_VIABLE);
1846 tmp->set_highlight_colour(LIGHTGRAY);
1847 tmp->set_description_text("Picks a random viable book");
1848 menu->attach_item(tmp);
1849 tmp->set_visible(true);
1851 tmp = new TextItem();
1852 tmp->set_text("% - List aptitudes");
1853 min_coord.x = X_MARGIN;
1854 min_coord.y = SPECIAL_KEYS_START_Y + 1;
1855 max_coord.x = min_coord.x + tmp->get_text().size();
1856 max_coord.y = min_coord.y + 1;
1857 tmp->set_bounds(min_coord, max_coord);
1858 tmp->set_fg_colour(BROWN);
1859 tmp->add_hotkey('%');
1860 tmp->set_id(M_APTITUDES);
1861 tmp->set_highlight_colour(LIGHTGRAY);
1862 tmp->set_description_text("Lists the numerical skill train aptitudes for all races");
1863 menu->attach_item(tmp);
1864 tmp->set_visible(true);
1866 tmp = new TextItem();
1867 tmp->set_text("? - Help");
1868 min_coord.x = X_MARGIN;
1869 min_coord.y = SPECIAL_KEYS_START_Y + 2;
1870 max_coord.x = min_coord.x + tmp->get_text().size();
1871 max_coord.y = min_coord.y + 1;
1872 tmp->set_bounds(min_coord, max_coord);
1873 tmp->set_fg_colour(BROWN);
1874 tmp->add_hotkey('?');
1875 tmp->set_id(M_HELP);
1876 tmp->set_highlight_colour(LIGHTGRAY);
1877 tmp->set_description_text("Opens the help screen");
1878 menu->attach_item(tmp);
1879 tmp->set_visible(true);
1881 tmp = new TextItem();
1882 tmp->set_text("* - Random book");
1883 min_coord.x = X_MARGIN + COLUMN_WIDTH;
1884 min_coord.y = SPECIAL_KEYS_START_Y;
1885 max_coord.x = min_coord.x + tmp->get_text().size();
1886 max_coord.y = min_coord.y + 1;
1887 tmp->set_bounds(min_coord, max_coord);
1888 tmp->set_fg_colour(BROWN);
1889 tmp->add_hotkey('*');
1890 tmp->set_id(M_RANDOM);
1891 tmp->set_highlight_colour(LIGHTGRAY);
1892 tmp->set_description_text("Picks a random book");
1893 menu->attach_item(tmp);
1894 tmp->set_visible(true);
1896 // Adjust the end marker to align the - because Bksp text is longer by 3
1897 tmp = new TextItem();
1898 tmp->set_text("Bksp - Return to character menu");
1899 tmp->set_description_text("Lets you return back to Character choice menu");
1900 min_coord.x = X_MARGIN + COLUMN_WIDTH - 3;
1901 min_coord.y = SPECIAL_KEYS_START_Y + 1;
1902 max_coord.x = min_coord.x + tmp->get_text().size();
1903 max_coord.y = min_coord.y + 1;
1904 tmp->set_bounds(min_coord, max_coord);
1905 tmp->set_fg_colour(BROWN);
1906 tmp->add_hotkey(CK_BKSP);
1907 tmp->set_id(M_ABORT);
1908 tmp->set_highlight_colour(LIGHTGRAY);
1909 menu->attach_item(tmp);
1910 tmp->set_visible(true);
1912 // Only add tab entry if we have a previous book choice
1913 if (defbook != SBT_NONE)
1915 tmp = new TextItem();
1916 text.clear();
1917 text = "Tab - ";
1919 text += _startup_book_name(defbook);
1921 // Adjust the end marker to aling the - because
1922 // Tab text is longer by 2
1923 tmp = new TextItem();
1924 tmp->set_text(text);
1925 min_coord.x = X_MARGIN + COLUMN_WIDTH - 2;
1926 min_coord.y = SPECIAL_KEYS_START_Y + 2;
1927 max_coord.x = min_coord.x + tmp->get_text().size();
1928 max_coord.y = min_coord.y + 1;
1929 tmp->set_bounds(min_coord, max_coord);
1930 tmp->set_fg_colour(BROWN);
1931 tmp->add_hotkey('\t');
1932 tmp->set_id(M_DEFAULT_CHOICE);
1933 tmp->set_highlight_colour(LIGHTGRAY);
1934 tmp->set_description_text("Select your previous book choice");
1935 menu->attach_item(tmp);
1936 tmp->set_visible(true);
1940 // Returns false if the user aborted.
1941 static bool _prompt_book(const newgame_def* ng, newgame_def* ng_choice,
1942 const newgame_def& defaults,
1943 book_type firstbook, int numbooks)
1945 PrecisionMenu menu;
1946 menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT);
1947 MenuFreeform* freeform = new MenuFreeform();
1948 freeform->init(coord_def(1,1), coord_def(get_number_of_cols(),
1949 get_number_of_lines()), "freeform");
1950 menu.attach_object(freeform);
1951 menu.set_active_object(freeform);
1953 startup_book_type defbook = _fixup_book(defaults.book, numbooks);
1954 _construct_book_menu(*ng, firstbook, defbook, numbooks, freeform);
1956 BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu);
1957 highlighter->init(coord_def(0,0), coord_def(0,0), "highlighter");
1958 menu.attach_object(highlighter);
1960 // Did we have a previous book?
1961 if (menu.get_active_item() == NULL)
1963 freeform->activate_first_item();
1965 _print_character_info(ng); // calls clrscr() so needs to be before attach()
1967 #ifdef USE_TILE
1968 tiles.get_crt()->attach_menu(&menu);
1969 #endif
1971 freeform->set_visible(true);
1972 highlighter->set_visible(true);
1974 textcolor(CYAN);
1975 cprintf("\nYou have a choice of books:");
1977 while (true)
1979 menu.draw_menu();
1981 int keyn = getch_ck();
1983 // First process menu entries
1984 if (!menu.process_key(keyn))
1986 // Process all the keys that are not attached to items
1987 switch (keyn)
1989 case 'X':
1990 cprintf("\nGoodbye!");
1991 end(0);
1992 break;
1993 case ' ':
1994 CASE_ESCAPE
1995 return false;
1996 default:
1997 // if we get this far, we did not get a significant selection
1998 // from the menu, nor did we get an escape character
1999 // continue the while loop from the beginning and poll a new key
2000 continue;
2003 // We have a significant key input!
2004 // Construct selection vector
2005 std::vector<MenuItem*> selection = menu.get_selected_items();
2006 // There should only be one selection, otherwise something broke
2007 if (selection.size() != 1)
2009 // poll a new key
2010 continue;
2013 // Get the stored id from the selection
2014 int selection_ID = selection.at(0)->get_id();
2015 switch (selection_ID)
2017 case M_ABORT:
2018 return false;
2019 case M_APTITUDES:
2020 list_commands('%', false, _highlight_pattern(ng));
2021 return _prompt_book(ng, ng_choice, defaults, firstbook, numbooks);
2022 case M_HELP:
2023 list_commands('?');
2024 return _prompt_book(ng, ng_choice, defaults, firstbook, numbooks);
2025 case M_DEFAULT_CHOICE:
2026 if (defbook != SBT_NONE)
2028 ng_choice->book = defbook;
2029 return true;
2031 // This case should not happen if defbook == SBT_NONE
2032 continue;
2033 case M_VIABLE:
2034 ng_choice->book = SBT_VIABLE;
2035 return true;
2036 case M_RANDOM:
2037 ng_choice->book = SBT_RANDOM;
2038 return true;
2039 default:
2040 // We got an item selection
2041 ng_choice->book = static_cast<startup_book_type> (selection_ID);
2042 return true;
2045 return false;
2048 static void _resolve_book(newgame_def* ng, const newgame_def* ng_choice,
2049 int numbooks)
2051 switch (ng_choice->book)
2053 case SBT_NONE:
2054 ng->book = SBT_NONE;
2055 return;
2057 case SBT_VIABLE:
2059 int good_choices = 0;
2060 for (int i = 0; i < numbooks; i++)
2062 startup_book_type sb = static_cast<startup_book_type>(i);
2063 if (book_restriction(sb, *ng) == CC_UNRESTRICTED
2064 && one_chance_in(++good_choices))
2066 ng->book = sb;
2069 if (good_choices)
2070 return;
2072 // intentional fall-through
2073 case SBT_RANDOM:
2074 ng->book = static_cast<startup_book_type>(random2(numbooks));
2075 return;
2077 default:
2078 if (ng_choice->book >= 0 && ng_choice->book < numbooks)
2079 ng->book = ng_choice->book;
2080 else
2082 // Either an invalid combination was passed in through options,
2083 // or we messed up.
2084 end(1, false,
2085 "Incompatible book specified in options file.");
2087 return;
2091 static bool _choose_book(newgame_def* ng, newgame_def* ng_choice,
2092 const newgame_def& defaults,
2093 book_type firstbook, int numbooks)
2095 if (ng_choice->book == SBT_NONE
2096 && !_prompt_book(ng, ng_choice, defaults, firstbook, numbooks))
2098 return (false);
2100 _resolve_book(ng, ng_choice, numbooks);
2101 return (true);
2104 static bool _choose_book(newgame_def* ng, newgame_def* ng_choice,
2105 const newgame_def& defaults)
2107 switch (ng->job)
2109 case JOB_REAVER:
2110 case JOB_CONJURER:
2111 return (_choose_book(ng, ng_choice, defaults, BOOK_CONJURATIONS_I, 2));
2112 case JOB_WIZARD:
2113 return (_choose_book(ng, ng_choice, defaults, BOOK_MINOR_MAGIC_I, 3));
2114 default:
2115 return (true);
2119 // Covers both chaos knight and priest choices.
2120 static std::string _god_text(god_type god)
2122 switch (god)
2124 case GOD_ZIN:
2125 return "Zin (for traditional priests)";
2126 case GOD_YREDELEMNUL:
2127 return "Yredelemnul (for priests of death)";
2128 case GOD_BEOGH:
2129 return "Beogh (for priests of Orcs)";
2130 case GOD_XOM:
2131 return "Xom of Chaos";
2132 case GOD_MAKHLEB:
2133 return "Makhleb the Destroyer";
2134 case GOD_LUGONU:
2135 return "Lugonu the Unformed";
2136 default:
2137 die("invalid priestly god: %d", god);
2141 typedef std::pair<god_type, char_choice_restriction> god_choice;
2143 static god_type _fixup_god(god_type god, const std::vector<god_choice>& gods)
2145 if (god == GOD_NO_GOD || god == GOD_RANDOM || god == GOD_VIABLE)
2146 return (god);
2147 for (unsigned int i = 0; i < gods.size(); ++i)
2148 if (god == gods[i].first)
2149 return (god);
2150 return (GOD_NO_GOD);
2153 static void _construct_god_menu(const god_type& defgod,
2154 const std::vector<god_choice>& gods,
2155 MenuFreeform* menu)
2157 static const int ITEMS_START_Y = 5;
2158 TextItem* tmp = NULL;
2159 std::string text;
2160 coord_def min_coord(0,0);
2161 coord_def max_coord(0,0);
2163 for (unsigned int i = 0; i < gods.size(); ++i)
2165 tmp = new TextItem();
2166 text.clear();
2168 if (gods[i].second == CC_UNRESTRICTED)
2170 tmp->set_fg_colour(LIGHTGREY);
2171 tmp->set_highlight_colour(GREEN);
2173 else
2175 tmp->set_fg_colour(DARKGREY);
2176 tmp->set_highlight_colour(YELLOW);
2179 const char letter = 'a' + i;
2180 text += letter;
2181 text += " - ";
2182 text += _god_text(gods[i].first);
2183 //god text is longer than COLUMN_WIDTH
2184 //text.append(COLUMN_WIDTH - text.size() - 1 , ' ');
2185 tmp->set_text(text);
2187 tmp->add_hotkey(letter);
2188 tmp->set_id(gods[i].first);
2190 min_coord.x = X_MARGIN;
2191 min_coord.y = ITEMS_START_Y + i;
2192 max_coord.x = min_coord.x + text.size();
2193 max_coord.y = min_coord.y + 1;
2194 tmp->set_bounds(min_coord, max_coord);
2196 menu->attach_item(tmp);
2197 tmp->set_visible(true);
2198 // Is this item our default god?
2199 if (gods[i].first == defgod)
2201 menu->set_active_item(tmp);
2205 // Add all the special button entries
2206 tmp = new TextItem();
2207 tmp->set_text("+ - Viable random choice");
2208 min_coord.x = X_MARGIN;
2209 min_coord.y = SPECIAL_KEYS_START_Y;
2210 max_coord.x = min_coord.x + tmp->get_text().size();
2211 max_coord.y = min_coord.y + 1;
2212 tmp->set_bounds(min_coord, max_coord);
2213 tmp->set_fg_colour(BROWN);
2214 tmp->add_hotkey('+');
2215 tmp->set_id(M_VIABLE);
2216 tmp->set_highlight_colour(LIGHTGRAY);
2217 tmp->set_description_text("Picks a random viable god");
2218 menu->attach_item(tmp);
2219 tmp->set_visible(true);
2221 tmp = new TextItem();
2222 tmp->set_text("% - List aptitudes");
2223 min_coord.x = X_MARGIN;
2224 min_coord.y = SPECIAL_KEYS_START_Y + 1;
2225 max_coord.x = min_coord.x + tmp->get_text().size();
2226 max_coord.y = min_coord.y + 1;
2227 tmp->set_bounds(min_coord, max_coord);
2228 tmp->set_fg_colour(BROWN);
2229 tmp->add_hotkey('%');
2230 tmp->set_id(M_APTITUDES);
2231 tmp->set_highlight_colour(LIGHTGRAY);
2232 tmp->set_description_text("Lists the numerical skill train aptitudes for all races");
2233 menu->attach_item(tmp);
2234 tmp->set_visible(true);
2236 tmp = new TextItem();
2237 tmp->set_text("? - Help");
2238 min_coord.x = X_MARGIN;
2239 min_coord.y = SPECIAL_KEYS_START_Y + 2;
2240 max_coord.x = min_coord.x + tmp->get_text().size();
2241 max_coord.y = min_coord.y + 1;
2242 tmp->set_bounds(min_coord, max_coord);
2243 tmp->set_fg_colour(BROWN);
2244 tmp->add_hotkey('?');
2245 tmp->set_id(M_HELP);
2246 tmp->set_highlight_colour(LIGHTGRAY);
2247 tmp->set_description_text("Opens the help screen");
2248 menu->attach_item(tmp);
2249 tmp->set_visible(true);
2251 tmp = new TextItem();
2252 tmp->set_text("* - Random god");
2253 min_coord.x = X_MARGIN + COLUMN_WIDTH;
2254 min_coord.y = SPECIAL_KEYS_START_Y;
2255 max_coord.x = min_coord.x + tmp->get_text().size();
2256 max_coord.y = min_coord.y + 1;
2257 tmp->set_bounds(min_coord, max_coord);
2258 tmp->set_fg_colour(BROWN);
2259 tmp->add_hotkey('*');
2260 tmp->set_id(M_RANDOM);
2261 tmp->set_highlight_colour(LIGHTGRAY);
2262 tmp->set_description_text("Picks a random god");
2263 menu->attach_item(tmp);
2264 tmp->set_visible(true);
2266 // Adjust the end marker to align the - because Bksp text is longer by 3
2267 tmp = new TextItem();
2268 tmp->set_text("Bksp - Return to character menu");
2269 tmp->set_description_text("Lets you return back to Character choice menu");
2270 min_coord.x = X_MARGIN + COLUMN_WIDTH - 3;
2271 min_coord.y = SPECIAL_KEYS_START_Y + 1;
2272 max_coord.x = min_coord.x + tmp->get_text().size();
2273 max_coord.y = min_coord.y + 1;
2274 tmp->set_bounds(min_coord, max_coord);
2275 tmp->set_fg_colour(BROWN);
2276 tmp->add_hotkey(CK_BKSP);
2277 tmp->set_id(M_ABORT);
2278 tmp->set_highlight_colour(LIGHTGRAY);
2279 menu->attach_item(tmp);
2280 tmp->set_visible(true);
2282 // Only add tab entry if we have a previous god choice
2283 if (defgod != GOD_NO_GOD)
2285 tmp = new TextItem();
2286 text.clear();
2287 text = "Tab - ";
2289 text += defgod == GOD_RANDOM ? "Random" :
2290 defgod == GOD_VIABLE ? "Viable" :
2291 god_name(defgod);
2293 // Adjust the end marker to align the - because
2294 // Tab text is longer by 2
2295 tmp = new TextItem();
2296 tmp->set_text(text);
2297 min_coord.x = X_MARGIN + COLUMN_WIDTH - 2;
2298 min_coord.y = SPECIAL_KEYS_START_Y + 2;
2299 max_coord.x = min_coord.x + tmp->get_text().size();
2300 max_coord.y = min_coord.y + 1;
2301 tmp->set_bounds(min_coord, max_coord);
2302 tmp->set_fg_colour(BROWN);
2303 tmp->add_hotkey('\t');
2304 tmp->set_id(M_DEFAULT_CHOICE);
2305 tmp->set_highlight_colour(LIGHTGRAY);
2306 tmp->set_description_text("Select your previous god choice");
2307 menu->attach_item(tmp);
2308 tmp->set_visible(true);
2312 static bool _prompt_god(const newgame_def* ng, newgame_def* ng_choice,
2313 const newgame_def& defaults,
2314 const std::vector<god_choice>& gods)
2316 PrecisionMenu menu;
2317 menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT);
2318 MenuFreeform* freeform = new MenuFreeform();
2319 freeform->init(coord_def(1,1), coord_def(get_number_of_cols(),
2320 get_number_of_lines()), "freeform");
2321 menu.attach_object(freeform);
2322 menu.set_active_object(freeform);
2324 const god_type defgod = _fixup_god(defaults.religion, gods);
2325 _construct_god_menu(defgod, gods, freeform);
2327 BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu);
2328 highlighter->init(coord_def(0,0), coord_def(0,0), "highlighter");
2329 menu.attach_object(highlighter);
2331 // Did we have a previous god?
2332 if (menu.get_active_item() == NULL)
2334 freeform->activate_first_item();
2336 _print_character_info(ng); // calls clrscr() so needs to be before attach()
2338 #ifdef USE_TILE
2339 tiles.get_crt()->attach_menu(&menu);
2340 #endif
2342 freeform->set_visible(true);
2343 highlighter->set_visible(true);
2345 textcolor(CYAN);
2346 cprintf("\nWhich god do you wish to serve?");
2348 while (true)
2350 menu.draw_menu();
2352 int keyn = getch_ck();
2354 // First process menu entries
2355 if (!menu.process_key(keyn))
2357 // Process all the keys that are not attached to items
2358 switch (keyn)
2360 case 'X':
2361 cprintf("\nGoodbye!");
2362 end(0);
2363 break;
2364 case ' ':
2365 CASE_ESCAPE
2366 return false;
2367 default:
2368 // if we get this far, we did not get a significant selection
2369 // from the menu, nor did we get an escape character
2370 // continue the while loop from the beginning and poll a new key
2371 continue;
2374 // We have a significant key input!
2375 // Construct selection vector
2376 std::vector<MenuItem*> selection = menu.get_selected_items();
2377 // There should only be one selection, otherwise something broke
2378 if (selection.size() != 1)
2380 // poll a new key
2381 continue;
2384 // Get the stored id from the selection
2385 int selection_ID = selection.at(0)->get_id();
2386 switch (selection_ID)
2388 case M_ABORT:
2389 return false;
2390 case M_APTITUDES:
2391 list_commands('%', false, _highlight_pattern(ng));
2392 return _prompt_god(ng, ng_choice, defaults, gods);
2393 case M_HELP:
2394 list_commands('?');
2395 return _prompt_god(ng, ng_choice, defaults, gods);
2396 case M_DEFAULT_CHOICE:
2397 if (defgod != GOD_NO_GOD)
2399 ng_choice->religion = defgod;
2400 return true;
2402 // This case should not happen when defgod == god_no_god
2403 continue;
2404 case M_VIABLE:
2405 ng_choice->religion = GOD_VIABLE;
2406 return true;
2407 case M_RANDOM:
2408 ng_choice->religion = GOD_RANDOM;
2409 return true;
2410 default:
2411 // We got an item selection
2412 ng_choice->religion = static_cast<god_type> (selection_ID);
2413 return true;
2416 return false;
2419 static void _resolve_god(newgame_def* ng, const newgame_def* ng_choice,
2420 const std::vector<god_choice>& gods)
2422 switch (ng_choice->religion)
2424 case GOD_NO_GOD:
2425 ng->religion = GOD_NO_GOD;
2426 return;
2428 case GOD_VIABLE:
2430 int good_choices = 0;
2431 for (unsigned int i = 0; i < gods.size(); i++)
2433 if (gods[i].second == CC_UNRESTRICTED
2434 && one_chance_in(++good_choices))
2436 ng->religion = gods[i].first;
2439 if (good_choices)
2440 return;
2442 // intentional fall-through
2443 case GOD_RANDOM:
2444 ng->religion = gods[random2(gods.size())].first;
2445 return;
2447 default:
2448 // Check this is a legal choice, in case it came
2449 // through command line options.
2450 ng->religion = _fixup_god(ng_choice->religion, gods);
2451 if (ng->religion == GOD_NO_GOD)
2453 // Either an invalid combination was passed in through options,
2454 // or we messed up.
2455 end(1, false,
2456 "Incompatible god specified in options file.");
2458 return;
2462 static bool _choose_god(newgame_def* ng, newgame_def* ng_choice,
2463 const newgame_def& defaults)
2465 if (ng->job != JOB_PRIEST && ng->job != JOB_CHAOS_KNIGHT)
2466 return (true);
2468 std::vector<god_choice> gods;
2469 for (unsigned int i = 0; i < NUM_GODS; ++i)
2471 god_choice god;
2472 god.first = static_cast<god_type>(i);
2473 god.second = religion_restriction(god.first, *ng);
2474 if (god.second != CC_BANNED)
2475 gods.push_back(god);
2478 ASSERT(!gods.empty());
2479 if (gods.size() == 1)
2481 ng->religion = ng_choice->religion = gods[0].first;
2482 return (true);
2485 // XXX: assumes we can never choose between a god and no god.
2486 if (ng_choice->religion == GOD_NO_GOD)
2487 if (!_prompt_god(ng, ng_choice, defaults, gods))
2488 return (false);
2490 _resolve_god(ng, ng_choice, gods);
2491 return (true);
2494 int start_to_wand(int wandtype, bool& is_rod)
2496 is_rod = false;
2498 switch (wandtype)
2500 case SWT_ENSLAVEMENT:
2501 return (WAND_ENSLAVEMENT);
2503 case SWT_CONFUSION:
2504 return (WAND_CONFUSION);
2506 case SWT_MAGIC_DARTS:
2507 return (WAND_MAGIC_DARTS);
2509 case SWT_FROST:
2510 return (WAND_FROST);
2512 case SWT_FLAME:
2513 return (WAND_FLAME);
2515 case SWT_STRIKING:
2516 is_rod = true;
2517 return (STAFF_STRIKING);
2519 default:
2520 return (-1);
2524 static void _construct_wand_menu(const startup_wand_type& defwand,
2525 MenuFreeform* menu)
2527 static const int ITEMS_START_Y = 5;
2528 TextItem* tmp = NULL;
2529 std::string text;
2530 coord_def min_coord(0,0);
2531 coord_def max_coord(0,0);
2534 for (int i = 0; i < NUM_STARTUP_WANDS; i++)
2536 tmp = new TextItem();
2537 text.clear();
2538 startup_wand_type sw = static_cast<startup_wand_type>(i);
2540 tmp->set_fg_colour(LIGHTGREY);
2541 tmp->set_highlight_colour(GREEN);
2543 const char letter = 'a' + i;
2544 text += letter;
2545 text += " - ";
2547 if (sw == SWT_STRIKING)
2549 item_def rod;
2550 make_rod(rod, STAFF_STRIKING, 8);
2551 text += rod.name(DESC_QUALNAME, false, true);
2553 else
2555 bool dummy;
2556 wand_type w = static_cast<wand_type>(start_to_wand(sw, dummy));
2557 text += wand_type_name(w);
2559 // Add padding
2560 text.append(COLUMN_WIDTH - text.size() - 1 , ' ');
2561 tmp->set_text(text);
2563 tmp->add_hotkey(letter);
2564 tmp->set_id(sw);
2566 min_coord.x = X_MARGIN;
2567 min_coord.y = ITEMS_START_Y + i;
2568 max_coord.x = min_coord.x + text.size();
2569 max_coord.y = min_coord.y + 1;
2570 tmp->set_bounds(min_coord, max_coord);
2572 menu->attach_item(tmp);
2573 tmp->set_visible(true);
2574 // Is this item our default god?
2575 if (sw == defwand)
2577 menu->set_active_item(tmp);
2581 // Add all the special button entries
2582 // Wands do not have unviable choices
2583 //tmp = new TextItem();
2584 //tmp->set_text("+ - Viable random choice");
2585 //min_coord.x = X_MARGIN;
2586 //min_coord.y = SPECIAL_KEYS_START_Y;
2587 //max_coord.x = min_coord.x + tmp->get_text().size();
2588 //max_coord.y = min_coord.y + 1;
2589 //tmp->set_bounds(min_coord, max_coord);
2590 //tmp->set_fg_colour(BROWN);
2591 //tmp->add_hotkey('+');
2592 //tmp->set_id(SBT_VIABLE);
2593 //tmp->set_highlight_colour(LIGHTGRAY);
2594 //tmp->set_description_text("Picks a random viable god");
2595 //menu->attach_item(tmp);
2596 //tmp->set_visible(true);
2598 tmp = new TextItem();
2599 tmp->set_text("% - List aptitudes");
2600 min_coord.x = X_MARGIN;
2601 min_coord.y = SPECIAL_KEYS_START_Y + 1;
2602 max_coord.x = min_coord.x + tmp->get_text().size();
2603 max_coord.y = min_coord.y + 1;
2604 tmp->set_bounds(min_coord, max_coord);
2605 tmp->set_fg_colour(BROWN);
2606 tmp->add_hotkey('%');
2607 tmp->set_id(M_APTITUDES);
2608 tmp->set_highlight_colour(LIGHTGRAY);
2609 tmp->set_description_text("Lists the numerical skill train aptitudes for all races");
2610 menu->attach_item(tmp);
2611 tmp->set_visible(true);
2613 tmp = new TextItem();
2614 tmp->set_text("? - Help");
2615 min_coord.x = X_MARGIN;
2616 min_coord.y = SPECIAL_KEYS_START_Y + 2;
2617 max_coord.x = min_coord.x + tmp->get_text().size();
2618 max_coord.y = min_coord.y + 1;
2619 tmp->set_bounds(min_coord, max_coord);
2620 tmp->set_fg_colour(BROWN);
2621 tmp->add_hotkey('?');
2622 tmp->set_id(M_HELP);
2623 tmp->set_highlight_colour(LIGHTGRAY);
2624 tmp->set_description_text("Opens the help screen");
2625 menu->attach_item(tmp);
2626 tmp->set_visible(true);
2628 tmp = new TextItem();
2629 tmp->set_text("* - Random wand");
2630 min_coord.x = X_MARGIN + COLUMN_WIDTH;
2631 min_coord.y = SPECIAL_KEYS_START_Y;
2632 max_coord.x = min_coord.x + tmp->get_text().size();
2633 max_coord.y = min_coord.y + 1;
2634 tmp->set_bounds(min_coord, max_coord);
2635 tmp->set_fg_colour(BROWN);
2636 tmp->add_hotkey('*');
2637 tmp->set_id(M_RANDOM);
2638 tmp->set_highlight_colour(LIGHTGRAY);
2639 tmp->set_description_text("Picks a random wand");
2640 menu->attach_item(tmp);
2641 tmp->set_visible(true);
2643 // Adjust the end marker to align the - because Bksp text is longer by 3
2644 tmp = new TextItem();
2645 tmp->set_text("Bksp - Return to character menu");
2646 tmp->set_description_text("Lets you return back to Character choice menu");
2647 min_coord.x = X_MARGIN + COLUMN_WIDTH - 3;
2648 min_coord.y = SPECIAL_KEYS_START_Y + 1;
2649 max_coord.x = min_coord.x + tmp->get_text().size();
2650 max_coord.y = min_coord.y + 1;
2651 tmp->set_bounds(min_coord, max_coord);
2652 tmp->set_fg_colour(BROWN);
2653 tmp->add_hotkey(CK_BKSP);
2654 tmp->set_id(M_ABORT);
2655 tmp->set_highlight_colour(LIGHTGRAY);
2656 menu->attach_item(tmp);
2657 tmp->set_visible(true);
2659 // Only add tab entry if we have a previous wand choice
2660 if (defwand != SWT_NO_SELECTION)
2662 tmp = new TextItem();
2663 text.clear();
2664 text = "Tab - ";
2666 text += defwand == SWT_ENSLAVEMENT ? "Enslavement" :
2667 defwand == SWT_CONFUSION ? "Confusion" :
2668 defwand == SWT_MAGIC_DARTS ? "Magic Darts" :
2669 defwand == SWT_FROST ? "Frost" :
2670 defwand == SWT_FLAME ? "Flame" :
2671 defwand == SWT_STRIKING ? "Striking" :
2672 defwand == SWT_RANDOM ? "Random" :
2673 "Buggy";
2675 // Adjust the end marker to aling the - because
2676 // Tab text is longer by 2
2677 tmp = new TextItem();
2678 tmp->set_text(text);
2679 min_coord.x = X_MARGIN + COLUMN_WIDTH - 2;
2680 min_coord.y = SPECIAL_KEYS_START_Y + 2;
2681 max_coord.x = min_coord.x + tmp->get_text().size();
2682 max_coord.y = min_coord.y + 1;
2683 tmp->set_bounds(min_coord, max_coord);
2684 tmp->set_fg_colour(BROWN);
2685 tmp->add_hotkey('\t');
2686 tmp->set_id(M_DEFAULT_CHOICE);
2687 tmp->set_highlight_colour(LIGHTGRAY);
2688 tmp->set_description_text("Select your previous wand choice");
2689 menu->attach_item(tmp);
2690 tmp->set_visible(true);
2694 static bool _prompt_wand(const newgame_def* ng, newgame_def* ng_choice,
2695 const newgame_def& defaults)
2697 PrecisionMenu menu;
2698 menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT);
2699 MenuFreeform* freeform = new MenuFreeform();
2700 freeform->init(coord_def(1,1), coord_def(get_number_of_cols(),
2701 get_number_of_lines()), "freeform");
2702 menu.attach_object(freeform);
2703 menu.set_active_object(freeform);
2705 const startup_wand_type defwand = defaults.wand;
2707 _construct_wand_menu(defwand, freeform);
2709 BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu);
2710 highlighter->init(coord_def(0,0), coord_def(0,0), "highlighter");
2711 menu.attach_object(highlighter);
2713 // Did we have a previous wand?
2714 if (menu.get_active_item() == NULL)
2716 freeform->activate_first_item();
2718 _print_character_info(ng); // calls clrscr() so needs to be before attach()
2720 #ifdef USE_TILE
2721 tiles.get_crt()->attach_menu(&menu);
2722 #endif
2724 freeform->set_visible(true);
2725 highlighter->set_visible(true);
2727 textcolor(CYAN);
2728 cprintf("\nYou have a choice of tools:");
2730 while (true)
2732 menu.draw_menu();
2734 int keyn = getch_ck();
2736 // First process menu entries
2737 if (!menu.process_key(keyn))
2739 // Process all the keys that are not attached to items
2740 switch (keyn)
2742 case 'X':
2743 cprintf("\nGoodbye!");
2744 end(0);
2745 break;
2746 case ' ':
2747 CASE_ESCAPE
2748 return false;
2749 default:
2750 // if we get this far, we did not get a significant selection
2751 // from the menu, nor did we get an escape character
2752 // continue the while loop from the beginning and poll a new key
2753 continue;
2756 // We have a significant key input!
2757 // Construct selection vector
2758 std::vector<MenuItem*> selection = menu.get_selected_items();
2759 // There should only be one selection, otherwise something broke
2760 if (selection.size() != 1)
2762 // poll a new key
2763 continue;
2766 // Get the stored id from the selection
2767 int selection_ID = selection.at(0)->get_id();
2768 switch (selection_ID)
2770 case M_ABORT:
2771 return false;
2772 case M_APTITUDES:
2773 list_commands('%', false, _highlight_pattern(ng));
2774 return _prompt_wand(ng, ng_choice, defaults);
2775 case M_HELP:
2776 list_commands('?');
2777 return _prompt_wand(ng, ng_choice, defaults);
2778 case M_DEFAULT_CHOICE:
2779 if (defwand != SWT_NO_SELECTION)
2781 ng_choice->wand = defwand;
2782 return true;
2784 // This case should not happen if defwand == swt_no_selection
2785 continue;
2786 case M_RANDOM:
2787 ng_choice->wand = SWT_RANDOM;
2788 return true;
2789 default:
2790 // We got an item selection
2791 ng_choice->wand = static_cast<startup_wand_type> (selection_ID);
2792 return true;
2795 return false;
2798 static void _resolve_wand(newgame_def* ng, const newgame_def* ng_choice)
2800 switch (ng_choice->wand)
2802 case SWT_RANDOM:
2803 ng->wand = static_cast<startup_wand_type>(random2(NUM_STARTUP_WANDS));
2804 return;
2805 default:
2806 ng->wand = ng_choice->wand;
2807 return;
2811 static bool _choose_wand(newgame_def* ng, newgame_def* ng_choice,
2812 const newgame_def& defaults)
2814 if (ng->job != JOB_ARTIFICER)
2815 return (true);
2817 if (ng_choice->wand == SWT_NO_SELECTION)
2818 if (!_prompt_wand(ng, ng_choice, defaults))
2819 return (false);
2821 _resolve_wand(ng, ng_choice);
2822 return (true);
2825 static void _construct_gamemode_map_menu(const mapref_vector& maps,
2826 const newgame_def& defaults,
2827 MenuFreeform* menu)
2829 static const int ITEMS_START_Y = 5;
2830 static const int MENU_COLUMN_WIDTH = get_number_of_cols();
2831 TextItem* tmp = NULL;
2832 std::string text;
2833 coord_def min_coord(0,0);
2834 coord_def max_coord(0,0);
2835 bool activate_next = false;
2837 unsigned int padding_width = 0;
2838 for (int i = 0; i < static_cast<int> (maps.size()); i++)
2840 if (padding_width < maps.at(i)->desc_or_name().length())
2841 padding_width = maps.at(i)->desc_or_name().length();
2843 padding_width += 4; // Count the letter and " - "
2845 for (int i = 0; i < static_cast<int> (maps.size()); i++)
2847 tmp = new TextItem();
2848 text.clear();
2850 tmp->set_fg_colour(LIGHTGREY);
2851 tmp->set_highlight_colour(GREEN);
2853 const char letter = 'a' + i;
2854 text += letter;
2855 text += " - ";
2857 text += maps[i]->desc_or_name();
2858 if (static_cast<int>(text.length()) > MENU_COLUMN_WIDTH - 1)
2859 text = text.substr(0, MENU_COLUMN_WIDTH - 1);
2861 // Add padding
2862 if (padding_width > text.size())
2863 text.append(padding_width - text.size(), ' ');
2865 tmp->set_text(text);
2866 tmp->add_hotkey(letter);
2867 tmp->set_id(i); // ID corresponds to location in vector
2869 min_coord.x = X_MARGIN;
2870 min_coord.y = ITEMS_START_Y + i;
2871 max_coord.x = min_coord.x + text.size();
2872 max_coord.y = min_coord.y + 1;
2873 tmp->set_bounds(min_coord, max_coord);
2875 menu->attach_item(tmp);
2876 tmp->set_visible(true);
2878 if (activate_next)
2880 menu->set_active_item(tmp);
2881 activate_next = false;
2883 // Is this item our default map?
2884 else if (defaults.map == maps[i]->name)
2886 if (crawl_state.last_game_won)
2887 activate_next = true;
2888 else
2889 menu->set_active_item(tmp);
2893 // Don't overwhelm new players with aptitudes or the full list of commands!
2894 if (!crawl_state.game_is_tutorial())
2896 tmp = new TextItem();
2897 tmp->set_text("% - List aptitudes");
2898 min_coord.x = X_MARGIN;
2899 min_coord.y = SPECIAL_KEYS_START_Y + 1;
2900 max_coord.x = min_coord.x + tmp->get_text().size();
2901 max_coord.y = min_coord.y + 1;
2902 tmp->set_bounds(min_coord, max_coord);
2903 tmp->set_fg_colour(BROWN);
2904 tmp->add_hotkey('%');
2905 tmp->set_id(M_APTITUDES);
2906 tmp->set_highlight_colour(LIGHTGRAY);
2907 tmp->set_description_text("Lists the numerical skill train aptitudes for all races");
2908 menu->attach_item(tmp);
2909 tmp->set_visible(true);
2911 tmp = new TextItem();
2912 tmp->set_text("? - Help");
2913 min_coord.x = X_MARGIN;
2914 min_coord.y = SPECIAL_KEYS_START_Y + 2;
2915 max_coord.x = min_coord.x + tmp->get_text().size();
2916 max_coord.y = min_coord.y + 1;
2917 tmp->set_bounds(min_coord, max_coord);
2918 tmp->set_fg_colour(BROWN);
2919 tmp->add_hotkey('?');
2920 tmp->set_id(M_HELP);
2921 tmp->set_highlight_colour(LIGHTGRAY);
2922 tmp->set_description_text("Opens the help screen");
2923 menu->attach_item(tmp);
2924 tmp->set_visible(true);
2926 tmp = new TextItem();
2927 tmp->set_text("* - Random map");
2928 min_coord.x = X_MARGIN + COLUMN_WIDTH;
2929 min_coord.y = SPECIAL_KEYS_START_Y + 1;
2930 max_coord.x = min_coord.x + tmp->get_text().size();
2931 max_coord.y = min_coord.y + 1;
2932 tmp->set_bounds(min_coord, max_coord);
2933 tmp->set_fg_colour(BROWN);
2934 tmp->add_hotkey('*');
2935 tmp->set_id(M_RANDOM);
2936 tmp->set_highlight_colour(LIGHTGRAY);
2937 tmp->set_description_text("Picks a random sprint map");
2938 menu->attach_item(tmp);
2939 tmp->set_visible(true);
2942 // TODO: let players escape back to first screen menu
2943 // Adjust the end marker to align the - because Bksp text is longer by 3
2944 //tmp = new TextItem();
2945 //tmp->set_text("Bksp - Return to character menu");
2946 //tmp->set_description_text("Lets you return back to Character choice menu");
2947 //min_coord.x = X_MARGIN + COLUMN_WIDTH - 3;
2948 //min_coord.y = SPECIAL_KEYS_START_Y + 1;
2949 //max_coord.x = min_coord.x + tmp->get_text().size();
2950 //max_coord.y = min_coord.y + 1;
2951 //tmp->set_bounds(min_coord, max_coord);
2952 //tmp->set_fg_colour(BROWN);
2953 //tmp->add_hotkey(CK_BKSP);
2954 //tmp->set_id(M_ABORT);
2955 //tmp->set_highlight_colour(LIGHTGRAY);
2956 //menu->attach_item(tmp);
2957 //tmp->set_visible(true);
2959 // Only add tab entry if we have a previous map choice
2960 if (crawl_state.game_is_sprint()
2961 && !defaults.map.empty() && _char_defined(defaults))
2963 tmp = new TextItem();
2964 text.clear();
2965 text += "Tab - ";
2966 text += defaults.map;
2968 // Adjust the end marker to align the - because
2969 // Tab text is longer by 2
2970 tmp->set_text(text);
2971 min_coord.x = X_MARGIN + COLUMN_WIDTH - 2;
2972 min_coord.y = SPECIAL_KEYS_START_Y + 2;
2973 max_coord.x = min_coord.x + tmp->get_text().size();
2974 max_coord.y = min_coord.y + 1;
2975 tmp->set_bounds(min_coord, max_coord);
2976 tmp->set_fg_colour(BROWN);
2977 tmp->add_hotkey('\t');
2978 tmp->set_id(M_DEFAULT_CHOICE);
2979 tmp->set_highlight_colour(LIGHTGRAY);
2980 tmp->set_description_text("Select your previous sprint map and character");
2981 menu->attach_item(tmp);
2982 tmp->set_visible(true);
2986 static bool _cmp_map_by_name(const map_def* m1, const map_def* m2)
2988 return (m1->desc_or_name() < m2->desc_or_name());
2991 static void _prompt_gamemode_map(newgame_def* ng, newgame_def* ng_choice,
2992 const newgame_def& defaults,
2993 mapref_vector maps)
2995 PrecisionMenu menu;
2996 menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT);
2997 MenuFreeform* freeform = new MenuFreeform();
2998 freeform->init(coord_def(1,1), coord_def(get_number_of_cols(),
2999 get_number_of_lines()), "freeform");
3000 menu.attach_object(freeform);
3001 menu.set_active_object(freeform);
3003 std::sort(maps.begin(), maps.end(), _cmp_map_by_name);
3004 _construct_gamemode_map_menu(maps, defaults, freeform);
3006 BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu);
3007 highlighter->init(coord_def(0,0), coord_def(0,0), "highlighter");
3008 menu.attach_object(highlighter);
3010 // Did we have a previous sprint map?
3011 if (menu.get_active_item() == NULL)
3012 freeform->activate_first_item();
3014 _print_character_info(ng); // calls clrscr() so needs to be before attach()
3016 #ifdef USE_TILE
3017 tiles.get_crt()->attach_menu(&menu);
3018 #endif
3020 freeform->set_visible(true);
3021 highlighter->set_visible(true);
3023 textcolor(CYAN);
3024 cprintf("\nYou have a choice of %s:\n\n",
3025 ng_choice->type == GAME_TYPE_TUTORIAL ? "lessons"
3026 : "maps");
3028 while (true)
3030 menu.draw_menu();
3032 int keyn = getch_ck();
3034 // First process menu entries
3035 if (!menu.process_key(keyn))
3037 // Process all the keys that are not attached to items
3038 switch (keyn)
3040 case 'X':
3041 cprintf("\nGoodbye!");
3042 end(0);
3043 break;
3044 CASE_ESCAPE
3045 game_ended();
3046 break;
3047 case ' ':
3048 return;
3049 default:
3050 // if we get this far, we did not get a significant selection
3051 // from the menu, nor did we get an escape character
3052 // continue the while loop from the beginning and poll a new key
3053 continue;
3056 // We have a significant key input!
3057 // Construct selection vector
3058 std::vector<MenuItem*> selection = menu.get_selected_items();
3059 // There should only be one selection, otherwise something broke
3060 if (selection.size() != 1)
3062 // poll a new key
3063 continue;
3066 // Get the stored id from the selection
3067 int selection_ID = selection.at(0)->get_id();
3068 switch (selection_ID)
3070 case M_ABORT:
3071 // TODO: fix
3072 return;
3073 case M_APTITUDES:
3074 list_commands('%', false, _highlight_pattern(ng));
3075 return _prompt_gamemode_map(ng, ng_choice, defaults, maps);
3076 case M_HELP:
3077 list_commands('?');
3078 return _prompt_gamemode_map(ng, ng_choice, defaults, maps);
3079 case M_DEFAULT_CHOICE:
3080 _set_default_choice(ng, ng_choice, defaults);
3081 return;
3082 case M_RANDOM:
3083 // FIXME setting this to "random" is broken
3084 ng_choice->map.clear();
3085 return;
3086 default:
3087 // We got an item selection
3088 ng_choice->map = maps.at(selection_ID)->name;
3089 return;
3094 static void _resolve_gamemode_map(newgame_def* ng, const newgame_def* ng_choice,
3095 const mapref_vector& maps)
3097 if (ng_choice->map == "random" || ng_choice->map.empty())
3098 ng->map = maps[random2(maps.size())]->name;
3099 else
3100 ng->map = ng_choice->map;
3103 static void _choose_gamemode_map(newgame_def* ng, newgame_def* ng_choice,
3104 const newgame_def& defaults)
3106 // Sprint, otherwise Tutorial.
3107 const bool is_sprint = (ng_choice->type == GAME_TYPE_SPRINT);
3109 const mapref_vector maps = (is_sprint ? get_sprint_maps()
3110 : get_tutorial_maps());
3111 if (maps.empty())
3113 end(1, true, make_stringf("No %s maps found.",
3114 is_sprint ? "sprint"
3115 : "tutorial").c_str());
3118 if (ng_choice->map.empty())
3120 if (is_sprint
3121 && ng_choice->type == !crawl_state.sprint_map.empty())
3123 ng_choice->map = crawl_state.sprint_map;
3125 else if (maps.size() > 1)
3126 _prompt_gamemode_map(ng, ng_choice, defaults, maps);
3127 else
3128 ng_choice->map = maps[0]->name;
3131 _resolve_gamemode_map(ng, ng_choice, maps);