1 /**
2  * \file ui-store.c
3  * \brief Store UI
4  *
5  * Copyright (c) 1997 Robert A. Koeneke, James E. Wilson, Ben Harrison
6  * Copyright (c) 1998-2014 Angband developers
7  *
8  * This work is free software; you can redistribute it and/or modify it
9  * under the terms of either:
10  *
11  * a) the GNU General Public License as published by the Free Software
12  *    Foundation, version 2, or
13  *
14  * b) the "Angband licence":
15  *    This software may be copied and distributed for educational, research,
16  *    and not for profit purposes provided that this copyright and statement
17  *    are included in all such copies.  Other copyrights may also apply.
18  */
19 #include "angband.h"
20 #include "cave.h"
21 #include "cmds.h"
22 #include "game-event.h"
23 #include "game-input.h"
24 #include "hint.h"
25 #include "init.h"
26 #include "monster.h"
27 #include "obj-desc.h"
28 #include "obj-gear.h"
29 #include "obj-ignore.h"
30 #include "obj-info.h"
31 #include "obj-knowledge.h"
32 #include "obj-make.h"
33 #include "obj-pile.h"
34 #include "obj-tval.h"
35 #include "obj-util.h"
36 #include "player-calcs.h"
37 #include "player-history.h"
38 #include "player-util.h"
39 #include "store.h"
40 #include "target.h"
41 #include "ui-display.h"
42 #include "ui-input.h"
43 #include "ui-menu.h"
44 #include "ui-object.h"
45 #include "ui-options.h"
46 #include "ui-knowledge.h"
47 #include "ui-object.h"
48 #include "ui-player.h"
49 #include "ui-spell.h"
50 #include "ui-command.h"
51 #include "ui-store.h"
52 #include "z-debug.h"
53 
54 
55 /**
56  * Shopkeeper welcome messages.
57  *
58  * The shopkeeper's name must come first, then the character's name.
59  */
60 static const char *comment_welcome[] =
61 {
62 	"",
63 	"%s nods to you.",
64 	"%s says hello.",
65 	"%s: \"See anything you like, adventurer?\"",
66 	"%s: \"How may I help you, %s?\"",
67 	"%s: \"Welcome back, %s.\"",
68 	"%s: \"A pleasure to see you again, %s.\"",
69 	"%s: \"How may I be of assistance, good %s?\"",
70 	"%s: \"You do honour to my humble store, noble %s.\"",
71 	"%s: \"I and my family are entirely at your service, %s.\""
72 };
73 
74 static const char *comment_hint[] =
75 {
76 /*	"%s tells you soberly: \"%s\".",
77 	"(%s) There's a saying round here, \"%s\".",
78 	"%s offers to tell you a secret next time you're about."*/
79 	"\"%s\""
80 };
81 
82 
83 /**
84  * Easy names for the elements of the 'scr_places' arrays.
85  */
86 enum
87 {
88 	LOC_PRICE = 0,
89 	LOC_OWNER,
90 	LOC_HEADER,
91 	LOC_MORE,
92 	LOC_HELP_CLEAR,
93 	LOC_HELP_PROMPT,
94 	LOC_AU,
95 	LOC_WEIGHT,
96 
97 	LOC_MAX
98 };
99 
100 /* State flags */
101 #define STORE_GOLD_CHANGE      0x01
102 #define STORE_FRAME_CHANGE     0x02
103 #define STORE_SHOW_HELP        0x04
104 
105 /* Compound flag for the initial display of a store */
106 #define STORE_INIT_CHANGE		(STORE_FRAME_CHANGE | STORE_GOLD_CHANGE)
107 
108 struct store_context {
109 	struct menu menu;			/* Menu instance */
110 	struct store *store;	/* Pointer to store */
111 	struct object **list;	/* List of objects (unused) */
112 	int flags;				/* Display flags */
113 	bool inspect_only;		/* Only allow looking */
114 
115 	/* Places for the various things displayed onscreen */
116 	unsigned int scr_places_x[LOC_MAX];
117 	unsigned int scr_places_y[LOC_MAX];
118 };
119 
120 /* Return a random hint from the global hints list */
random_hint(void)121 static const char *random_hint(void)
122 {
123 	struct hint *v, *r = NULL;
124 	int n;
125 	for (v = hints, n = 1; v; v = v->next, n++)
126 		if (one_in_(n))
127 			r = v;
128 	return r->hint;
129 }
130 
131 /**
132  * The greeting a shopkeeper gives the character says a lot about his
133  * general attitude.
134  *
135  * Taken and modified from Sangband 1.0.
136  *
137  * Note that each comment_hint should have exactly one %s
138  */
prt_welcome(const struct owner * proprietor)139 static void prt_welcome(const struct owner *proprietor)
140 {
141 	char short_name[20];
142 	const char *owner_name = proprietor->name;
143 
144 	int j;
145 
146 	if (one_in_(2))
147 		return;
148 
149 	/* Get the first name of the store owner (stop before the first space) */
150 	for (j = 0; owner_name[j] && owner_name[j] != ' '; j++)
151 		short_name[j] = owner_name[j];
152 
153 	/* Truncate the name */
154 	short_name[j] = '\0';
155 
156 	if (one_in_(3)) {
157 		size_t i = randint0(N_ELEMENTS(comment_hint));
158 		msg(comment_hint[i], random_hint());
159 	} else if (player->lev > 5) {
160 		const char *player_name;
161 
162 		/* We go from level 1 - 50  */
163 		size_t i = ((unsigned)player->lev - 1) / 5;
164 		i = MIN(i, N_ELEMENTS(comment_welcome) - 1);
165 
166 		/* Get a title for the character */
167 		if ((i % 2) && randint0(2))
168 			player_name = player->class->title[(player->lev - 1) / 5];
169 		else if (randint0(2))
170 			player_name = player->full_name;
171 		else
172 			player_name = "valued customer";
173 
174 		/* Balthazar says "Welcome" */
175 		prt(format(comment_welcome[i], short_name, player_name), 0, 0);
176 	}
177 }
178 
179 
180 /*** Display code ***/
181 
182 
183 /**
184  * This function sets up screen locations based on the current term size.
185  *
186  * Current screen layout:
187  *  line 0: reserved for messages
188  *  line 1: shopkeeper and their purse / item buying price
189  *  line 2: empty
190  *  line 3: table headers
191  *
192  *  line 4: Start of items
193  *
194  * If help is turned off, then the rest of the display goes as:
195  *
196  *  line (height - 4): end of items
197  *  line (height - 3): "more" prompt
198  *  line (height - 2): empty
199  *  line (height - 1): Help prompt and remaining gold
200  *
201  * If help is turned on, then the rest of the display goes as:
202  *
203  *  line (height - 7): end of items
204  *  line (height - 6): "more" prompt
205  *  line (height - 4): gold remaining
206  *  line (height - 3): command help
207  */
store_display_recalc(struct store_context * ctx)208 static void store_display_recalc(struct store_context *ctx)
209 {
210 	int wid, hgt;
211 	region loc;
212 
213 	struct menu *m = &ctx->menu;
214 	struct store *store = ctx->store;
215 
216 	Term_get_size(&wid, &hgt);
217 
218 	/* Clip the width at a max of 104 (enough room for an 80-char item name) */
219 	if (wid > 104) wid = 104;
220 
221 	/* Clip the text_out function at two smaller than the screen width */
222 	text_out_wrap = wid - 2;
223 
224 
225 	/* X co-ords first */
226 	ctx->scr_places_x[LOC_PRICE] = wid - 14;
227 	ctx->scr_places_x[LOC_AU] = wid - 26;
228 	ctx->scr_places_x[LOC_OWNER] = wid - 2;
229 	ctx->scr_places_x[LOC_WEIGHT] = wid - 14;
230 
231 	/* Add space for for prices */
232 	if (store->sidx != STORE_HOME)
233 		ctx->scr_places_x[LOC_WEIGHT] -= 10;
234 
235 	/* Then Y */
236 	ctx->scr_places_y[LOC_OWNER] = 1;
237 	ctx->scr_places_y[LOC_HEADER] = 3;
238 
239 	/* If we are displaying help, make the height smaller */
240 	if (ctx->flags & (STORE_SHOW_HELP))
241 		hgt -= 3;
242 
243 	ctx->scr_places_y[LOC_MORE] = hgt - 3;
244 	ctx->scr_places_y[LOC_AU] = hgt - 1;
245 
246 	loc = m->boundary;
247 
248 	/* If we're displaying the help, then put it with a line of padding */
249 	if (ctx->flags & (STORE_SHOW_HELP)) {
250 		ctx->scr_places_y[LOC_HELP_CLEAR] = hgt - 1;
251 		ctx->scr_places_y[LOC_HELP_PROMPT] = hgt;
252 		loc.page_rows = -5;
253 	} else {
254 		ctx->scr_places_y[LOC_HELP_CLEAR] = hgt - 2;
255 		ctx->scr_places_y[LOC_HELP_PROMPT] = hgt - 1;
256 		loc.page_rows = -2;
257 	}
258 
259 	menu_layout(m, &loc);
260 }
261 
262 
263 /**
264  * Redisplay a single store entry
265  */
store_display_entry(struct menu * menu,int oid,bool cursor,int row,int col,int width)266 static void store_display_entry(struct menu *menu, int oid, bool cursor, int row,
267 								int col, int width)
268 {
269 	struct object *obj;
270 	s32b x;
271 	int desc = ODESC_PREFIX;
272 
273 	char o_name[80];
274 	char out_val[160];
275 	byte colour;
276 
277 	struct store_context *ctx = menu_priv(menu);
278 	struct store *store = ctx->store;
279 	assert(store);
280 
281 	/* Get the object */
282 	obj = ctx->list[oid];
283 
284 	/* Describe the object - preserving insriptions in the home */
285 	if (store->sidx == STORE_HOME) {
286 		desc |= ODESC_FULL;
287 	} else {
288 		desc |= ODESC_FULL | ODESC_STORE;
289 	}
290 	object_desc(o_name, sizeof(o_name), obj, desc);
291 
292 	/* Display the object */
293 	c_put_str(obj->kind->base->attr, o_name, row, col);
294 
295 	/* Show weights */
296 	colour = curs_attrs[CURS_KNOWN][(int)cursor];
297 	strnfmt(out_val, sizeof out_val, "%3d.%d lb", obj->weight / 10,
298 			obj->weight % 10);
299 	c_put_str(colour, out_val, row, ctx->scr_places_x[LOC_WEIGHT]);
300 
301 	/* Describe an object (fully) in a store */
302 	if (store->sidx != STORE_HOME) {
303 		/* Extract the "minimum" price */
304 		x = price_item(store, obj, false, 1);
305 
306 		/* Make sure the player can afford it */
307 		if ((int) player->au < (int) x)
308 			colour = curs_attrs[CURS_UNKNOWN][(int)cursor];
309 
310 		/* Actually draw the price */
311 		if (tval_can_have_charges(obj) && (obj->number > 1))
312 			strnfmt(out_val, sizeof out_val, "%9d avg", x);
313 		else
314 			strnfmt(out_val, sizeof out_val, "%9d    ", x);
315 
316 		c_put_str(colour, out_val, row, ctx->scr_places_x[LOC_PRICE]);
317 	}
318 }
319 
320 
321 /**
322  * Display store (after clearing screen)
323  */
store_display_frame(struct store_context * ctx)324 static void store_display_frame(struct store_context *ctx)
325 {
326 	char buf[80];
327 	struct store *store = ctx->store;
328 	struct owner *proprietor = store->owner;
329 
330 	/* Clear screen */
331 	Term_clear();
332 
333 	/* The "Home" is special */
334 	if (store->sidx == STORE_HOME) {
335 		/* Put the owner name */
336 		put_str("Your Home", ctx->scr_places_y[LOC_OWNER], 1);
337 
338 		/* Label the object descriptions */
339 		put_str("Home Inventory", ctx->scr_places_y[LOC_HEADER], 1);
340 
341 		/* Show weight header */
342 		put_str("Weight", ctx->scr_places_y[LOC_HEADER],
343 				ctx->scr_places_x[LOC_WEIGHT] + 2);
344 	} else {
345 		/* Normal stores */
346 		const char *store_name = store->name;
347 		const char *owner_name = proprietor->name;
348 
349 		/* Put the owner name */
350 		put_str(owner_name, ctx->scr_places_y[LOC_OWNER], 1);
351 
352 		/* Show the max price in the store (above prices) */
353 		strnfmt(buf, sizeof(buf), "%s (%d)", store_name,
354 				proprietor->max_cost);
355 		prt(buf, ctx->scr_places_y[LOC_OWNER],
356 			ctx->scr_places_x[LOC_OWNER] - strlen(buf));
357 
358 		/* Label the object descriptions */
359 		put_str("Store Inventory", ctx->scr_places_y[LOC_HEADER], 1);
360 
361 		/* Showing weight label */
362 		put_str("Weight", ctx->scr_places_y[LOC_HEADER],
363 				ctx->scr_places_x[LOC_WEIGHT] + 2);
364 
365 		/* Label the asking price (in stores) */
366 		put_str("Price", ctx->scr_places_y[LOC_HEADER], ctx->scr_places_x[LOC_PRICE] + 4);
367 	}
368 }
369 
370 
371 /**
372  * Display help.
373  */
store_display_help(struct store_context * ctx)374 static void store_display_help(struct store_context *ctx)
375 {
376 	struct store *store = ctx->store;
377 	int help_loc = ctx->scr_places_y[LOC_HELP_PROMPT];
378 	bool is_home = (store->sidx == STORE_HOME) ? true : false;
379 
380 	/* Clear */
381 	clear_from(ctx->scr_places_y[LOC_HELP_CLEAR]);
382 
383 	/* Prepare help hooks */
384 	text_out_hook = text_out_to_screen;
385 	text_out_indent = 1;
386 	Term_gotoxy(1, help_loc);
387 
388 	if (OPT(player, rogue_like_commands))
389 		text_out_c(COLOUR_L_GREEN, "x");
390 	else
391 		text_out_c(COLOUR_L_GREEN, "l");
392 
393 	text_out(" examines");
394 	if (!ctx->inspect_only) {
395 		text_out(" and ");
396 		text_out_c(COLOUR_L_GREEN, "p");
397 
398 		if (is_home) text_out(" picks up");
399 		else text_out(" purchases");
400 	}
401 	text_out(" the selected item. ");
402 
403 	if (!ctx->inspect_only) {
404 		if (OPT(player, birth_no_selling)) {
405 			text_out_c(COLOUR_L_GREEN, "d");
406 			text_out(" gives an item to the store in return for its identification. Some wands and staves will also be recharged. ");
407 		} else {
408 			text_out_c(COLOUR_L_GREEN, "d");
409 			if (is_home) text_out(" drops");
410 			else text_out(" sells");
411 			text_out(" an item from your inventory. ");
412 		}
413 	} else {
414 		text_out_c(COLOUR_L_GREEN, "I");
415 		text_out(" inspects an item from your inventory. ");
416 	}
417 
418 	text_out_c(COLOUR_L_GREEN, "ESC");
419 	if (!ctx->inspect_only)
420 		text_out(" exits the building.");
421 	else
422 		text_out(" exits this screen.");
423 
424 	text_out_indent = 0;
425 }
426 
427 /**
428  * Decides what parts of the store display to redraw.  Called on terminal
429  * resizings and the redraw command.
430  */
store_redraw(struct store_context * ctx)431 static void store_redraw(struct store_context *ctx)
432 {
433 	if (ctx->flags & (STORE_FRAME_CHANGE)) {
434 		store_display_frame(ctx);
435 
436 		if (ctx->flags & STORE_SHOW_HELP)
437 			store_display_help(ctx);
438 		else
439 			prt("Press '?' for help.", ctx->scr_places_y[LOC_HELP_PROMPT], 1);
440 
441 		ctx->flags &= ~(STORE_FRAME_CHANGE);
442 	}
443 
444 	if (ctx->flags & (STORE_GOLD_CHANGE)) {
445 		prt(format("Gold Remaining: %9d", player->au),
446 				ctx->scr_places_y[LOC_AU], ctx->scr_places_x[LOC_AU]);
447 		ctx->flags &= ~(STORE_GOLD_CHANGE);
448 	}
449 }
450 
store_get_check(const char * prompt)451 static bool store_get_check(const char *prompt)
452 {
453 	struct keypress ch;
454 
455 	/* Prompt for it */
456 	prt(prompt, 0, 0);
457 
458 	/* Get an answer */
459 	ch = inkey();
460 
461 	/* Erase the prompt */
462 	prt("", 0, 0);
463 
464 	if (ch.code == ESCAPE) return (false);
465 	if (strchr("Nn", ch.code)) return (false);
466 
467 	/* Success */
468 	return (true);
469 }
470 
471 /*
472  * Sell an object, or drop if it we're in the home.
473  */
store_sell(struct store_context * ctx)474 static bool store_sell(struct store_context *ctx)
475 {
476 	int amt;
477 	int get_mode = USE_EQUIP | USE_INVEN | USE_FLOOR | USE_QUIVER;
478 
479 	struct store *store = ctx->store;
480 
481 	struct object *obj;
482 	struct object object_type_body = OBJECT_NULL;
483 	struct object *temp_obj = &object_type_body;
484 
485 	char o_name[120];
486 
487 	item_tester tester = NULL;
488 
489 	const char *reject = "You have nothing that I want. ";
490 	const char *prompt = OPT(player, birth_no_selling) ? "Give which item? " : "Sell which item? ";
491 
492 	assert(store);
493 
494 	/* Clear all current messages */
495 	msg_flag = false;
496 	prt("", 0, 0);
497 
498 	if (store->sidx == STORE_HOME) {
499 		prompt = "Drop which item? ";
500 	} else {
501 		tester = store_will_buy_tester;
502 		get_mode |= SHOW_PRICES;
503 	}
504 
505 	/* Get an item */
506 	player->upkeep->command_wrk = USE_INVEN;
507 
508 	if (!get_item(&obj, prompt, reject, CMD_DROP, tester, get_mode))
509 		return false;
510 
511 	/* Cannot remove stickied objects */
512 	if (object_is_equipped(player->body, obj) && !obj_can_takeoff(obj)) {
513 		/* Oops */
514 		msg("Hmmm, it seems to be stuck.");
515 
516 		/* Nope */
517 		return false;
518 	}
519 
520 	/* Get a quantity */
521 	amt = get_quantity(NULL, obj->number);
522 
523 	/* Allow user abort */
524 	if (amt <= 0) return false;
525 
526 	/* Get a copy of the object representing the number being sold */
527 	object_copy_amt(temp_obj, obj, amt);
528 
529 	if (!store_check_num(store, temp_obj)) {
530 		object_wipe(temp_obj);
531 		if (store->sidx == STORE_HOME)
532 			msg("Your home is full.");
533 		else
534 			msg("I have not the room in my store to keep it.");
535 
536 		return false;
537 	}
538 
539 	/* Get a full description */
540 	object_desc(o_name, sizeof(o_name), temp_obj, ODESC_PREFIX | ODESC_FULL);
541 
542 	/* Real store */
543 	if (store->sidx != STORE_HOME) {
544 		/* Extract the value of the items */
545 		u32b price = price_item(store, temp_obj, true, amt);
546 
547 		object_wipe(temp_obj);
548 		screen_save();
549 
550 		/* Show price */
551 		if (!OPT(player, birth_no_selling))
552 			prt(format("Price: %d", price), 1, 0);
553 
554 		/* Confirm sale */
555 		if (!store_get_check(format("%s %s? [ESC, any other key to accept]",
556 				OPT(player, birth_no_selling) ? "Give" : "Sell", o_name))) {
557 			screen_load();
558 			return false;
559 		}
560 
561 		screen_load();
562 
563 		cmdq_push(CMD_SELL);
564 		cmd_set_arg_item(cmdq_peek(), "item", obj);
565 		cmd_set_arg_number(cmdq_peek(), "quantity", amt);
566 	} else { /* Player is at home */
567 		object_wipe(temp_obj);
568 		cmdq_push(CMD_STASH);
569 		cmd_set_arg_item(cmdq_peek(), "item", obj);
570 		cmd_set_arg_number(cmdq_peek(), "quantity", amt);
571 	}
572 
573 	/* Update the display */
574 	ctx->flags |= STORE_GOLD_CHANGE;
575 
576 	return true;
577 }
578 
579 
580 
581 /**
582  * Buy an object from a store
583  */
store_purchase(struct store_context * ctx,int item,bool single)584 static bool store_purchase(struct store_context *ctx, int item, bool single)
585 {
586 	struct store *store = ctx->store;
587 
588 	struct object *obj = ctx->list[item];
589 	struct object *dummy = NULL;
590 
591 	char o_name[80];
592 
593 	int amt, num;
594 
595 	s32b price;
596 
597 	/* Clear all current messages */
598 	msg_flag = false;
599 	prt("", 0, 0);
600 
601 
602 	/*** Check the player can get any at all ***/
603 
604 	/* Get an amount if we weren't given one */
605 	if (single) {
606 		amt = 1;
607 
608 		/* Check if the player can afford any at all */
609 		if (store->sidx != STORE_HOME &&
610 				(int)player->au < (int)price_item(store, obj, false, 1)) {
611 			msg("You do not have enough gold for this item.");
612 			return false;
613 		}
614 	} else {
615 		if (store->sidx == STORE_HOME) {
616 			amt = obj->number;
617 		} else {
618 			/* Price of one */
619 			price = price_item(store, obj, false, 1);
620 
621 			/* Check if the player can afford any at all */
622 			if ((u32b)player->au < (u32b)price) {
623 				msg("You do not have enough gold for this item.");
624 				return false;
625 			}
626 
627 			/* Work out how many the player can afford */
628 			if (price == 0)
629 				amt = obj->number; /* Prevent division by zero */
630 			else
631 				amt = player->au / price;
632 
633 			if (amt > obj->number) amt = obj->number;
634 
635 			/* Double check for wands/staves */
636 			if ((player->au >= price_item(store, obj, false, amt+1)) &&
637 				(amt < obj->number))
638 				amt++;
639 		}
640 
641 		/* Limit to the number that can be carried */
642 		amt = MIN(amt, inven_carry_num(obj, false));
643 
644 		/* Fail if there is no room */
645 		if ((amt <= 0) || (!object_flavor_is_aware(obj) && pack_is_full())) {
646 			msg("You cannot carry that many items.");
647 			return false;
648 		}
649 
650 		/* Find the number of this item in the inventory */
651 		if (!object_flavor_is_aware(obj))
652 			num = 0;
653 		else
654 			num = find_inven(obj);
655 
656 		strnfmt(o_name, sizeof o_name, "%s how many%s? (max %d) ",
657 				(store->sidx == STORE_HOME) ? "Take" : "Buy",
658 				num ? format(" (you have %d)", num) : "", amt);
659 
660 		/* Get a quantity */
661 		amt = get_quantity(o_name, amt);
662 
663 		/* Allow user abort */
664 		if (amt <= 0) return false;
665 	}
666 
667 	/* Get desired object */
668 	dummy = object_new();
669 	object_copy_amt(dummy, obj, amt);
670 
671 	/* Ensure we have room */
672 	if (!inven_carry_okay(dummy)) {
673 		msg("You cannot carry that many items.");
674 		object_delete(&dummy);
675 		return false;
676 	}
677 
678 	/* Describe the object (fully) */
679 	object_desc(o_name, sizeof(o_name), dummy, ODESC_PREFIX | ODESC_FULL |
680 		ODESC_STORE);
681 
682 	/* Attempt to buy it */
683 	if (store->sidx != STORE_HOME) {
684 		bool response;
685 
686 		/* Extract the price for the entire stack */
687 		price = price_item(store, dummy, false, dummy->number);
688 
689 		screen_save();
690 
691 		/* Show price */
692 		prt(format("Price: %d", price), 1, 0);
693 
694 		/* Confirm purchase */
695 		response = store_get_check(format("Buy %s? [ESC, any other key to accept]", o_name));
696 		screen_load();
697 
698 		/* Negative response, so give up */
699 		if (!response) return false;
700 
701 		cmdq_push(CMD_BUY);
702 		cmd_set_arg_item(cmdq_peek(), "item", obj);
703 		cmd_set_arg_number(cmdq_peek(), "quantity", amt);
704 	} else {
705 		/* Home is much easier */
706 		cmdq_push(CMD_RETRIEVE);
707 		cmd_set_arg_item(cmdq_peek(), "item", obj);
708 		cmd_set_arg_number(cmdq_peek(), "quantity", amt);
709 	}
710 
711 	/* Update the display */
712 	ctx->flags |= STORE_GOLD_CHANGE;
713 
714 	object_delete(&dummy);
715 
716 	/* Not kicked out */
717 	return true;
718 }
719 
720 
721 /**
722  * Examine an item in a store
723  */
store_examine(struct store_context * ctx,int item)724 static void store_examine(struct store_context *ctx, int item)
725 {
726 	struct object *obj;
727 	char header[120];
728 	textblock *tb;
729 	region area = { 0, 0, 0, 0 };
730 	int odesc_flags = ODESC_PREFIX | ODESC_FULL;
731 
732 	if (item < 0) return;
733 
734 	/* Get the actual object */
735 	obj = ctx->list[item];
736 
737 	/* Items in the home get less description */
738 	if (ctx->store->sidx == STORE_HOME) {
739 		odesc_flags |= ODESC_CAPITAL;
740 	} else {
741 		odesc_flags |= ODESC_STORE;
742 	}
743 
744 	/* Hack -- no flush needed */
745 	msg_flag = false;
746 
747 	/* Show full info in most stores, but normal info in player home */
748 	tb = object_info(obj, OINFO_NONE);
749 	object_desc(header, sizeof(header), obj, odesc_flags);
750 
751 	textui_textblock_show(tb, area, header);
752 	textblock_free(tb);
753 
754 	/* Hack -- Browse book, then prompt for a command */
755 	if (obj_can_browse(obj))
756 		textui_book_browse(obj);
757 }
758 
759 
store_menu_set_selections(struct menu * menu,bool knowledge_menu)760 static void store_menu_set_selections(struct menu *menu, bool knowledge_menu)
761 {
762 	if (knowledge_menu) {
763 		if (OPT(player, rogue_like_commands)) {
764 			/* These two can't intersect! */
765 			menu->cmd_keys = "?|Ieilx";
766 			menu->selections = "abcdfghjkmnopqrstuvwyz134567";
767 		} else {
768 			/* These two can't intersect! */
769 			menu->cmd_keys = "?|Ieil";
770 			menu->selections = "abcdfghjkmnopqrstuvwxyz13456";
771 		}
772 	} else {
773 		if (OPT(player, rogue_like_commands)) {
774 			/* These two can't intersect! */
775 			menu->cmd_keys = "\x04\x05\x10?={|}~CEIPTdegilpswx"; /* \x10 = ^p , \x04 = ^D, \x05 = ^E */
776 			menu->selections = "abcfmnoqrtuvyz13456790ABDFGH";
777 		} else {
778 			/* These two can't intersect! */
779 			menu->cmd_keys = "\x05\x010?={|}~CEIbdegiklpstwx"; /* \x05 = ^E, \x10 = ^p */
780 			menu->selections = "acfhjmnoqruvyz13456790ABDFGH";
781 		}
782 	}
783 }
784 
store_menu_recalc(struct menu * m)785 static void store_menu_recalc(struct menu *m)
786 {
787 	struct store_context *ctx = menu_priv(m);
788 	menu_setpriv(m, ctx->store->stock_num, ctx);
789 }
790 
791 /**
792  * Process a command in a store
793  *
794  * Note that we must allow the use of a few "special" commands in the stores
795  * which are not allowed in the dungeon, and we must disable some commands
796  * which are allowed in the dungeon but not in the stores, to prevent chaos.
797  */
store_process_command_key(struct keypress kp)798 static bool store_process_command_key(struct keypress kp)
799 {
800 	int cmd = 0;
801 
802 	/* Hack -- no flush needed */
803 	prt("", 0, 0);
804 	msg_flag = false;
805 
806 	/* Process the keycode */
807 	switch (kp.code) {
808 		case 'T': /* roguelike */
809 		case 't': cmd = CMD_TAKEOFF; break;
810 
811 		case KTRL('D'): /* roguelike */
812 		case 'k': textui_cmd_ignore(); break;
813 
814 		case 'P': /* roguelike */
815 		case 'b': textui_spell_browse(); break;
816 
817 		case '~': textui_browse_knowledge(); break;
818 		case 'I': textui_obj_examine(); break;
819 		case 'w': cmd = CMD_WIELD; break;
820 		case '{': cmd = CMD_INSCRIBE; break;
821 		case '}': cmd = CMD_UNINSCRIBE; break;
822 
823 		case 'e': do_cmd_equip(); break;
824 		case 'i': do_cmd_inven(); break;
825 		case '|': do_cmd_quiver(); break;
826 		case KTRL('E'): toggle_inven_equip(); break;
827 		case 'C': do_cmd_change_name(); break;
828 		case KTRL('P'): do_cmd_messages(); break;
829 		case ')': do_cmd_save_screen(); break;
830 
831 		default: return false;
832 	}
833 
834 	if (cmd)
835 		cmdq_push_repeat(cmd, 0);
836 
837 	return true;
838 }
839 
840 /**
841  * Select an item from the store's stock, and return the stock index
842  */
store_get_stock(struct menu * m,int oid)843 static int store_get_stock(struct menu *m, int oid)
844 {
845 	ui_event e;
846 	int no_act = m->flags & MN_NO_ACTION;
847 
848 	/* Set a flag to make sure that we get the selection or escape
849 	 * without running the menu handler */
850 	m->flags |= MN_NO_ACTION;
851 	e = menu_select(m, 0, true);
852 	if (!no_act) {
853 		m->flags &= ~MN_NO_ACTION;
854 	}
855 
856 	if (e.type == EVT_SELECT) {
857 		return m->cursor;
858 	} else if (e.type == EVT_ESCAPE) {
859 		return -1;
860 	}
861 
862 	/* if we do not have a new selection, just return the original item */
863 	return oid;
864 }
865 
866 /** Enum for context menu entries */
867 enum {
868 	ACT_INSPECT_INVEN,
869 	ACT_SELL,
870 	ACT_EXAMINE,
871 	ACT_BUY,
872 	ACT_BUY_ONE,
873 	ACT_EXIT
874 };
875 
876 /* pick the context menu options appropiate for a store */
context_menu_store(struct store_context * ctx,const int oid,int mx,int my)877 static int context_menu_store(struct store_context *ctx, const int oid, int mx, int my)
878 {
879 	struct store *store = ctx->store;
880 	bool home = (store->sidx == STORE_HOME) ? true : false;
881 
882 	struct menu *m = menu_dynamic_new();
883 
884 	int selected;
885 	char *labels = string_make(lower_case);
886 	m->selections = labels;
887 
888 	menu_dynamic_add_label(m, "Inspect inventory", 'I', ACT_INSPECT_INVEN, labels);
889 	menu_dynamic_add_label(m, home ? "Stash" : "Sell", 'd', ACT_SELL, labels);
890 	menu_dynamic_add_label(m, "Exit", '`', ACT_EXIT, labels);
891 
892 	/* Hack -- no flush needed */
893 	msg_flag = false;
894 	screen_save();
895 
896 	menu_dynamic_calc_location(m, mx, my);
897 	region_erase_bordered(&m->boundary);
898 
899 	prt("(Enter to select, ESC) Command:", 0, 0);
900 	selected = menu_dynamic_select(m);
901 
902 	menu_dynamic_free(m);
903 	string_free(labels);
904 
905 	screen_load();
906 
907 	switch (selected) {
908 		case ACT_SELL:
909 			store_sell(ctx);
910 			break;
911 		case ACT_INSPECT_INVEN:
912 			textui_obj_examine();
913 			break;
914 		case ACT_EXIT:
915 			return false;
916 	}
917 
918 	return true;
919 }
920 
921 /* pick the context menu options appropiate for an item available in a store */
context_menu_store_item(struct store_context * ctx,const int oid,int mx,int my)922 static void context_menu_store_item(struct store_context *ctx, const int oid, int mx, int my)
923 {
924 	struct store *store = ctx->store;
925 	bool home = (store->sidx == STORE_HOME) ? true : false;
926 
927 	struct menu *m = menu_dynamic_new();
928 	struct object *obj = ctx->list[oid];
929 
930 	int selected;
931 	char *labels;
932 	char header[120];
933 
934 	object_desc(header, sizeof(header), obj,
935 				ODESC_PREFIX | ODESC_FULL | ODESC_STORE);
936 
937 	labels = string_make(lower_case);
938 	m->selections = labels;
939 
940 	menu_dynamic_add_label(m, "Examine", 'x', ACT_EXAMINE, labels);
941 	menu_dynamic_add_label(m, home ? "Take" : "Buy", 'd', ACT_SELL, labels);
942 	if (obj->number > 1)
943 		menu_dynamic_add_label(m, home ? "Take one" : "Buy one", 'o', ACT_BUY_ONE, labels);
944 
945 	/* Hack -- no flush needed */
946 	msg_flag = false;
947 	screen_save();
948 
949 	menu_dynamic_calc_location(m, mx, my);
950 	region_erase_bordered(&m->boundary);
951 
952 	prt(format("(Enter to select, ESC) Command for %s:", header), 0, 0);
953 	selected = menu_dynamic_select(m);
954 
955 	menu_dynamic_free(m);
956 	string_free(labels);
957 
958 	screen_load();
959 
960 	switch (selected) {
961 		case ACT_EXAMINE:
962 			store_examine(ctx, oid);
963 			break;
964 		case ACT_BUY:
965 			store_purchase(ctx, oid, false);
966 			break;
967 		case ACT_BUY_ONE:
968 			store_purchase(ctx, oid, true);
969 			break;
970 	}
971 }
972 
973 /**
974  * Handle store menu input
975  */
store_menu_handle(struct menu * m,const ui_event * event,int oid)976 static bool store_menu_handle(struct menu *m, const ui_event *event, int oid)
977 {
978 	bool processed = true;
979 	struct store_context *ctx = menu_priv(m);
980 	struct store *store = ctx->store;
981 
982 	if (event->type == EVT_SELECT) {
983 		/* Nothing for now, except "handle" the event */
984 		return true;
985 		/* In future, maybe we want a display a list of what you can do. */
986 	} else if (event->type == EVT_MOUSE) {
987 		if (event->mouse.button == 2) {
988 			/* exit the store? what already does this? menu_handle_mouse
989 			 * so exit this so that menu_handle_mouse will be called */
990 			return false;
991 		} else if (event->mouse.button == 1) {
992 			bool action = false;
993 			if ((event->mouse.y == 0) || (event->mouse.y == 1)) {
994 				/* show the store context menu */
995 				if (context_menu_store(ctx, oid, event->mouse.x, event->mouse.y) == false)
996 					return false;
997 
998 				action = true;
999 			} else if ((oid >= 0) && (event->mouse.y == m->active.row + oid)) {
1000 				/* if press is on a list item, so store item context */
1001 				context_menu_store_item(ctx, oid, event->mouse.x,
1002 										event->mouse.y);
1003 				action = true;
1004 			}
1005 
1006 			if (action) {
1007 				ctx->flags |= (STORE_FRAME_CHANGE | STORE_GOLD_CHANGE);
1008 
1009 				/* Let the game handle any core commands (equipping, etc) */
1010 				cmdq_pop(CTX_STORE);
1011 
1012 				/* Notice and handle stuff */
1013 				notice_stuff(player);
1014 				handle_stuff(player);
1015 
1016 				/* Display the store */
1017 				store_display_recalc(ctx);
1018 				store_menu_recalc(m);
1019 				store_redraw(ctx);
1020 
1021 				return true;
1022 			}
1023 		}
1024 	} else if (event->type == EVT_KBRD) {
1025 		switch (event->key.code) {
1026 			case 's':
1027 			case 'd': store_sell(ctx); break;
1028 
1029 			case 'p':
1030 			case 'g':
1031 				/* use the old way of purchasing items */
1032 				msg_flag = false;
1033 				if (store->sidx != STORE_HOME) {
1034 					prt("Purchase which item? (ESC to cancel, Enter to select)",
1035 						0, 0);
1036 				} else {
1037 					prt("Get which item? (Esc to cancel, Enter to select)",
1038 						0, 0);
1039 				}
1040 				oid = store_get_stock(m, oid);
1041 				prt("", 0, 0);
1042 				if (oid >= 0) {
1043 					store_purchase(ctx, oid, false);
1044 				}
1045 				break;
1046 			case 'l':
1047 			case 'x':
1048 				/* use the old way of examining items */
1049 				msg_flag = false;
1050 				prt("Examine which item? (ESC to cancel, Enter to select)",
1051 					0, 0);
1052 				oid = store_get_stock(m, oid);
1053 				prt("", 0, 0);
1054 				if (oid >= 0) {
1055 					store_examine(ctx, oid);
1056 				}
1057 				break;
1058 
1059 			case '?': {
1060 				/* Toggle help */
1061 				if (ctx->flags & STORE_SHOW_HELP)
1062 					ctx->flags &= ~(STORE_SHOW_HELP);
1063 				else
1064 					ctx->flags |= STORE_SHOW_HELP;
1065 
1066 				/* Redisplay */
1067 				ctx->flags |= STORE_INIT_CHANGE;
1068 
1069 				store_display_recalc(ctx);
1070 				store_redraw(ctx);
1071 
1072 				break;
1073 			}
1074 
1075 			case '=': {
1076 				do_cmd_options();
1077 				store_menu_set_selections(m, false);
1078 				break;
1079 			}
1080 
1081 			default:
1082 				processed = store_process_command_key(event->key);
1083 		}
1084 
1085 		/* Let the game handle any core commands (equipping, etc) */
1086 		cmdq_pop(CTX_STORE);
1087 
1088 		if (processed) {
1089 			event_signal(EVENT_INVENTORY);
1090 			event_signal(EVENT_EQUIPMENT);
1091 		}
1092 
1093 		/* Notice and handle stuff */
1094 		notice_stuff(player);
1095 		handle_stuff(player);
1096 
1097 		return processed;
1098 	}
1099 
1100 	return false;
1101 }
1102 
1103 static region store_menu_region = { 1, 4, -1, -2 };
1104 static const menu_iter store_menu =
1105 {
1106 	NULL,
1107 	NULL,
1108 	store_display_entry,
1109 	store_menu_handle,
1110 	NULL
1111 };
1112 
1113 /**
1114  * Init the store menu
1115  */
store_menu_init(struct store_context * ctx,struct store * store,bool inspect_only)1116 static void store_menu_init(struct store_context *ctx, struct store *store, bool inspect_only)
1117 {
1118 	struct menu *menu = &ctx->menu;
1119 
1120 	ctx->store = store;
1121 	ctx->flags = STORE_INIT_CHANGE;
1122 	ctx->inspect_only = inspect_only;
1123 	ctx->list = mem_zalloc(sizeof(struct object *) * z_info->store_inven_max);
1124 
1125 	store_stock_list(ctx->store, ctx->list, z_info->store_inven_max);
1126 
1127 	/* Init the menu structure */
1128 	menu_init(menu, MN_SKIN_SCROLL, &store_menu);
1129 	menu_setpriv(menu, 0, ctx);
1130 
1131 	/* Calculate the positions of things and draw */
1132 	menu_layout(menu, &store_menu_region);
1133 	store_menu_set_selections(menu, inspect_only);
1134 	store_display_recalc(ctx);
1135 	store_menu_recalc(menu);
1136 	store_redraw(ctx);
1137 }
1138 
1139 /**
1140  * Display contents of a store from knowledge menu
1141  *
1142  * The only allowed actions are 'I' to inspect an item
1143  */
textui_store_knowledge(int n)1144 void textui_store_knowledge(int n)
1145 {
1146 	struct store_context ctx;
1147 
1148 	screen_save();
1149 	clear_from(0);
1150 
1151 	store_menu_init(&ctx, &stores[n], true);
1152 	menu_select(&ctx.menu, 0, false);
1153 
1154 	/* Flush messages XXX XXX XXX */
1155 	event_signal(EVENT_MESSAGE_FLUSH);
1156 
1157 	screen_load();
1158 
1159 	mem_free(ctx.list);
1160 }
1161 
1162 
1163 /**
1164  * Handle stock change.
1165  */
refresh_stock(game_event_type type,game_event_data * unused,void * user)1166 static void refresh_stock(game_event_type type, game_event_data *unused, void *user)
1167 {
1168 	struct store_context *ctx = user;
1169 	struct menu *menu = &ctx->menu;
1170 
1171 	store_stock_list(ctx->store, ctx->list, z_info->store_inven_max);
1172 
1173 	/* Display the store */
1174 	store_display_recalc(ctx);
1175 	store_menu_recalc(menu);
1176 	store_redraw(ctx);
1177 }
1178 
1179 /**
1180  * Enter a store.
1181  */
enter_store(game_event_type type,game_event_data * data,void * user)1182 void enter_store(game_event_type type, game_event_data *data, void *user)
1183 {
1184 	/* Check that we're on a store */
1185 	if (!square_isshop(cave, player->grid)) {
1186 		msg("You see no store here.");
1187 		return;
1188 	}
1189 
1190 	/* Shut down the normal game view */
1191 	event_signal(EVENT_LEAVE_WORLD);
1192 }
1193 
1194 /**
1195  * Interact with a store.
1196  */
use_store(game_event_type type,game_event_data * data,void * user)1197 void use_store(game_event_type type, game_event_data *data, void *user)
1198 {
1199 	struct store *store = store_at(cave, player->grid);
1200 	struct store_context ctx;
1201 
1202 	/* Check that we're on a store */
1203 	if (!store) return;
1204 
1205 	/*** Display ***/
1206 
1207 	/* Save current screen (ie. dungeon) */
1208 	screen_save();
1209 	msg_flag = false;
1210 
1211 	/* Get a array version of the store stock, register handler for changes */
1212 	event_add_handler(EVENT_STORECHANGED, refresh_stock, &ctx);
1213 	store_menu_init(&ctx, store, false);
1214 
1215 	/* Say a friendly hello. */
1216 	if (store->sidx != STORE_HOME)
1217 		prt_welcome(store->owner);
1218 
1219 	/* Shopping */
1220 	menu_select(&ctx.menu, 0, false);
1221 
1222 	/* Shopping's done */
1223 	event_remove_handler(EVENT_STORECHANGED, refresh_stock, &ctx);
1224 	msg_flag = false;
1225 	mem_free(ctx.list);
1226 
1227 	/* Take a turn */
1228 	player->upkeep->energy_use = z_info->move_energy;
1229 
1230 	/* Flush messages */
1231 	event_signal(EVENT_MESSAGE_FLUSH);
1232 
1233 	/* Load the screen */
1234 	screen_load();
1235 }
1236 
leave_store(game_event_type type,game_event_data * data,void * user)1237 void leave_store(game_event_type type, game_event_data *data, void *user)
1238 {
1239 	/* Disable repeats */
1240 	cmd_disable_repeat();
1241 
1242 	/* Switch back to the normal game view. */
1243 	event_signal(EVENT_ENTER_WORLD);
1244 
1245 	/* Update the visuals */
1246 	player->upkeep->update |= (PU_UPDATE_VIEW | PU_MONSTERS);
1247 
1248 	/* Redraw entire screen */
1249 	player->upkeep->redraw |= (PR_BASIC | PR_EXTRA);
1250 
1251 	/* Redraw map */
1252 	player->upkeep->redraw |= (PR_MAP);
1253 }
1254