1 /**
2  * \file ui-birth.c
3  * \brief Text-based user interface for character creation
4  *
5  * Copyright (c) 1987 - 2015 Angband contributors
6  *
7  * This work is free software; you can redistribute it and/or modify it
8  * under the terms of either:
9  *
10  * a) the GNU General Public License as published by the Free Software
11  *    Foundation, version 2, or
12  *
13  * b) the "Angband licence":
14  *    This software may be copied and distributed for educational, research,
15  *    and not for profit purposes provided that this copyright and statement
16  *    are included in all such copies.  Other copyrights may also apply.
17  */
18 
19 #include "angband.h"
20 #include "cmds.h"
21 #include "cmd-core.h"
22 #include "game-event.h"
23 #include "game-input.h"
24 #include "obj-tval.h"
25 #include "player.h"
26 #include "player-spell.h"
27 #include "ui-birth.h"
28 #include "ui-display.h"
29 #include "ui-game.h"
30 #include "ui-help.h"
31 #include "ui-input.h"
32 #include "ui-menu.h"
33 #include "ui-options.h"
34 #include "ui-player.h"
35 #include "ui-prefs.h"
36 #include "ui-target.h"
37 
38 /**
39  * Overview
40  * ========
41  * This file implements the user interface side of the birth process
42  * for the classic terminal-based UI of Angband.
43  *
44  * It models birth as a series of steps which must be carried out in
45  * a specified order, with the option of stepping backwards to revisit
46  * past choices.
47  *
48  * It starts when we receive the EVENT_ENTER_BIRTH event from the game,
49  * and ends when we receive the EVENT_LEAVE_BIRTH event.  In between,
50  * we will repeatedly be asked to supply a game command, which change
51  * the state of the character being rolled.  Once the player is happy
52  * with their character, we send the CMD_ACCEPT_CHARACTER command.
53  */
54 
55 /**
56  * A local-to-this-file global to hold the most important bit of state
57  * between calls to the game proper.  Probably not strictly necessary,
58  * but reduces complexity a bit. */
59 enum birth_stage
60 {
61 	BIRTH_BACK = -1,
62 	BIRTH_RESET = 0,
63 	BIRTH_QUICKSTART,
64 	BIRTH_RACE_CHOICE,
65 	BIRTH_CLASS_CHOICE,
66 	BIRTH_ROLLER_CHOICE,
67 	BIRTH_POINTBASED,
68 	BIRTH_ROLLER,
69 	BIRTH_NAME_CHOICE,
70 	BIRTH_HISTORY_CHOICE,
71 	BIRTH_FINAL_CONFIRM,
72 	BIRTH_COMPLETE
73 };
74 
75 
76 enum birth_questions
77 {
78 	BQ_METHOD = 0,
79 	BQ_RACE,
80 	BQ_CLASS,
81 	BQ_ROLLER,
82 	MAX_BIRTH_QUESTIONS
83 };
84 
85 enum birth_rollers
86 {
87 	BR_POINTBASED = 0,
88 	BR_NORMAL,
89 	MAX_BIRTH_ROLLERS
90 };
91 
92 
93 static void point_based_start(void);
94 static bool quickstart_allowed = false;
95 bool arg_force_name;
96 
97 /**
98  * ------------------------------------------------------------------------
99  * Quickstart? screen.
100  * ------------------------------------------------------------------------ */
textui_birth_quickstart(void)101 static enum birth_stage textui_birth_quickstart(void)
102 //phantom name change changes
103 {
104 	const char *prompt = "['Y': use as is; 'N': redo; 'C': change name/history; '=': set birth options]";
105 
106 	enum birth_stage next = BIRTH_QUICKSTART;
107 
108 	/* Prompt for it */
109 	prt("New character based on previous one:", 0, 0);
110 	prt(prompt, Term->hgt - 1, Term->wid / 2 - strlen(prompt) / 2);
111 
112 	do {
113 		/* Get a key */
114 		struct keypress ke = inkey();
115 
116 		if (ke.code == 'N' || ke.code == 'n') {
117 			cmdq_push(CMD_BIRTH_RESET);
118 			next = BIRTH_RACE_CHOICE;
119 		} else if (ke.code == KTRL('X')) {
120 			quit(NULL);
121 		} else if ( !arg_force_name && (ke.code == 'C' || ke.code == 'c')) {
122 			next = BIRTH_NAME_CHOICE;
123 		} else if (ke.code == '=') {
124 			do_cmd_options_birth();
125 		} else if (ke.code == 'Y' || ke.code == 'y') {
126 			cmdq_push(CMD_ACCEPT_CHARACTER);
127 			next = BIRTH_COMPLETE;
128 		}
129 	} while (next == BIRTH_QUICKSTART);
130 
131 	/* Clear prompt */
132 	clear_from(23);
133 
134 	return next;
135 }
136 
137 /**
138  * ------------------------------------------------------------------------
139  * The various "menu" bits of the birth process - namely choice of race,
140  * class, and roller type.
141  * ------------------------------------------------------------------------ */
142 
143 /**
144  * The various menus
145  */
146 static struct menu race_menu, class_menu, roller_menu;
147 
148 /**
149  * Locations of the menus, etc. on the screen
150  */
151 #define HEADER_ROW       1
152 #define QUESTION_ROW     7
153 #define TABLE_ROW        9
154 
155 #define QUESTION_COL     2
156 #define RACE_COL         2
157 #define RACE_AUX_COL    19
158 #define CLASS_COL       19
159 #define CLASS_AUX_COL   36
160 #define ROLLER_COL      36
161 #define HIST_INSTRUCT_ROW 18
162 
163 #define MENU_ROWS TABLE_ROW + 14
164 
165 /**
166  * upper left column and row, width, and lower column
167  */
168 static region race_region = {RACE_COL, TABLE_ROW, 17, MENU_ROWS};
169 static region class_region = {CLASS_COL, TABLE_ROW, 17, MENU_ROWS};
170 static region roller_region = {ROLLER_COL, TABLE_ROW, 34, MENU_ROWS};
171 
172 /**
173  * We use different menu "browse functions" to display the help text
174  * sometimes supplied with the menu items - currently just the list
175  * of bonuses, etc, corresponding to each race and class.
176  */
177 typedef void (*browse_f) (int oid, void *db, const region *l);
178 
179 /**
180  * We have one of these structures for each menu we display - it holds
181  * the useful information for the menu - text of the menu items, "help"
182  * text, current (or default) selection, and whether random selection
183  * is allowed.
184  */
185 struct birthmenu_data
186 {
187 	const char **items;
188 	const char *hint;
189 	bool allow_random;
190 };
191 
192 /**
193  * A custom "display" function for our menus that simply displays the
194  * text from our stored data in a different colour if it's currently
195  * selected.
196  */
birthmenu_display(struct menu * menu,int oid,bool cursor,int row,int col,int width)197 static void birthmenu_display(struct menu *menu, int oid, bool cursor,
198 			      int row, int col, int width)
199 {
200 	struct birthmenu_data *data = menu->menu_data;
201 
202 	byte attr = curs_attrs[CURS_KNOWN][0 != cursor];
203 	c_put_str(attr, data->items[oid], row, col);
204 }
205 
206 /**
207  * Our custom menu iterator, only really needed to allow us to override
208  * the default handling of "commands" in the standard iterators (hence
209  * only defining the display and handler parts).
210  */
211 static const menu_iter birth_iter = { NULL, NULL, birthmenu_display, NULL, NULL };
212 
skill_help(const int r_skills[],const int c_skills[],int mhp,int exp,int infra)213 static void skill_help(const int r_skills[], const int c_skills[], int mhp, int exp, int infra)
214 {
215 	s16b skills[SKILL_MAX];
216 	unsigned i;
217 
218 	for (i = 0; i < SKILL_MAX ; ++i)
219 		skills[i] = (r_skills ? r_skills[i] : 0 ) + (c_skills ? c_skills[i] : 0);
220 
221 	text_out_e("Hit/Shoot/Throw: %+d/%+d/%+d\n", skills[SKILL_TO_HIT_MELEE],
222 			   skills[SKILL_TO_HIT_BOW], skills[SKILL_TO_HIT_THROW]);
223 	text_out_e("Hit die: %2d   XP mod: %d%%\n", mhp, exp);
224 	text_out_e("Disarm: %+3d/%+3d   Devices: %+3d\n", skills[SKILL_DISARM_PHYS],
225 			   skills[SKILL_DISARM_MAGIC], skills[SKILL_DEVICE]);
226 	text_out_e("Save:   %+3d   Stealth: %+3d\n", skills[SKILL_SAVE],
227 			   skills[SKILL_STEALTH]);
228 	if (infra >= 0)
229 		text_out_e("Infravision:  %d ft\n", infra * 10);
230 	text_out_e("Digging:      %+d\n", skills[SKILL_DIGGING]);
231 	text_out_e("Search:       %+d", skills[SKILL_SEARCH]);
232 	if (infra < 0)
233 		text_out_e("\n");
234 }
235 
race_help(int i,void * db,const region * l)236 static void race_help(int i, void *db, const region *l)
237 {
238 	int j;
239 	struct player_race *r = player_id2race(i);
240 	int len = (STAT_MAX + 1) / 2;
241 
242 	struct player_ability *ability;
243 	int n_flags = 0;
244 	int flag_space = 3;
245 
246 	if (!r) return;
247 
248 	/* Output to the screen */
249 	text_out_hook = text_out_to_screen;
250 
251 	/* Indent output */
252 	text_out_indent = RACE_AUX_COL;
253 	Term_gotoxy(RACE_AUX_COL, TABLE_ROW);
254 
255 	for (j = 0; j < len; j++) {
256 		const char *name = stat_names_reduced[j];
257 		int adj = r->r_adj[j];
258 
259 		text_out_e("%s%+3d", name, adj);
260 
261 		if (j * 2 + 1 < STAT_MAX) {
262 			name = stat_names_reduced[j + len];
263 			adj = r->r_adj[j + len];
264 			text_out_e("  %s%+3d", name, adj);
265 		}
266 
267 		text_out("\n");
268 	}
269 
270 	text_out_e("\n");
271 	skill_help(r->r_skills, NULL, r->r_mhp, r->r_exp, r->infra);
272 	text_out_e("\n");
273 
274 	for (ability = player_abilities; ability; ability = ability->next) {
275 		if (n_flags >= flag_space) break;
276 		if (streq(ability->type, "object") &&
277 			!of_has(r->flags, ability->index)) {
278 			continue;
279 		} else if (streq(ability->type, "player") &&
280 				   !pf_has(r->pflags, ability->index)) {
281 			continue;
282 		} else if (streq(ability->type, "element") &&
283 				   (r->el_info[ability->index].res_level != ability->value)) {
284 			continue;
285 		}
286 		text_out_e("\n%s", ability->name);
287 		n_flags++;
288 	}
289 
290 	while (n_flags < flag_space) {
291 		text_out_e("\n");
292 		n_flags++;
293 	}
294 
295 	/* Reset text_out() indentation */
296 	text_out_indent = 0;
297 }
298 
class_help(int i,void * db,const region * l)299 static void class_help(int i, void *db, const region *l)
300 {
301 	int j;
302 	struct player_class *c = player_id2class(i);
303 	const struct player_race *r = player->race;
304 	int len = (STAT_MAX + 1) / 2;
305 
306 	struct player_ability *ability;
307 	int n_flags = 0;
308 	int flag_space = 5;
309 
310 	if (!c) return;
311 
312 	/* Output to the screen */
313 	text_out_hook = text_out_to_screen;
314 
315 	/* Indent output */
316 	text_out_indent = CLASS_AUX_COL;
317 	Term_gotoxy(CLASS_AUX_COL, TABLE_ROW);
318 
319 	for (j = 0; j < len; j++) {
320 		const char *name = stat_names_reduced[j];
321 		int adj = c->c_adj[j] + r->r_adj[j];
322 
323 		text_out_e("%s%+3d", name, adj);
324 
325 		if (j*2 + 1 < STAT_MAX) {
326 			name = stat_names_reduced[j + len];
327 			adj = c->c_adj[j + len] + r->r_adj[j + len];
328 			text_out_e("  %s%+3d", name, adj);
329 		}
330 
331 		text_out("\n");
332 	}
333 
334 	text_out_e("\n");
335 
336 	skill_help(r->r_skills, c->c_skills, r->r_mhp + c->c_mhp,
337 			   r->r_exp + c->c_exp, -1);
338 
339 	if (c->magic.total_spells) {
340 		int count;
341 		struct magic_realm *realm = class_magic_realms(c, &count), *realm_next;
342 		char buf[120];
343 
344 		my_strcpy(buf, realm->name, sizeof(buf));
345 		realm_next = realm->next;
346 		mem_free(realm);
347 		realm = realm_next;
348 		if (count > 1) {
349 			while (realm) {
350 				count--;
351 				if (count) {
352 					my_strcat(buf, ", ", sizeof(buf));
353 				} else {
354 					my_strcat(buf, " and ", sizeof(buf));
355 				}
356 				my_strcat(buf, realm->name, sizeof(buf));
357 				realm_next = realm->next;
358 				mem_free(realm);
359 				realm = realm_next;
360 			}
361 		}
362 		text_out_e("\nLearns %s magic", buf);
363 	}
364 
365 	for (ability = player_abilities; ability; ability = ability->next) {
366 		if (n_flags >= flag_space) break;
367 		if (streq(ability->type, "object") &&
368 			!of_has(c->flags, ability->index)) {
369 			continue;
370 		} else if (streq(ability->type, "player") &&
371 				   !pf_has(c->pflags, ability->index)) {
372 			continue;
373 		} else if (streq(ability->type, "element")) {
374 			continue;
375 		}
376 
377 		text_out_e("\n%s", ability->name);
378 		n_flags++;
379 	}
380 
381 	while (n_flags < flag_space) {
382 		text_out_e("\n");
383 		n_flags++;
384 	}
385 
386 	/* Reset text_out() indentation */
387 	text_out_indent = 0;
388 }
389 
390 /**
391  * Set up one of our menus ready to display choices for a birth question.
392  * This is slightly involved.
393  */
init_birth_menu(struct menu * menu,int n_choices,int initial_choice,const region * reg,bool allow_random,browse_f aux)394 static void init_birth_menu(struct menu *menu, int n_choices,
395 							int initial_choice, const region *reg,
396 							bool allow_random, browse_f aux)
397 {
398 	struct birthmenu_data *menu_data;
399 
400 	/* Initialise a basic menu */
401 	menu_init(menu, MN_SKIN_SCROLL, &birth_iter);
402 
403 	/* A couple of behavioural flags - we want selections letters in
404 	   lower case and a double tap to act as a selection. */
405 	menu->selections = lower_case;
406 	menu->flags = MN_DBL_TAP;
407 
408 	/* Copy across the game's suggested initial selection, etc. */
409 	menu->cursor = initial_choice;
410 
411 	/* Allocate sufficient space for our own bits of menu information. */
412 	menu_data = mem_alloc(sizeof *menu_data);
413 
414 	/* Allocate space for an array of menu item texts and help texts
415 	   (where applicable) */
416 	menu_data->items = mem_alloc(n_choices * sizeof *menu_data->items);
417 	menu_data->allow_random = allow_random;
418 
419 	/* Set private data */
420 	menu_setpriv(menu, n_choices, menu_data);
421 
422 	/* Set up the "browse" hook to display help text (where applicable). */
423 	menu->browse_hook = aux;
424 
425 	/* Lay out the menu appropriately */
426 	menu_layout(menu, reg);
427 }
428 
429 
430 
setup_menus(void)431 static void setup_menus(void)
432 {
433 	int i, n;
434 	struct player_class *c;
435 	struct player_race *r;
436 
437 	const char *roller_choices[MAX_BIRTH_ROLLERS] = {
438 		"Point-based",
439 		"Standard roller"
440 	};
441 
442 	struct birthmenu_data *mdata;
443 
444 	/* Count the races */
445 	n = 0;
446 	for (r = races; r; r = r->next) n++;
447 
448 	/* Race menu. */
449 	init_birth_menu(&race_menu, n, player->race ? player->race->ridx : 0,
450 	                &race_region, true, race_help);
451 	mdata = race_menu.menu_data;
452 
453 	for (i = 0, r = races; r; r = r->next, i++)
454 		mdata->items[r->ridx] = r->name;
455 	mdata->hint = "Race affects stats and skills, and may confer resistances and abilities.";
456 
457 	/* Count the classes */
458 	n = 0;
459 	for (c = classes; c; c = c->next) n++;
460 
461 	/* Class menu similar to race. */
462 	init_birth_menu(&class_menu, n, player->class ? player->class->cidx : 0,
463 	                &class_region, true, class_help);
464 	mdata = class_menu.menu_data;
465 
466 	for (i = 0, c = classes; c; c = c->next, i++)
467 		mdata->items[c->cidx] = c->name;
468 	mdata->hint = "Class affects stats, skills, and other character traits.";
469 
470 	/* Roller menu straightforward */
471 	init_birth_menu(&roller_menu, MAX_BIRTH_ROLLERS, 0, &roller_region, false,
472 					NULL);
473 	mdata = roller_menu.menu_data;
474 	for (i = 0; i < MAX_BIRTH_ROLLERS; i++)
475 		mdata->items[i] = roller_choices[i];
476 	mdata->hint = "Choose how to generate your intrinsic stats. Point-based is recommended.";
477 }
478 
479 /**
480  * Cleans up our stored menu info when we've finished with it.
481  */
free_birth_menu(struct menu * menu)482 static void free_birth_menu(struct menu *menu)
483 {
484 	struct birthmenu_data *data = menu->menu_data;
485 
486 	if (data) {
487 		mem_free(data->items);
488 		mem_free(data);
489 	}
490 }
491 
free_birth_menus(void)492 static void free_birth_menus(void)
493 {
494 	/* We don't need these any more. */
495 	free_birth_menu(&race_menu);
496 	free_birth_menu(&class_menu);
497 	free_birth_menu(&roller_menu);
498 }
499 
500 /**
501  * Clear the previous question
502  */
clear_question(void)503 static void clear_question(void)
504 {
505 	int i;
506 
507 	for (i = QUESTION_ROW; i < TABLE_ROW; i++)
508 		/* Clear line, position cursor */
509 		Term_erase(0, i, 255);
510 }
511 
512 
513 #define BIRTH_MENU_HELPTEXT \
514 	"{light blue}Please select your character traits from the menus below:{/}\n\n" \
515 	"Use the {light green}movement keys{/} to scroll the menu, " \
516 	"{light green}Enter{/} to select the current menu item, '{light green}*{/}' " \
517 	"for a random menu item, '{light green}ESC{/}' to step back through the " \
518 	"birth process, '{light green}={/}' for the birth options, '{light green}?{/}' " \
519 	"for help, or '{light green}Ctrl-X{/}' to quit."
520 
521 /**
522  * Show the birth instructions on an otherwise blank screen
523  */
print_menu_instructions(void)524 static void print_menu_instructions(void)
525 {
526 	/* Clear screen */
527 	Term_clear();
528 
529 	/* Output to the screen */
530 	text_out_hook = text_out_to_screen;
531 
532 	/* Indent output */
533 	text_out_indent = QUESTION_COL;
534 	Term_gotoxy(QUESTION_COL, HEADER_ROW);
535 
536 	/* Display some helpful information */
537 	text_out_e(BIRTH_MENU_HELPTEXT);
538 
539 	/* Reset text_out() indentation */
540 	text_out_indent = 0;
541 }
542 
543 /**
544  * Allow the user to select from the current menu, and return the
545  * corresponding command to the game.  Some actions are handled entirely
546  * by the UI (displaying help text, for instance).
547  */
menu_question(enum birth_stage current,struct menu * current_menu,cmd_code choice_command)548 static enum birth_stage menu_question(enum birth_stage current,
549 									  struct menu *current_menu,
550 									  cmd_code choice_command)
551 {
552 	struct birthmenu_data *menu_data = menu_priv(current_menu);
553 	ui_event cx;
554 
555 	enum birth_stage next = BIRTH_RESET;
556 
557 	/* Print the question currently being asked. */
558 	clear_question();
559 	Term_putstr(QUESTION_COL, QUESTION_ROW, -1, COLOUR_YELLOW, menu_data->hint);
560 
561 	current_menu->cmd_keys = "?=*\x18";	 /* ?, =, *, <ctl-X> */
562 
563 	while (next == BIRTH_RESET) {
564 		/* Display the menu, wait for a selection of some sort to be made. */
565 		cx = menu_select(current_menu, EVT_KBRD, false);
566 
567 		/* As all the menus are displayed in "hierarchical" style, we allow
568 		   use of "back" (left arrow key or equivalent) to step back in
569 		   the proces as well as "escape". */
570 		if (cx.type == EVT_ESCAPE) {
571 			next = BIRTH_BACK;
572 		} else if (cx.type == EVT_SELECT) {
573 			if (current == BIRTH_ROLLER_CHOICE) {
574 				if (current_menu->cursor) {
575 					/* Do a first roll of the stats */
576 					cmdq_push(CMD_ROLL_STATS);
577 					next = current + 2;
578 				} else {
579 					/*
580 					 * Make sure we've got a point-based char to play with.
581 					 * We call point_based_start here to make sure we get
582 					 * an update on the points totals before trying to
583 					 * display the screen.  The call to CMD_RESET_STATS
584 					 * forces a rebuying of the stats to give us up-to-date
585 					 * totals.  This is, it should go without saying, a hack.
586 					 */
587 					point_based_start();
588 					cmdq_push(CMD_RESET_STATS);
589 					cmd_set_arg_choice(cmdq_peek(), "choice", true);
590 					next = current + 1;
591 				}
592 			} else {
593 				cmdq_push(choice_command);
594 				cmd_set_arg_choice(cmdq_peek(), "choice", current_menu->cursor);
595 				next = current + 1;
596 			}
597 		} else if (cx.type == EVT_KBRD) {
598 			/* '*' chooses an option at random from those the game's provided */
599 			if (cx.key.code == '*' && menu_data->allow_random) {
600 				current_menu->cursor = randint0(current_menu->count);
601 				cmdq_push(choice_command);
602 				cmd_set_arg_choice(cmdq_peek(), "choice", current_menu->cursor);
603 
604 				menu_refresh(current_menu, false);
605 				next = current + 1;
606 			} else if (cx.key.code == '=') {
607 				do_cmd_options_birth();
608 				next = current;
609 			} else if (cx.key.code == KTRL('X')) {
610 				quit(NULL);
611 			} else if (cx.key.code == '?') {
612 				do_cmd_help();
613 			}
614 		}
615 	}
616 
617 	return next;
618 }
619 
620 /**
621  * ------------------------------------------------------------------------
622  * The rolling bit of the roller.
623  * ------------------------------------------------------------------------ */
roller_command(bool first_call)624 static enum birth_stage roller_command(bool first_call)
625 {
626 	char prompt[80] = "";
627 	size_t promptlen = 0;
628 
629 	struct keypress ch;
630 
631 	enum birth_stage next = BIRTH_ROLLER;
632 
633 	/* Used to keep track of whether we've rolled a character before or not. */
634 	static bool prev_roll = false;
635 
636 	/* Display the player - a bit cheaty, but never mind. */
637 	display_player(0);
638 
639 	if (first_call)
640 		prev_roll = false;
641 
642 	/* Prepare a prompt (must squeeze everything in) */
643 	strnfcat(prompt, sizeof (prompt), &promptlen, "['r' to reroll");
644 	if (prev_roll)
645 		strnfcat(prompt, sizeof(prompt), &promptlen, ", 'p' for previous roll");
646 	strnfcat(prompt, sizeof (prompt), &promptlen, " or 'Enter' to accept]");
647 
648 	/* Prompt for it */
649 	prt(prompt, Term->hgt - 1, Term->wid / 2 - promptlen / 2);
650 
651 	/* Prompt and get a command */
652 	ch = inkey();
653 
654 	/* Analyse the command */
655 	if (ch.code == ESCAPE) {
656 		/* Back out */
657 		next = BIRTH_BACK;
658 	} else if (ch.code == KC_ENTER) {
659 		/* 'Enter' accepts the roll */
660 		next = BIRTH_NAME_CHOICE;
661 	} else if ((ch.code == ' ') || (ch.code == 'r')) {
662 		/* Reroll this character */
663 		cmdq_push(CMD_ROLL_STATS);
664 		prev_roll = true;
665 	} else if (prev_roll && (ch.code == 'p')) {
666 		/* Previous character */
667 		cmdq_push(CMD_PREV_STATS);
668 	} else if (ch.code == KTRL('X')) {
669 		/* Quit */
670 		quit(NULL);
671 	} else if (ch.code == '?') {
672 		/* Help XXX */
673 		do_cmd_help();
674 	} else {
675 		/* Nothing handled directly here */
676 		bell("Illegal roller command!");
677 	}
678 
679 	return next;
680 }
681 
682 /**
683  * ------------------------------------------------------------------------
684  * Point-based stat allocation.
685  * ------------------------------------------------------------------------ */
686 
687 /* The locations of the "costs" area on the birth screen. */
688 #define COSTS_ROW 2
689 #define COSTS_COL (42 + 32)
690 #define TOTAL_COL (42 + 19)
691 
692 /**
693  * This is called whenever a stat changes.  We take the easy road, and just
694  * redisplay them all using the standard function.
695  */
point_based_stats(game_event_type type,game_event_data * data,void * user)696 static void point_based_stats(game_event_type type, game_event_data *data,
697 							  void *user)
698 {
699 	display_player_stat_info();
700 }
701 
702 /**
703  * This is called whenever any of the other miscellaneous stat-dependent things
704  * changed.  We are hooked into changes in the amount of gold in this case,
705  * but redisplay everything because it's easier.
706  */
point_based_misc(game_event_type type,game_event_data * data,void * user)707 static void point_based_misc(game_event_type type, game_event_data *data,
708 							 void *user)
709 {
710 	display_player_xtra_info();
711 }
712 
713 
714 /**
715  * This is called whenever the points totals are changed (in birth.c), so
716  * that we can update our display of how many points have been spent and
717  * are available.
718  */
point_based_points(game_event_type type,game_event_data * data,void * user)719 static void point_based_points(game_event_type type, game_event_data *data,
720 							   void *user)
721 {
722 	int i;
723 	int sum = 0;
724 	int *stats = data->birthstats.stats;
725 
726 	/* Display the costs header */
727 	put_str("Cost", COSTS_ROW - 1, COSTS_COL);
728 
729 	/* Display the costs */
730 	for (i = 0; i < STAT_MAX; i++) {
731 		/* Display cost */
732 		put_str(format("%4d", stats[i]), COSTS_ROW + i, COSTS_COL);
733 		sum += stats[i];
734 	}
735 
736 	put_str(format("Total Cost: %2d/%2d", sum,
737 				   data->birthstats.remaining + sum), COSTS_ROW + STAT_MAX,
738 			TOTAL_COL);
739 }
740 
point_based_start(void)741 static void point_based_start(void)
742 {
743 	const char *prompt = "[up/down to move, left/right to modify, 'r' to reset, 'Enter' to accept]";
744 
745 	/* Clear */
746 	Term_clear();
747 
748 	/* Display the player */
749 	display_player_xtra_info();
750 	display_player_stat_info();
751 
752 	prt(prompt, Term->hgt - 1, Term->wid / 2 - strlen(prompt) / 2);
753 
754 	/* Register handlers for various events - cheat a bit because we redraw
755 	   the lot at once rather than each bit at a time. */
756 	event_add_handler(EVENT_BIRTHPOINTS, point_based_points, NULL);
757 	event_add_handler(EVENT_STATS, point_based_stats, NULL);
758 	event_add_handler(EVENT_GOLD, point_based_misc, NULL);
759 }
760 
point_based_stop(void)761 static void point_based_stop(void)
762 {
763 	event_remove_handler(EVENT_BIRTHPOINTS, point_based_points, NULL);
764 	event_remove_handler(EVENT_STATS, point_based_stats, NULL);
765 	event_remove_handler(EVENT_GOLD, point_based_misc, NULL);
766 }
767 
point_based_command(void)768 static enum birth_stage point_based_command(void)
769 {
770 	static int stat = 0;
771 	struct keypress ch;
772 	enum birth_stage next = BIRTH_POINTBASED;
773 
774 	/* Place cursor just after cost of current stat */
775 	Term_gotoxy(COSTS_COL + 4, COSTS_ROW + stat);
776 
777 	/* Get key */
778 	ch = inkey();
779 
780 	if (ch.code == KTRL('X')) {
781 		quit(NULL);
782 	} else if (ch.code == ESCAPE) {
783 		/* Go back a step, or back to the start of this step */
784 		next = BIRTH_BACK;
785 	} else if (ch.code == 'r' || ch.code == 'R') {
786 		cmdq_push(CMD_RESET_STATS);
787 		cmd_set_arg_choice(cmdq_peek(), "choice", false);
788 	} else if (ch.code == KC_ENTER) {
789 		/* Done */
790 		next = BIRTH_NAME_CHOICE;
791 	} else {
792 		int dir = target_dir(ch);
793 
794 		/* Prev stat, looping round to the bottom when going off the top */
795 		if (dir == 8)
796 			stat = (stat + STAT_MAX - 1) % STAT_MAX;
797 
798 		/* Next stat, looping round to the top when going off the bottom */
799 		if (dir == 2)
800 			stat = (stat + 1) % STAT_MAX;
801 
802 		/* Decrease stat (if possible) */
803 		if (dir == 4) {
804 			cmdq_push(CMD_SELL_STAT);
805 			cmd_set_arg_choice(cmdq_peek(), "choice", stat);
806 		}
807 
808 		/* Increase stat (if possible) */
809 		if (dir == 6) {
810 			cmdq_push(CMD_BUY_STAT);
811 			cmd_set_arg_choice(cmdq_peek(), "choice", stat);
812 		}
813 	}
814 
815 	return next;
816 }
817 
818 /**
819  * ------------------------------------------------------------------------
820  * Asking for the player's chosen name.
821  * ------------------------------------------------------------------------ */
822 //phantom changes for server
get_name_command(void)823 static enum birth_stage get_name_command(void)
824 {
825 	enum birth_stage next;
826 	char name[PLAYER_NAME_LEN];
827 
828 	/* Use frontend-provided savefile name if requested */
829 	if (arg_name[0]) {
830 		my_strcpy(player->full_name, arg_name, sizeof(player->full_name));
831 	}
832 
833 	if (arg_force_name) {
834 		next = BIRTH_HISTORY_CHOICE;
835 	} else if (get_character_name(name, sizeof(name))) {
836 		cmdq_push(CMD_NAME_CHOICE);
837 		cmd_set_arg_string(cmdq_peek(), "name", name);
838 		next = BIRTH_HISTORY_CHOICE;
839 	} else {
840 		next = BIRTH_BACK;
841 	}
842 
843 
844 	return next;
845 }
846 
get_screen_loc(size_t cursor,int * x,int * y,size_t n_lines,size_t * line_starts,size_t * line_lengths)847 void get_screen_loc(size_t cursor, int *x, int *y, size_t n_lines, size_t *line_starts, size_t *line_lengths)
848 {
849 	size_t lengths_so_far = 0;
850 	size_t i;
851 
852 	if (!line_starts || !line_lengths) return;
853 
854 	for (i = 0; i < n_lines; i++) {
855 		if (cursor >= line_starts[i]) {
856 			if (cursor <= (line_starts[i] + line_lengths[i])) {
857 				*y = i;
858 				*x = cursor - lengths_so_far;
859 				break;
860 			}
861 		}
862 		/* +1 for the space */
863 		lengths_so_far += line_lengths[i] + 1;
864 	}
865 }
866 
edit_text(char * buffer,int buflen)867 int edit_text(char *buffer, int buflen) {
868 	int len = strlen(buffer);
869 	bool done = false;
870 	int cursor = 0;
871 
872 	while (!done) {
873 		int x = 0, y = 0;
874 		struct keypress ke;
875 
876 		region area = { 1, HIST_INSTRUCT_ROW + 1, 71, 5 };
877 		textblock *tb = textblock_new();
878 
879 		size_t *line_starts = NULL, *line_lengths = NULL;
880 		size_t n_lines;
881 		/*
882 		 * This is the total number of UTF-8 characters; can be less
883 		 * less than len, the number of 8-bit units in the buffer,
884 		 * if a single character is encoded with more than one 8-bit
885 		 * unit.
886 		 */
887 		int ulen;
888 
889 		/* Display on screen */
890 		clear_from(HIST_INSTRUCT_ROW);
891 		textblock_append(tb, "%s", buffer);
892 		textui_textblock_place(tb, area, NULL);
893 
894 		n_lines = textblock_calculate_lines(tb,
895 				&line_starts, &line_lengths, area.width);
896 		ulen = (n_lines > 0) ? line_starts[n_lines - 1] +
897 			line_lengths[n_lines - 1]: 0;
898 
899 		/* Set cursor to current editing position */
900 		get_screen_loc(cursor, &x, &y, n_lines, line_starts, line_lengths);
901 		Term_gotoxy(1 + x, 19 + y);
902 
903 		ke = inkey();
904 		switch (ke.code) {
905 			case ESCAPE:
906 				return -1;
907 
908 			case KC_ENTER:
909 				done = true;
910 				break;
911 
912 			case ARROW_LEFT:
913 				if (cursor > 0) cursor--;
914 				break;
915 
916 			case ARROW_RIGHT:
917 				if (cursor < ulen) cursor++;
918 				break;
919 
920 			case ARROW_DOWN: {
921 				int add = line_lengths[y] + 1;
922 				if (cursor + add < ulen) cursor += add;
923 				break;
924 			}
925 
926 			case ARROW_UP:
927 				if (y > 0) {
928 					int up = line_lengths[y - 1] + 1;
929 					if (cursor - up >= 0) cursor -= up;
930 				}
931 				break;
932 
933 			case KC_END:
934 				cursor = MAX(0, ulen);
935 				break;
936 
937 			case KC_HOME:
938 				cursor = 0;
939 				break;
940 
941 			case KC_BACKSPACE:
942 			case KC_DELETE: {
943 				char *ocurs, *oshift;
944 
945 				/* Refuse to backspace into oblivion */
946 				if ((ke.code == KC_BACKSPACE && cursor == 0) ||
947 						(ke.code == KC_DELETE && cursor >= ulen))
948 					break;
949 
950 				/*
951 				 * Move the string from k to nul along to the
952 				 * left by 1.  First, have to get offset
953 				 * corresponding to the cursor position.
954 				 */
955 				ocurs = utf8_fskip(buffer, cursor, NULL);
956 				assert(ocurs);
957 				if (ke.code == KC_BACKSPACE) {
958 					/* Get offset of the previous character. */
959 					oshift = utf8_rskip(ocurs, 1, buffer);
960 					assert(oshift);
961 					memmove(oshift, ocurs,
962 						len - (ocurs - buffer));
963 					/* Decrement */
964 					--cursor;
965 					len -= ocurs - oshift;
966 				} else {
967 					/* Get offset of the next character. */
968 					oshift = utf8_fskip(ocurs, 1, NULL);
969 					assert(oshift);
970 					memmove(ocurs, oshift,
971 						len - (oshift - buffer));
972 					/* Decrement. */
973 					len -= oshift - ocurs;
974 				}
975 
976 				/* Terminate */
977 				buffer[len] = '\0';
978 
979 				break;
980 			}
981 
982 			default: {
983 				bool atnull = (cursor == ulen);
984 				char encoded[5];
985 				int n_enc;
986 				char *ocurs;
987 
988 				if (!keycode_isprint(ke.code))
989 					break;
990 
991 				n_enc = utf32_to_utf8(encoded,
992 					N_ELEMENTS(encoded), &ke.code, 1, NULL);
993 
994 				/*
995 				 * Make sure we have something to add and have
996 				 * enough space.
997 				 */
998 				if (n_enc == 0 || n_enc + len >= buflen) {
999 					break;
1000 				}
1001 
1002 				/* Insert the encoded character. */
1003 				if (atnull) {
1004 					ocurs = buffer + len;
1005 				} else {
1006 					ocurs = utf8_fskip(buffer, cursor, NULL);
1007 					assert(ocurs);
1008 					/*
1009 					 * Move the rest of the buffer along
1010 					 * to make room.
1011 					 */
1012 					memmove(ocurs + n_enc, ocurs,
1013 						len - (ocurs - buffer));
1014 				}
1015 				memcpy(ocurs, encoded, n_enc);
1016 
1017 				/* Update cursor position and length. */
1018 				++cursor;
1019 				len += n_enc;
1020 
1021 				/* Terminate */
1022 				buffer[len] = '\0';
1023 
1024 				break;
1025 			}
1026 		}
1027 
1028 		mem_free(line_starts);
1029 		mem_free(line_lengths);
1030 		textblock_free(tb);
1031 	}
1032 
1033 	return 0;
1034 }
1035 
1036 /**
1037  * ------------------------------------------------------------------------
1038  * Allowing the player to choose their history.
1039  * ------------------------------------------------------------------------ */
get_history_command(void)1040 static enum birth_stage get_history_command(void)
1041 {
1042 	enum birth_stage next = 0;
1043 	struct keypress ke;
1044 	char old_history[240];
1045 
1046 	/* Save the original history */
1047 	my_strcpy(old_history, player->history, sizeof(old_history));
1048 
1049 	/* Ask for some history */
1050 	prt("Accept character history? [y/n]", 0, 0);
1051 	ke = inkey();
1052 
1053 	/* Quit, go back, change history, or accept */
1054 	if (ke.code == KTRL('X')) {
1055 		quit(NULL);
1056 	} else if (ke.code == ESCAPE) {
1057 		next = BIRTH_BACK;
1058 	} else if (ke.code == 'N' || ke.code == 'n') {
1059 		char history[240];
1060 		my_strcpy(history, player->history, sizeof(history));
1061 
1062 		switch (edit_text(history, sizeof(history))) {
1063 			case -1:
1064 				next = BIRTH_BACK;
1065 				break;
1066 			case 0:
1067 				cmdq_push(CMD_HISTORY_CHOICE);
1068 				cmd_set_arg_string(cmdq_peek(), "history", history);
1069 				next = BIRTH_HISTORY_CHOICE;
1070 		}
1071 	} else {
1072 		next = BIRTH_FINAL_CONFIRM;
1073 	}
1074 
1075 	return next;
1076 }
1077 
1078 /**
1079  * ------------------------------------------------------------------------
1080  * Final confirmation of character.
1081  * ------------------------------------------------------------------------ */
get_confirm_command(void)1082 static enum birth_stage get_confirm_command(void)
1083 {
1084 	const char *prompt = "['ESC' to step back, 'S' to start over, or any other key to continue]";
1085 	struct keypress ke;
1086 
1087 	enum birth_stage next = BIRTH_RESET;
1088 
1089 	/* Prompt for it */
1090 	prt(prompt, Term->hgt - 1, Term->wid / 2 - strlen(prompt) / 2);
1091 
1092 	/* Get a key */
1093 	ke = inkey();
1094 
1095 	/* Start over */
1096 	if (ke.code == 'S' || ke.code == 's') {
1097 		next = BIRTH_RESET;
1098 	} else if (ke.code == KTRL('X')) {
1099 		quit(NULL);
1100 	} else if (ke.code == ESCAPE) {
1101 		next = BIRTH_BACK;
1102 	} else {
1103 		cmdq_push(CMD_ACCEPT_CHARACTER);
1104 		next = BIRTH_COMPLETE;
1105 	}
1106 
1107 	/* Clear prompt */
1108 	clear_from(23);
1109 
1110 	return next;
1111 }
1112 
1113 
1114 
1115 /**
1116  * ------------------------------------------------------------------------
1117  * Things that relate to the world outside this file: receiving game events
1118  * and being asked for game commands.
1119  * ------------------------------------------------------------------------ */
1120 
1121 /**
1122  * This is called when we receive a request for a command in the birth
1123  * process.
1124 
1125  * The birth process continues until we send a final character confirmation
1126  * command (or quit), so this is effectively called in a loop by the main
1127  * game.
1128  *
1129  * We're imposing a step-based system onto the main game here, so we need
1130  * to keep track of where we're up to, where each step moves on to, etc.
1131  */
textui_do_birth(void)1132 int textui_do_birth(void)
1133 {
1134 	enum birth_stage current_stage = BIRTH_RESET;
1135 	enum birth_stage prev = BIRTH_BACK;
1136 	enum birth_stage roller = BIRTH_RESET;
1137 	enum birth_stage next = current_stage;
1138 
1139 	bool done = false;
1140 
1141 	cmdq_push(CMD_BIRTH_INIT);
1142 	cmdq_execute(CTX_BIRTH);
1143 
1144 	while (!done) {
1145 
1146 		switch (current_stage)
1147 		{
1148 			case BIRTH_RESET:
1149 			{
1150 				cmdq_push(CMD_BIRTH_RESET);
1151 
1152 				roller = BIRTH_RESET;
1153 
1154 				if (quickstart_allowed)
1155 					next = BIRTH_QUICKSTART;
1156 				else
1157 					next = BIRTH_RACE_CHOICE;
1158 
1159 				break;
1160 			}
1161 
1162 			case BIRTH_QUICKSTART:
1163 			{
1164 				display_player(0);
1165 				next = textui_birth_quickstart();
1166 				if (next == BIRTH_COMPLETE)
1167 					done = true;
1168 				break;
1169 			}
1170 
1171 			case BIRTH_CLASS_CHOICE:
1172 			case BIRTH_RACE_CHOICE:
1173 			case BIRTH_ROLLER_CHOICE:
1174 			{
1175 				struct menu *menu = &race_menu;
1176 				cmd_code command = CMD_CHOOSE_RACE;
1177 
1178 				Term_clear();
1179 				print_menu_instructions();
1180 
1181 				if (current_stage > BIRTH_RACE_CHOICE) {
1182 					menu_refresh(&race_menu, false);
1183 					menu = &class_menu;
1184 					command = CMD_CHOOSE_CLASS;
1185 				}
1186 
1187 				if (current_stage > BIRTH_CLASS_CHOICE) {
1188 					menu_refresh(&class_menu, false);
1189 					menu = &roller_menu;
1190 				}
1191 
1192 				next = menu_question(current_stage, menu, command);
1193 
1194 				if (next == BIRTH_BACK)
1195 					next = current_stage - 1;
1196 
1197 				/* Make sure the character gets reset before quickstarting */
1198 				if (next == BIRTH_QUICKSTART)
1199 					next = BIRTH_RESET;
1200 
1201 				break;
1202 			}
1203 
1204 			case BIRTH_POINTBASED:
1205 			{
1206 				roller = BIRTH_POINTBASED;
1207 
1208 				if (prev > BIRTH_POINTBASED)
1209 					point_based_start();
1210 
1211 				next = point_based_command();
1212 
1213 				if (next == BIRTH_BACK)
1214 					next = BIRTH_ROLLER_CHOICE;
1215 
1216 				if (next != BIRTH_POINTBASED)
1217 					point_based_stop();
1218 
1219 				break;
1220 			}
1221 
1222 			case BIRTH_ROLLER:
1223 			{
1224 				roller = BIRTH_ROLLER;
1225 				next = roller_command(prev < BIRTH_ROLLER);
1226 				if (next == BIRTH_BACK)
1227 					next = BIRTH_ROLLER_CHOICE;
1228 
1229 				break;
1230 			}
1231 
1232 			case BIRTH_NAME_CHOICE:
1233 			{
1234 				if (prev < BIRTH_NAME_CHOICE)
1235 					display_player(0);
1236 
1237 				next = get_name_command();
1238 				if (next == BIRTH_BACK)
1239 					next = roller;
1240 
1241 				break;
1242 			}
1243 
1244 			case BIRTH_HISTORY_CHOICE:
1245 			{
1246 				if (prev < BIRTH_HISTORY_CHOICE)
1247 					display_player(0);
1248 
1249 				next = get_history_command();
1250 				if (next == BIRTH_BACK)
1251 					next = BIRTH_NAME_CHOICE;
1252 
1253 				break;
1254 			}
1255 
1256 			case BIRTH_FINAL_CONFIRM:
1257 			{
1258 				if (prev < BIRTH_FINAL_CONFIRM)
1259 					display_player(0);
1260 
1261 				next = get_confirm_command();
1262 				if (next == BIRTH_BACK)
1263 					next = BIRTH_HISTORY_CHOICE;
1264 
1265 				if (next == BIRTH_COMPLETE)
1266 					done = true;
1267 
1268 				break;
1269 			}
1270 
1271 			default:
1272 			{
1273 				/* Remove dodgy compiler warning, */
1274 			}
1275 		}
1276 
1277 		prev = current_stage;
1278 		current_stage = next;
1279 
1280 		/* Execute whatever commands have been sent */
1281 		cmdq_execute(CTX_BIRTH);
1282 	}
1283 
1284 	return 0;
1285 }
1286 
1287 /**
1288  * Called when we enter the birth mode - so we set up handlers, command hooks,
1289  * etc, here.
1290  */
ui_enter_birthscreen(game_event_type type,game_event_data * data,void * user)1291 static void ui_enter_birthscreen(game_event_type type, game_event_data *data,
1292 								 void *user)
1293 {
1294 	/* Set the ugly static global that tells us if quickstart's available. */
1295 	quickstart_allowed = data->flag;
1296 
1297 	setup_menus();
1298 }
1299 
ui_leave_birthscreen(game_event_type type,game_event_data * data,void * user)1300 static void ui_leave_birthscreen(game_event_type type, game_event_data *data,
1301 								 void *user)
1302 {
1303 	/* Set the savefile name if it's not already set */
1304 	if (!savefile[0])
1305 		savefile_set_name(player->full_name, true, true);
1306 
1307 	free_birth_menus();
1308 }
1309 
1310 
ui_init_birthstate_handlers(void)1311 void ui_init_birthstate_handlers(void)
1312 {
1313 	event_add_handler(EVENT_ENTER_BIRTH, ui_enter_birthscreen, NULL);
1314 	event_add_handler(EVENT_LEAVE_BIRTH, ui_leave_birthscreen, NULL);
1315 }
1316 
1317