1 /**
2  * \file ui-context.c
3  * \brief Show player and terrain context menus.
4  *
5  * Copyright (c) 2011 Brett Reid
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 license":
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 "cave.h"
21 #include "cmd-core.h"
22 #include "cmds.h"
23 #include "game-input.h"
24 #include "mon-desc.h"
25 #include "mon-lore.h"
26 #include "mon-util.h"
27 #include "obj-chest.h"
28 #include "obj-desc.h"
29 #include "obj-gear.h"
30 #include "obj-ignore.h"
31 #include "obj-info.h"
32 #include "obj-tval.h"
33 #include "obj-util.h"
34 #include "player-calcs.h"
35 #include "player-timed.h"
36 #include "player-util.h"
37 #include "store.h"
38 #include "target.h"
39 #include "ui-context.h"
40 #include "ui-game.h"
41 #include "ui-input.h"
42 #include "ui-keymap.h"
43 #include "ui-knowledge.h"
44 #include "ui-menu.h"
45 #include "ui-mon-lore.h"
46 #include "ui-object.h"
47 #include "ui-player.h"
48 #include "ui-spell.h"
49 #include "ui-store.h"
50 #include "ui-target.h"
51 #include "wizard.h"
52 
53 #define ADD_LABEL(text, cmd, valid) { \
54 	cmdkey = cmd_lookup_key_unktrl((cmd), mode); \
55 	menu_dynamic_add_label_valid(m, (text), cmdkey, (cmd), labels, (valid)); \
56 }
57 
58 /**
59  * Additional constants for menu item values. The values must not collide
60  * with the cmd_code enum, since those are the main values for these menu items.
61  */
62 enum context_menu_value_e {
63     MENU_VALUE_INSPECT = CMD_REPEAT + 1000,
64     MENU_VALUE_DROP_ALL,
65 	MENU_VALUE_LOOK,
66 	MENU_VALUE_RECALL,
67 	MENU_VALUE_REST,
68 	MENU_VALUE_INVENTORY,
69 	MENU_VALUE_CENTER_MAP,
70 	MENU_VALUE_FLOOR,
71 	MENU_VALUE_CHARACTER,
72 	MENU_VALUE_OTHER,
73 	MENU_VALUE_KNOWLEDGE,
74 	MENU_VALUE_MAP,
75 	MENU_VALUE_MESSAGES,
76 	MENU_VALUE_OBJECTS,
77 	MENU_VALUE_MONSTERS,
78 	MENU_VALUE_TOGGLE_IGNORED,
79 	MENU_VALUE_OPTIONS,
80 	MENU_VALUE_HELP,
81 };
82 
83 
context_menu_player_2(int mx,int my)84 static int context_menu_player_2(int mx, int my)
85 {
86 	struct menu *m;
87 	int selected;
88 	char *labels;
89 	bool allowed = true;
90 	int mode = OPT(player, rogue_like_commands) ? KEYMAP_MODE_ROGUE : KEYMAP_MODE_ORIG;
91 	unsigned char cmdkey;
92 
93 	m = menu_dynamic_new();
94 	if (!m) {
95 		return 0;
96 	}
97 
98 	labels = string_make(lower_case);
99 	m->selections = labels;
100 
101 	menu_dynamic_add_label(m, "Knowledge", '~', MENU_VALUE_KNOWLEDGE, labels);
102 	menu_dynamic_add_label(m, "Show Map", 'M', MENU_VALUE_MAP, labels);
103 	menu_dynamic_add_label(m, "^Show Messages", 'P', MENU_VALUE_MESSAGES,
104 						   labels);
105 	menu_dynamic_add_label(m, "Show Monster List", '[', MENU_VALUE_MONSTERS,
106 						   labels);
107 	menu_dynamic_add_label(m, "Show Object List", ']', MENU_VALUE_OBJECTS,
108 						   labels);
109 
110 	/* Ignore toggle has different keys, but we don't have a way to look them
111 	 * up (see ui-game.c). */
112 	cmdkey = (mode == KEYMAP_MODE_ORIG) ? 'K' : 'O';
113 	menu_dynamic_add_label(m, "Toggle Ignored", cmdkey,
114 						   MENU_VALUE_TOGGLE_IGNORED, labels);
115 
116 	ADD_LABEL("Ignore an item", CMD_IGNORE, MN_ROW_VALID);
117 
118 	menu_dynamic_add_label(m, "Options", '=', MENU_VALUE_OPTIONS, labels);
119 	menu_dynamic_add_label(m, "Commands", '?', MENU_VALUE_HELP, labels);
120 
121 	/* Hack -- no flush needed */
122 	msg_flag = false;
123 	screen_save();
124 
125 	menu_dynamic_calc_location(m, mx, my);
126 	region_erase_bordered(&m->boundary);
127 
128 	prt("(Enter to select, ESC) Command:", 0, 0);
129 	selected = menu_dynamic_select(m);
130 
131 	menu_dynamic_free(m);
132 	string_free(labels);
133 
134 	screen_load();
135 
136 	/* Check the command to see if it is allowed. */
137 	switch (selected) {
138 		case -1:
139 			/* User cancelled the menu. */
140 			return 3;
141 
142 		case MENU_VALUE_KNOWLEDGE:
143 		case MENU_VALUE_MAP:
144 		case MENU_VALUE_MESSAGES:
145 		case MENU_VALUE_TOGGLE_IGNORED:
146 		case MENU_VALUE_HELP:
147 		case MENU_VALUE_MONSTERS:
148 		case MENU_VALUE_OBJECTS:
149 		case MENU_VALUE_OPTIONS:
150 			allowed = true;
151 			break;
152 
153 		case CMD_IGNORE:
154 			cmdkey = cmd_lookup_key(selected, mode);
155 			allowed = key_confirm_command(cmdkey);
156 			break;
157 
158 		default:
159 			/* Invalid command; prevent anything from happening. */
160 			bell("Invalid context menu command.");
161 			allowed = false;
162 			break;
163 	}
164 
165 	if (!allowed)
166 		return 1;
167 
168 	/* Perform the command. */
169 	switch (selected) {
170 		case MENU_VALUE_KNOWLEDGE:
171 			Term_keypress('~', 0);
172 			break;
173 
174 		case MENU_VALUE_MAP:
175 			Term_keypress('M', 0);
176 			break;
177 
178 		case MENU_VALUE_MESSAGES:
179 			Term_keypress(KTRL('p'), 0);
180 			break;
181 
182 		case CMD_IGNORE:
183 			cmdkey = cmd_lookup_key(selected, mode);
184 			Term_keypress(cmdkey, 0);
185 			break;
186 
187 		case MENU_VALUE_TOGGLE_IGNORED:
188 			/* Ignore toggle has different keys, but we don't have a way to
189 			 * look them up (see ui-game.c). */
190 			cmdkey = (mode == KEYMAP_MODE_ORIG) ? 'K' : 'O';
191 			Term_keypress(cmdkey, 0);
192 			break;
193 
194 		case MENU_VALUE_HELP:
195 			context_menu_command(mx, my);
196 			break;
197 
198 		case MENU_VALUE_MONSTERS:
199 			Term_keypress('[', 0);
200 			break;
201 
202 		case MENU_VALUE_OBJECTS:
203 			Term_keypress(']', 0);
204 			break;
205 
206 		case MENU_VALUE_OPTIONS:
207 			Term_keypress('=', 0);
208 			break;
209 
210 		default:
211 			break;
212 	}
213 
214 	return 1;
215 }
216 
context_menu_player_display_floor(void)217 static void context_menu_player_display_floor(void)
218 {
219 	int diff = weight_remaining(player);
220 	struct object *obj;
221 
222 	/* There is an item on the floor, select from there */
223 	player->upkeep->command_wrk = (USE_FLOOR);
224 
225 	/* Save screen */
226 	screen_save();
227 
228 	/* Prompt for a command */
229 	prt(format("(Inventory) Burden %d.%d lb (%d.%d lb %s). Item for command: ",
230 			   player->upkeep->total_weight / 10,
231 			   player->upkeep->total_weight % 10,
232 			   abs(diff) / 10, abs(diff) % 10,
233 			   (diff < 0 ? "overweight" : "remaining")), 0, 0);
234 
235 
236 	/* Get an item to use a context command on */
237 	if (get_item(&obj, NULL, NULL, CMD_NULL, NULL, USE_EQUIP | USE_INVEN | USE_QUIVER | USE_FLOOR | SHOW_EMPTY | IS_HARMLESS)) {
238 		/* Track the object kind */
239 		track_object(player->upkeep, obj);
240 
241 		context_menu_object(obj);
242 	}
243 
244 	/* Load screen */
245 	screen_load();
246 }
247 
context_menu_player(int mx,int my)248 int context_menu_player(int mx, int my)
249 {
250 	struct menu *m;
251 	int selected;
252 	char *labels;
253 	bool allowed = true;
254 	int mode = OPT(player, rogue_like_commands) ? KEYMAP_MODE_ROGUE : KEYMAP_MODE_ORIG;
255 	unsigned char cmdkey;
256 	struct object *obj;
257 
258 	m = menu_dynamic_new();
259 	if (!m) {
260 		return 0;
261 	}
262 
263 	labels = string_make(lower_case);
264 	m->selections = labels;
265 
266 	ADD_LABEL("Use", CMD_USE, MN_ROW_VALID);
267 
268 	/* if player can cast, add casting option */
269 	if (player_can_cast(player, false)) {
270 		ADD_LABEL("Cast", CMD_CAST, MN_ROW_VALID);
271 	}
272 
273 	/* if player is on stairs add option to use them */
274 	if (square_isupstairs(cave, player->grid)) {
275 		ADD_LABEL("Go Up", CMD_GO_UP, MN_ROW_VALID);
276 	}
277 	else if (square_isdownstairs(cave, player->grid)) {
278 		ADD_LABEL("Go Down", CMD_GO_DOWN, MN_ROW_VALID);
279 	}
280 
281 	/* Looking has different keys, but we don't have a way to look them up
282 	 * (see ui-game.c). */
283 	cmdkey = (mode == KEYMAP_MODE_ORIG) ? 'l' : 'x';
284 	menu_dynamic_add_label(m, "Look", cmdkey, MENU_VALUE_LOOK, labels);
285 
286 	/* 'R' is used for resting in both keymaps. */
287 	menu_dynamic_add_label(m, "Rest", 'R', MENU_VALUE_REST, labels);
288 
289 	/* 'i' is used for inventory in both keymaps. */
290 	menu_dynamic_add_label(m, "Inventory", 'i', MENU_VALUE_INVENTORY, labels);
291 
292 	/* if object under player add pickup option */
293 	obj = square_object(cave, player->grid);
294 	if (obj && !ignore_item_ok(obj)) {
295 			menu_row_validity_t valid;
296 
297 			/* 'f' isn't in rogue keymap, so we can use it here. */
298   			menu_dynamic_add_label(m, "Floor", 'f', MENU_VALUE_FLOOR, labels);
299 			valid = (inven_carry_okay(obj)) ? MN_ROW_VALID : MN_ROW_INVALID;
300 			ADD_LABEL("Pick up", CMD_PICKUP, valid);
301 	}
302 
303 	/* 'C' is used for the character sheet in both keymaps. */
304 	menu_dynamic_add_label(m, "Character", 'C', MENU_VALUE_CHARACTER, labels);
305 
306 	if (!OPT(player, center_player)) {
307 		menu_dynamic_add_label(m, "^Center Map", 'L', MENU_VALUE_CENTER_MAP,
308 							   labels);
309 	}
310 
311 	menu_dynamic_add_label(m, "Other", ' ', MENU_VALUE_OTHER, labels);
312 
313 	/* Hack -- no flush needed */
314 	msg_flag = false;
315 	screen_save();
316 
317 	menu_dynamic_calc_location(m, mx, my);
318 	region_erase_bordered(&m->boundary);
319 
320 	prt("(Enter to select, ESC) Command:", 0, 0);
321 	selected = menu_dynamic_select(m);
322 
323 	menu_dynamic_free(m);
324 	string_free(labels);
325 
326 	screen_load();
327 
328 	cmdkey = cmd_lookup_key(selected, mode);
329 
330 	/* Check the command to see if it is allowed. */
331 	switch(selected) {
332 		case -1:
333 			/* User cancelled the menu. */
334 			return 3;
335 
336 		case CMD_USE:
337 		case CMD_CAST:
338 		case CMD_GO_UP:
339 		case CMD_GO_DOWN:
340 		case CMD_PICKUP:
341 			/* Only check for ^ inscriptions, since we don't have an object
342 			 * selected (if we need one). */
343 			allowed = key_confirm_command(cmdkey);
344 			break;
345 
346 		case MENU_VALUE_REST:
347 			allowed = key_confirm_command('R');
348 			break;
349 
350 		case MENU_VALUE_INVENTORY:
351 		case MENU_VALUE_LOOK:
352 		case MENU_VALUE_CHARACTER:
353 		case MENU_VALUE_OTHER:
354 		case MENU_VALUE_FLOOR:
355 		case MENU_VALUE_CENTER_MAP:
356 			allowed = true;
357 			break;
358 
359 		default:
360 			/* Invalid command; prevent anything from happening. */
361 			bell("Invalid context menu command.");
362 			allowed = false;
363 			break;
364 	}
365 
366 	if (!allowed)
367 		return 1;
368 
369 	/* Perform the command. */
370 	switch(selected) {
371 		case CMD_USE:
372 		case CMD_CAST:
373 			cmdkey = cmd_lookup_key(selected, mode);
374 			Term_keypress(cmdkey, 0);
375 			break;
376 
377 		case CMD_GO_UP:
378 		case CMD_GO_DOWN:
379 		case CMD_PICKUP:
380 			cmdq_push(selected);
381 			break;
382 
383 		case MENU_VALUE_REST:
384 			Term_keypress('R', 0);
385 			break;
386 
387 		case MENU_VALUE_INVENTORY:
388 			Term_keypress('i', 0);
389 			break;
390 
391 		case MENU_VALUE_LOOK:
392 			if (target_set_interactive(TARGET_LOOK, player->grid.x, player->grid.y))
393 				msg("Target Selected.");
394 			break;
395 
396 		case MENU_VALUE_CHARACTER:
397 			Term_keypress('C', 0);
398 			break;
399 
400 		case MENU_VALUE_OTHER:
401 			context_menu_player_2(mx, my);
402 			break;
403 
404 		case MENU_VALUE_FLOOR:
405 			context_menu_player_display_floor();
406 			break;
407 
408 		case MENU_VALUE_CENTER_MAP:
409 			do_cmd_center_map();
410 			break;
411 
412 		default:
413 			break;
414 	}
415 
416 	return 1;
417 }
418 
context_menu_cave(struct chunk * c,int y,int x,int adjacent,int mx,int my)419 int context_menu_cave(struct chunk *c, int y, int x, int adjacent, int mx,
420 					  int my)
421 {
422 	struct menu *m;
423 	int selected;
424 	char *labels;
425 	bool allowed = true;
426 	int mode = OPT(player, rogue_like_commands) ? KEYMAP_MODE_ROGUE : KEYMAP_MODE_ORIG;
427 	unsigned char cmdkey;
428 	struct loc grid = loc(x, y);
429 	struct object *square_obj = square_object(c, grid);
430 
431 	m = menu_dynamic_new();
432 	if (!m)
433 		return 0;
434 
435 	labels = string_make(lower_case);
436 	m->selections = labels;
437 
438 	/* Looking has different keys, but we don't have a way to look them up
439 	 * (see ui-game.c). */
440 	cmdkey = (mode == KEYMAP_MODE_ORIG) ? 'l' : 'x';
441 	menu_dynamic_add_label(m, "Look At", cmdkey, MENU_VALUE_LOOK, labels);
442 
443 	if (square(c, grid)->mon)
444 		/* '/' is used for recall in both keymaps. */
445 		menu_dynamic_add_label(m, "Recall Info", '/', MENU_VALUE_RECALL,
446 							   labels);
447 
448 	ADD_LABEL("Use Item On", CMD_USE, MN_ROW_VALID);
449 
450 	if (player_can_cast(player, false))
451 		ADD_LABEL("Cast On", CMD_CAST, MN_ROW_VALID);
452 
453 	if (adjacent) {
454 		struct object *obj = chest_check(grid, CHEST_ANY);
455 		ADD_LABEL((square(c, grid)->mon) ? "Attack" : "Alter", CMD_ALTER,
456 				  MN_ROW_VALID);
457 
458 		if (obj && !ignore_item_ok(obj)) {
459 			if (obj->known->pval) {
460 				if (is_locked_chest(obj)) {
461 					ADD_LABEL("Disarm Chest", CMD_DISARM, MN_ROW_VALID);
462 					ADD_LABEL("Open Chest", CMD_OPEN, MN_ROW_VALID);
463 				} else {
464 					ADD_LABEL("Open Disarmed Chest", CMD_OPEN, MN_ROW_VALID);
465 				}
466 			} else {
467 				ADD_LABEL("Open Chest", CMD_OPEN, MN_ROW_VALID);
468 			}
469 		}
470 
471 		if ((square(cave, grid)->mon > 0) && player_has(player, PF_STEAL)) {
472 			ADD_LABEL("Steal", CMD_STEAL, MN_ROW_VALID);
473 		}
474 
475 		if (square_isdisarmabletrap(c, grid)) {
476 			ADD_LABEL("Disarm", CMD_DISARM, MN_ROW_VALID);
477 			ADD_LABEL("Jump Onto", CMD_JUMP, MN_ROW_VALID);
478 		}
479 
480 		if (square_isopendoor(c, grid)) {
481 			ADD_LABEL("Close", CMD_CLOSE, MN_ROW_VALID);
482 		}
483 		else if (square_iscloseddoor(c, grid)) {
484 			ADD_LABEL("Open", CMD_OPEN, MN_ROW_VALID);
485 			ADD_LABEL("Lock", CMD_DISARM, MN_ROW_VALID);
486 		}
487 		else if (square_isdiggable(c, grid)) {
488 			ADD_LABEL("Tunnel", CMD_TUNNEL, MN_ROW_VALID);
489 		}
490 
491 		ADD_LABEL("Walk Towards", CMD_WALK, MN_ROW_VALID);
492 	} else {
493 		/* ',' is used for ignore in rogue keymap, so we'll just swap letters */
494 		cmdkey = (mode == KEYMAP_MODE_ORIG) ? ',' : '.';
495 		menu_dynamic_add_label(m, "Pathfind To", cmdkey, CMD_PATHFIND, labels);
496 
497 		ADD_LABEL("Walk Towards", CMD_WALK, MN_ROW_VALID);
498 		ADD_LABEL("Run Towards", CMD_RUN, MN_ROW_VALID);
499 	}
500 
501 	if (player_can_fire(player, false)) {
502 		ADD_LABEL("Fire On", CMD_FIRE, MN_ROW_VALID);
503 	}
504 
505 	ADD_LABEL("Throw To", CMD_THROW, MN_ROW_VALID);
506 
507 	/* Hack -- no flush needed */
508 	msg_flag = false;
509 	screen_save();
510 
511 	menu_dynamic_calc_location(m, mx, my);
512 	region_erase_bordered(&m->boundary);
513 
514 	if (player->timed[TMD_IMAGE]) {
515 		prt("(Enter to select command, ESC to cancel) You see something strange:", 0, 0);
516 	} else if (square(c, grid)->mon) {
517 		char m_name[80];
518 		struct monster *mon = square_monster(c, grid);
519 
520 		/* Get the monster name ("a kobold") */
521 		monster_desc(m_name, sizeof(m_name), mon, MDESC_IND_VIS);
522 
523 		prt(format("(Enter to select command, ESC to cancel) You see %s:",
524 				   m_name), 0, 0);
525 	} else if (square_obj && !ignore_item_ok(square_obj)) {
526 		char o_name[80];
527 
528 		/* Obtain an object description */
529 		object_desc(o_name, sizeof (o_name), square_obj,
530 					ODESC_PREFIX | ODESC_FULL);
531 
532 		prt(format("(Enter to select command, ESC to cancel) You see %s:",
533 				   o_name), 0, 0);
534 	} else {
535 		/* Feature (apply mimic) */
536 		const char *name = square_apparent_name(c, player, grid);
537 		const char *prefix = square_apparent_look_prefix(c, player, grid);
538 
539 		prt(format("(Enter to select command, ESC to cancel) You see %s%s:", prefix, name), 0, 0);
540 	}
541 
542 	selected = menu_dynamic_select(m);
543 
544 	menu_dynamic_free(m);
545 	string_free(labels);
546 
547 	screen_load();
548 
549 	cmdkey = cmd_lookup_key(selected, mode);
550 
551 	/* Check the command to see if it is allowed. */
552 	switch (selected) {
553 		case -1:
554 			/* User cancelled the menu. */
555 			return 3;
556 
557 		case MENU_VALUE_LOOK:
558 		case MENU_VALUE_RECALL:
559 		case CMD_PATHFIND:
560 			allowed = true;
561 			break;
562 
563 		case CMD_ALTER:
564 		case CMD_STEAL:
565 		case CMD_DISARM:
566 		case CMD_JUMP:
567 		case CMD_CLOSE:
568 		case CMD_OPEN:
569 		case CMD_TUNNEL:
570 		case CMD_WALK:
571 		case CMD_RUN:
572 		case CMD_CAST:
573 		case CMD_FIRE:
574 		case CMD_THROW:
575 		case CMD_USE:
576 			/* Only check for ^ inscriptions, since we don't have an object
577 			 * selected (if we need one). */
578 			allowed = key_confirm_command(cmdkey);
579 			break;
580 
581 		default:
582 			/* Invalid command; prevent anything from happening. */
583 			bell("Invalid context menu command.");
584 			allowed = false;
585 			break;
586 	}
587 
588 	if (!allowed)
589 		return 1;
590 
591 	/* Perform the command. */
592 	switch (selected) {
593 		case MENU_VALUE_LOOK:
594 			/* Look at the spot */
595 			if (target_set_interactive(TARGET_LOOK, x, y)) {
596 				msg("Target Selected.");
597 			}
598 			break;
599 
600 		case MENU_VALUE_RECALL: {
601 			/* Recall monster Info */
602 			struct monster *mon = square_monster(c, grid);
603 			if (mon) {
604 				struct monster_lore *lore = get_lore(mon->race);
605 				lore_show_interactive(mon->race, lore);
606 			}
607 		}
608 			break;
609 
610 		case CMD_PATHFIND:
611 			cmdq_push(selected);
612 			cmd_set_arg_point(cmdq_peek(), "point", loc(x, y));
613 			break;
614 
615 		case CMD_ALTER:
616 		case CMD_STEAL:
617 		case CMD_DISARM:
618 		case CMD_JUMP:
619 		case CMD_CLOSE:
620 		case CMD_OPEN:
621 		case CMD_TUNNEL:
622 		case CMD_WALK:
623 		case CMD_RUN:
624 			cmdq_push(selected);
625 			cmd_set_arg_direction(cmdq_peek(), "direction",
626 								  motion_dir(player->grid, loc(x, y)));
627 			break;
628 
629 		case CMD_CAST:
630 		case CMD_FIRE:
631 		case CMD_THROW:
632 		case CMD_USE:
633 			cmdq_push(selected);
634 			cmd_set_arg_target(cmdq_peek(), "target", DIR_TARGET);
635 			break;
636 
637 		default:
638 			break;
639 	}
640 
641 	return 1;
642 }
643 
644 /**
645  * Pick the context menu options appropiate for the item
646  */
context_menu_object(struct object * obj)647 int context_menu_object(struct object *obj)
648 {
649 	struct menu *m;
650 	region r;
651 	int selected;
652 	char *labels;
653 	char header[120];
654 
655 	textblock *tb;
656 	region area = { 0, 0, 0, 0 };
657 
658 	bool allowed = true;
659 	int mode = OPT(player, rogue_like_commands) ? KEYMAP_MODE_ROGUE : KEYMAP_MODE_ORIG;
660 	unsigned char cmdkey;
661 
662 	m = menu_dynamic_new();
663 	if (!m || !obj)
664 		return 0;
665 
666 	object_desc(header, sizeof(header), obj, ODESC_PREFIX | ODESC_BASE);
667 
668 	labels = string_make(lower_case);
669 	m->selections = labels;
670 
671 	/* 'I' is used for inspect in both keymaps. */
672 	menu_dynamic_add_label(m, "Inspect", 'I', MENU_VALUE_INSPECT, labels);
673 
674 	if (obj_can_browse(obj)) {
675 		if (obj_can_cast_from(obj) && player_can_cast(player, false))
676 			ADD_LABEL("Cast", CMD_CAST, MN_ROW_VALID);
677 
678 		if (obj_can_study(obj) && player_can_study(player, false))
679 			ADD_LABEL("Study", CMD_STUDY, MN_ROW_VALID);
680 
681 		if (player_can_read(player, false))
682 			ADD_LABEL("Browse", CMD_BROWSE_SPELL, MN_ROW_VALID);
683 	} else if (obj_is_useable(obj)) {
684 		if (tval_is_wand(obj)) {
685 			menu_row_validity_t valid = (obj_has_charges(obj)) ?
686 				MN_ROW_VALID : MN_ROW_INVALID;
687 			ADD_LABEL("Aim", CMD_USE_WAND, valid);
688 		} else if (tval_is_rod(obj)) {
689 			menu_row_validity_t valid = (obj_can_zap(obj)) ?
690 				MN_ROW_VALID : MN_ROW_INVALID;
691 			ADD_LABEL("Zap", CMD_USE_ROD, valid);
692 		} else if (tval_is_staff(obj)) {
693 			menu_row_validity_t valid = (obj_has_charges(obj)) ?
694 				MN_ROW_VALID : MN_ROW_INVALID;
695 			ADD_LABEL("Use", CMD_USE_STAFF, valid);
696 		} else if (tval_is_scroll(obj)) {
697 			menu_row_validity_t valid = (player_can_read(player, false)) ?
698 				MN_ROW_VALID : MN_ROW_INVALID;
699 			ADD_LABEL("Read", CMD_READ_SCROLL, valid);
700 		} else if (tval_is_potion(obj)) {
701 			ADD_LABEL("Quaff", CMD_QUAFF, MN_ROW_VALID);
702 		} else if (tval_is_edible(obj)) {
703 			ADD_LABEL("Eat", CMD_EAT, MN_ROW_VALID);
704 		} else if (obj_is_activatable(obj)) {
705 			menu_row_validity_t valid = (object_is_equipped(player->body, obj)
706 										 && obj_can_activate(obj)) ?
707 				MN_ROW_VALID : MN_ROW_INVALID;
708 			ADD_LABEL("Activate", CMD_ACTIVATE, valid);
709 		} else if (obj_can_fire(obj)) {
710 			ADD_LABEL("Fire", CMD_FIRE, MN_ROW_VALID);
711 		} else {
712 			ADD_LABEL("Use", CMD_USE, MN_ROW_VALID);
713 		}
714 	}
715 
716 	if (obj_can_refill(obj))
717 		ADD_LABEL("Refill", CMD_REFILL, MN_ROW_VALID);
718 
719 	if (object_is_equipped(player->body, obj) && obj_can_takeoff(obj)) {
720 		ADD_LABEL("Take off", CMD_TAKEOFF, MN_ROW_VALID);
721 	} else if (!object_is_equipped(player->body, obj) && obj_can_wear(obj)) {
722 		ADD_LABEL("Equip", CMD_WIELD, MN_ROW_VALID);
723 	}
724 
725 	if (object_is_carried(player, obj)) {
726 		if (!square_isshop(cave, player->grid)) {
727 			ADD_LABEL("Drop", CMD_DROP, MN_ROW_VALID);
728 
729 			if (obj->number > 1) {
730 				/* 'D' is used for ignore in rogue keymap, so swap letters. */
731 				cmdkey = (mode == KEYMAP_MODE_ORIG) ? 'D' : 'k';
732 				menu_dynamic_add_label(m, "Drop All", cmdkey,
733 									   MENU_VALUE_DROP_ALL, labels);
734 			}
735 		} else if (square_shopnum(cave, player->grid) == STORE_HOME) {
736 			ADD_LABEL("Drop", CMD_DROP, MN_ROW_VALID);
737 
738 			if (obj->number > 1) {
739 				/* 'D' is used for ignore in rogue keymap, so swap letters. */
740 				cmdkey = (mode == KEYMAP_MODE_ORIG) ? 'D' : 'k';
741 				menu_dynamic_add_label(m, "Drop All", cmdkey,
742 									   MENU_VALUE_DROP_ALL, labels);
743 			}
744 		} else if (store_will_buy_tester(obj)) {
745 			ADD_LABEL("Sell", CMD_DROP, MN_ROW_VALID);
746 		}
747 	} else {
748 		menu_row_validity_t valid = (inven_carry_okay(obj)) ?
749 			MN_ROW_VALID : MN_ROW_INVALID;
750 		ADD_LABEL("Pick up", CMD_PICKUP, valid);
751 	}
752 
753 	ADD_LABEL("Throw", CMD_THROW, MN_ROW_VALID);
754 	ADD_LABEL("Inscribe", CMD_INSCRIBE, MN_ROW_VALID);
755 
756 	if (obj_has_inscrip(obj))
757 		ADD_LABEL("Uninscribe", CMD_UNINSCRIBE, MN_ROW_VALID);
758 
759 	ADD_LABEL( (object_is_ignored(obj) ? "Unignore" : "Ignore"), CMD_IGNORE,
760 			   MN_ROW_VALID);
761 
762 	/* work out display region */
763 	r.width = (int)menu_dynamic_longest_entry(m) + 3 + 2; /* +3 for tag,
764 														   * 2 for pad */
765 	r.col = Term->wid - r.width - 1;
766 	r.row = 1;
767 	r.page_rows = m->count;
768 
769 	area.width = -(r.width + 2);
770 
771 	/* Hack -- no flush needed */
772 	msg_flag = false;
773 	screen_save();
774 
775 	/* Display info */
776 	tb = object_info(obj, OINFO_NONE);
777 	object_desc(header, sizeof(header), obj, ODESC_PREFIX | ODESC_FULL);
778 
779 	textui_textblock_place(tb, area, format("%s", header));
780 	textblock_free(tb);
781 
782 	menu_layout(m, &r);
783 	region_erase_bordered(&r);
784 
785 	prt(format("(Enter to select, ESC) Command for %s:", header), 0, 0);
786 	selected = menu_dynamic_select(m);
787 
788 	menu_dynamic_free(m);
789 	string_free(labels);
790 
791 	screen_load();
792 
793 	cmdkey = cmd_lookup_key(selected, mode);
794 
795 	switch (selected) {
796 		case -1:
797 			/* User cancelled the menu. */
798 			return 3;
799 
800 		case MENU_VALUE_INSPECT:
801 			/* copied from textui_obj_examine */
802 			/* Display info */
803 			tb = object_info(obj, OINFO_NONE);
804 			object_desc(header, sizeof(header), obj, ODESC_PREFIX | ODESC_FULL);
805 
806 			textui_textblock_show(tb, area, format("%s", header));
807 			textblock_free(tb);
808 			return 2;
809 
810 		case MENU_VALUE_DROP_ALL:
811 			/* Drop entire stack without confirmation. */
812 			if (square_isshop(cave, player->grid))
813 				cmdq_push(CMD_STASH);
814 			else
815 				cmdq_push(CMD_DROP);
816 			cmd_set_arg_item(cmdq_peek(), "item", obj);
817 			cmd_set_arg_number(cmdq_peek(), "quantity", obj->number);
818 			return 1;
819 
820 		case CMD_BROWSE_SPELL:
821 		case CMD_STUDY:
822 		case CMD_CAST:
823 		case CMD_IGNORE:
824 		case CMD_WIELD:
825 		case CMD_TAKEOFF:
826 		case CMD_INSCRIBE:
827 		case CMD_UNINSCRIBE:
828 		case CMD_PICKUP:
829 		case CMD_DROP:
830 		case CMD_REFILL:
831 		case CMD_THROW:
832 		case CMD_USE_WAND:
833 		case CMD_USE_ROD:
834 		case CMD_USE_STAFF:
835 		case CMD_READ_SCROLL:
836 		case CMD_QUAFF:
837 		case CMD_EAT:
838 		case CMD_ACTIVATE:
839 		case CMD_FIRE:
840 		case CMD_USE:
841 			/* Check for inscriptions that trigger confirmation. */
842 			allowed = key_confirm_command(cmdkey) &&
843 				get_item_allow(obj, cmdkey, selected, false);
844 			break;
845 		default:
846 			/* Invalid command; prevent anything from happening. */
847 			bell("Invalid context menu command.");
848 			allowed = false;
849 			break;
850 	}
851 
852 	if (!allowed)
853 		return 1;
854 
855 	if (selected == CMD_IGNORE) {
856 		/* ignore or unignore the item */
857 		textui_cmd_ignore_menu(obj);
858 	} else if (selected == CMD_BROWSE_SPELL) {
859 		/* browse a spellbook */
860 		/* copied from textui_spell_browse */
861 		textui_book_browse(obj);
862 		return 2;
863 	} else if (selected == CMD_STUDY) {
864 		cmdq_push(CMD_STUDY);
865 		cmd_set_arg_item(cmdq_peek(), "item", obj);
866 	} else if (selected == CMD_CAST) {
867 		if (obj_can_cast_from(obj)) {
868 			cmdq_push(CMD_CAST);
869 			cmd_set_arg_item(cmdq_peek(), "book", obj);
870 		}
871 	} else {
872 		cmdq_push(selected);
873 		cmd_set_arg_item(cmdq_peek(), "item", obj);
874 
875 		/* If we're in a store, change the "drop" command to "stash". */
876 		if (selected == CMD_DROP &&
877 			square_isshop(cave, player->grid)) {
878 			struct command *gc = cmdq_peek();
879 			if (square_shopnum(cave, player->grid) == STORE_HOME)
880 				gc->code = CMD_STASH;
881 			else
882 				gc->code = CMD_SELL;
883 		}
884 	}
885 
886 	return 1;
887 }
888 
889 
890 
show_command_list(struct cmd_info cmd_list[],int size,int mx,int my)891 static int show_command_list(struct cmd_info cmd_list[], int size, int mx,
892                              int my)
893 {
894 	struct menu *m;
895 	int selected;
896 	int i;
897 	char cmd_name[80];
898 	char key[3];
899 
900 	int mode = OPT(player, rogue_like_commands) ? KEYMAP_MODE_ROGUE : KEYMAP_MODE_ORIG;
901 
902 	m = menu_dynamic_new();
903 	if (!m) {
904 		return 0;
905 	}
906 	m->selections = lower_case;
907 	key[2] = '\0';
908 
909 	for (i = 0; i < size; ++i) {
910 		if (KTRL(cmd_list[i].key[mode]) == cmd_list[i].key[mode]) {
911 			key[0] = '^';
912 			key[1] = UN_KTRL(cmd_list[i].key[mode]);
913 		} else {
914 			key[0] = cmd_list[i].key[mode];
915 			key[1] = '\0';
916 		}
917 		strnfmt(cmd_name, 80, "%s (%s)",  cmd_list[i].desc, key);
918 		menu_dynamic_add(m, cmd_name, i+1);
919 	}
920 
921 	menu_dynamic_calc_location(m, mx, my);
922 
923 	screen_save();
924 	region_erase_bordered(&m->boundary);
925 
926 	prt("(Enter to select, ESC) Command:", 0, 0);
927 	selected = menu_dynamic_select(m);
928 	menu_dynamic_free(m);
929 
930 	screen_load();
931 
932 	if ((selected > 0) && (selected < size+1)) {
933 		/* execute the command */
934 		Term_keypress(cmd_list[selected-1].key[mode], 0);
935 	}
936 
937 	return 1;
938 }
939 
context_menu_command(int mx,int my)940 int context_menu_command(int mx, int my)
941 {
942 	struct menu *m;
943 	int selected;
944 
945 	m = menu_dynamic_new();
946 	if (!m) {
947 		return 0;
948 	}
949 
950 	m->selections = lower_case;
951 	menu_dynamic_add(m, "Item", 1);
952 	menu_dynamic_add(m, "Action", 2);
953 	menu_dynamic_add(m, "Item Management", 3);
954 	menu_dynamic_add(m, "Info", 4);
955 	menu_dynamic_add(m, "Util", 5);
956 	menu_dynamic_add(m, "Misc", 6);
957 
958 	menu_dynamic_calc_location(m, mx, my);
959 
960 	screen_save();
961 	region_erase_bordered(&m->boundary);
962 
963 	prt("(Enter to select, ESC) Command:", 0, 0);
964 	selected = menu_dynamic_select(m);
965 	menu_dynamic_free(m);
966 
967 	screen_load();
968 
969 	/* XXX-AS this is gross, as is the way there's two ways to display the
970 	 * entire command list.  Fix me */
971 	if (selected > 0) {
972 		selected--;
973 		show_command_list(cmds_all[selected].list, cmds_all[selected].len,
974 						  mx, my);
975 	} else {
976 		return 0;
977 	}
978 
979 	return 1;
980 }
981 
982 /**
983  * Handle a textui mouseclick.
984  */
textui_process_click(ui_event e)985 void textui_process_click(ui_event e)
986 {
987 	int x, y;
988 
989 	if (!OPT(player, mouse_movement)) return;
990 
991 	y = KEY_GRID_Y(e);
992 	x = KEY_GRID_X(e);
993 
994 	/* Check for a valid location */
995 	if (!square_in_bounds_fully(cave, loc(x, y))) return;
996 
997 	/* XXX show context menu here */
998 	if (loc_eq(player->grid, loc(x, y))) {
999 		if (e.mouse.mods & KC_MOD_SHIFT) {
1000 			/* shift-click - cast magic */
1001 			if (e.mouse.button == 1) {
1002 				cmdq_push(CMD_CAST);
1003 			} else if (e.mouse.button == 2) {
1004 				Term_keypress('i',0);
1005 			}
1006 		} else if (e.mouse.mods & KC_MOD_CONTROL) {
1007 			/* ctrl-click - use feature / use inventory item */
1008 			/* switch with default */
1009 			if (e.mouse.button == 1) {
1010 				if (square_isupstairs(cave, player->grid))
1011 					cmdq_push(CMD_GO_UP);
1012 				else if (square_isdownstairs(cave, player->grid))
1013 					cmdq_push(CMD_GO_DOWN);
1014 			} else if (e.mouse.button == 2) {
1015 				cmdq_push(CMD_USE);
1016 			}
1017 		} else if (e.mouse.mods & KC_MOD_ALT) {
1018 			/* alt-click - show char screen */
1019 			/* XXX call a platform specific hook */
1020 			if (e.mouse.button == 1) {
1021 				Term_keypress('C',0);
1022 			}
1023 		} else {
1024 			if (e.mouse.button == 1) {
1025 				if (square_object(cave, loc(x, y))) {
1026 					cmdq_push(CMD_PICKUP);
1027 				} else {
1028 					cmdq_push(CMD_HOLD);
1029 				}
1030 			} else if (e.mouse.button == 2) {
1031 				/* Show a context menu */
1032 				context_menu_player(e.mouse.x, e.mouse.y);
1033 			}
1034 		}
1035 	} else if (e.mouse.button == 1) {
1036 		if (player->timed[TMD_CONFUSED]) {
1037 			cmdq_push(CMD_WALK);
1038 		} else {
1039 			if (e.mouse.mods & KC_MOD_SHIFT) {
1040 				/* shift-click - run */
1041 				cmdq_push(CMD_RUN);
1042 				cmd_set_arg_direction(cmdq_peek(), "direction",
1043 									  motion_dir(player->grid, loc(x, y)));
1044 			} else if (e.mouse.mods & KC_MOD_CONTROL) {
1045 				/* control-click - alter */
1046 				cmdq_push(CMD_ALTER);
1047 				cmd_set_arg_direction(cmdq_peek(), "direction",
1048 									  motion_dir(player->grid, loc(x, y)));
1049 			} else if (e.mouse.mods & KC_MOD_ALT) {
1050 				/* alt-click - look */
1051 				if (target_set_interactive(TARGET_LOOK, x, y)) {
1052 					msg("Target Selected.");
1053 				}
1054 			} else {
1055 				/* Pathfind does not work well on trap detection borders,
1056 				 * so if the click is next to the player, force a walk step */
1057 				if ((y - player->grid.y >= -1) && (y - player->grid.y <= 1)	&&
1058 					(x - player->grid.x >= -1) && (x - player->grid.x <= 1)) {
1059 					cmdq_push(CMD_WALK);
1060 					cmd_set_arg_direction(cmdq_peek(), "direction",
1061 										  motion_dir(player->grid, loc(x, y)));
1062 				} else {
1063 					cmdq_push(CMD_PATHFIND);
1064 					cmd_set_arg_point(cmdq_peek(), "point", loc(x, y));
1065 				}
1066 			}
1067 		}
1068 	} else if (e.mouse.button == 2) {
1069 		struct monster *m = square_monster(cave, loc(x, y));
1070 		if (m && target_able(m)) {
1071 			/* Set up target information */
1072 			monster_race_track(player->upkeep, m->race);
1073 			health_track(player->upkeep, m);
1074 			target_set_monster(m);
1075 		} else {
1076 			target_set_location(y, x);
1077 		}
1078 
1079 		if (e.mouse.mods & KC_MOD_SHIFT) {
1080 			/* shift-click - cast spell at target */
1081 			cmdq_push(CMD_CAST);
1082 			cmd_set_arg_target(cmdq_peek(), "target", DIR_TARGET);
1083 		} else if (e.mouse.mods & KC_MOD_CONTROL) {
1084 			/* control-click - fire at target */
1085 			cmdq_push(CMD_USE);
1086 			cmd_set_arg_target(cmdq_peek(), "target", DIR_TARGET);
1087 		} else if (e.mouse.mods & KC_MOD_ALT) {
1088 			/* alt-click - throw at target */
1089 			cmdq_push(CMD_THROW);
1090 			cmd_set_arg_target(cmdq_peek(), "target", DIR_TARGET);
1091 		} else {
1092 			/* see if the click was adjacent to the player */
1093 			if ((y - player->grid.y >= -1) && (y - player->grid.y <= 1)	&&
1094 				(x - player->grid.x >= -1) && (x - player->grid.x <= 1)) {
1095 				context_menu_cave(cave,y,x,1,e.mouse.x, e.mouse.y);
1096 			} else {
1097 				context_menu_cave(cave,y,x,0,e.mouse.x, e.mouse.y);
1098 			}
1099 		}
1100 	}
1101 }
1102 
1103 
1104 
1105 
1106 /**
1107  * ------------------------------------------------------------------------
1108  * Menu functions
1109  * ------------------------------------------------------------------------ */
1110 
1111 /**
1112  * Display an entry on a command menu
1113  */
cmd_sub_entry(struct menu * menu,int oid,bool cursor,int row,int col,int width)1114 static void cmd_sub_entry(struct menu *menu, int oid, bool cursor, int row,
1115 						  int col, int width)
1116 {
1117 	byte attr = (cursor ? COLOUR_L_BLUE : COLOUR_WHITE);
1118 	const struct cmd_info *commands = menu_priv(menu);
1119 
1120 	int mode = OPT(player, rogue_like_commands) ? KEYMAP_MODE_ROGUE : KEYMAP_MODE_ORIG;
1121 	struct keypress kp = { EVT_KBRD, commands[oid].key[mode], 0 };
1122 	char buf[16];
1123 
1124 	/* Write the description */
1125 	Term_putstr(col, row, -1, attr, commands[oid].desc);
1126 
1127 	/* Include keypress */
1128 	Term_addch(attr, L' ');
1129 	Term_addch(attr, L'(');
1130 
1131 	/* Get readable version */
1132 	keypress_to_readable(buf, sizeof buf, kp);
1133 	Term_addstr(-1, attr, buf);
1134 
1135 	Term_addch(attr, L')');
1136 }
1137 
1138 /**
1139  * Display a list of commands.
1140  */
cmd_menu(struct command_list * list,void * selection_p)1141 static bool cmd_menu(struct command_list *list, void *selection_p)
1142 {
1143 	struct menu menu;
1144 	menu_iter commands_menu = { NULL, NULL, cmd_sub_entry, NULL, NULL };
1145 	region area = { 23, 4, 37, 13 };
1146 
1147 	ui_event evt;
1148 	struct cmd_info **selection = selection_p;
1149 
1150 	/* Set up the menu */
1151 	menu_init(&menu, MN_SKIN_SCROLL, &commands_menu);
1152 	menu_setpriv(&menu, list->len, list->list);
1153 	menu_layout(&menu, &area);
1154 
1155 	/* Set up the screen */
1156 	screen_save();
1157 	window_make(21, 3, 62, 17);
1158 
1159 	/* Select an entry */
1160 	evt = menu_select(&menu, 0, true);
1161 
1162 	/* Load de screen */
1163 	screen_load();
1164 
1165 	if (evt.type == EVT_SELECT)
1166 		*selection = &list->list[menu.cursor];
1167 
1168 	return false;
1169 }
1170 
1171 
1172 
cmd_list_action(struct menu * m,const ui_event * event,int oid)1173 static bool cmd_list_action(struct menu *m, const ui_event *event, int oid)
1174 {
1175 	if (event->type == EVT_SELECT)
1176 		return cmd_menu(&cmds_all[oid], menu_priv(m));
1177 	else
1178 		return false;
1179 }
1180 
cmd_list_entry(struct menu * menu,int oid,bool cursor,int row,int col,int width)1181 static void cmd_list_entry(struct menu *menu, int oid, bool cursor, int row,
1182 						   int col, int width)
1183 {
1184 	byte attr = (cursor ? COLOUR_L_BLUE : COLOUR_WHITE);
1185 	Term_putstr(col, row, -1, attr, cmds_all[oid].name);
1186 }
1187 
1188 static struct menu *command_menu;
1189 static menu_iter command_menu_iter =
1190 {
1191 	NULL,
1192 	NULL,
1193 	cmd_list_entry,
1194 	cmd_list_action,
1195 	NULL
1196 };
1197 
1198 /**
1199  * Display a list of command types, allowing the user to select one.
1200  */
textui_action_menu_choose(void)1201 struct cmd_info *textui_action_menu_choose(void)
1202 {
1203 	region area = { 21, 5, 37, 6 };
1204 	int len = 0;
1205 
1206 	struct cmd_info *chosen_command = NULL;
1207 
1208 	if (!command_menu)
1209 		command_menu = menu_new(MN_SKIN_SCROLL, &command_menu_iter);
1210 
1211 	while (cmds_all[len].len) {
1212 		if (cmds_all[len].len)
1213 			len++;
1214 	};
1215 
1216 	menu_setpriv(command_menu, len, &chosen_command);
1217 	menu_layout(command_menu, &area);
1218 
1219 	/* Set up the screen */
1220 	screen_save();
1221 	window_make(19, 4, 58, 11);
1222 
1223 	menu_select(command_menu, 0, true);
1224 
1225 	screen_load();
1226 
1227 	return chosen_command;
1228 }
1229