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