1 /**
2 * @file
3 * @brief Functions with decks of cards.
4 **/
5
6 #include "AppHdr.h"
7
8 #include "decks.h"
9
10 #include <algorithm>
11 #include <iostream>
12 #include <iterator>
13 #include <sstream>
14 #include <unordered_set>
15
16 #include "ability.h"
17 #include "abyss.h"
18 #include "artefact.h"
19 #include "beam.h"
20 #include "cloud.h"
21 #include "coordit.h"
22 #include "dactions.h"
23 #include "database.h"
24 #include "describe.h"
25 #include "directn.h"
26 #include "dungeon.h"
27 #include "evoke.h"
28 #include "ghost.h"
29 #include "god-passive.h" // passive_t::no_haste
30 #include "god-wrath.h"
31 #include "invent.h"
32 #include "item-prop.h"
33 #include "item-status-flag-type.h"
34 #include "items.h"
35 #include "libutil.h"
36 #include "macro.h"
37 #include "message.h"
38 #include "mon-cast.h"
39 #include "mon-clone.h"
40 #include "mon-place.h"
41 #include "mon-poly.h"
42 #include "mon-project.h"
43 #include "mon-util.h"
44 #include "mutation.h"
45 #include "notes.h"
46 #include "output.h"
47 #include "prompt.h"
48 #include "random.h"
49 #include "religion.h"
50 #include "spl-clouds.h"
51 #include "spl-goditem.h"
52 #include "spl-miscast.h"
53 #include "spl-monench.h"
54 #include "spl-wpnench.h"
55 #include "state.h"
56 #include "stringutil.h"
57 #include "tag-version.h"
58 #include "teleport.h"
59 #include "terrain.h"
60 #include "transform.h"
61 #include "traps.h"
62 #include "uncancel.h"
63 #include "unicode.h"
64 #include "view.h"
65 #include "viewchar.h"
66 #include "xom.h"
67
68 using namespace ui;
69
70 typedef map<card_type, int> deck_archetype;
71
72 deck_archetype deck_of_escape =
73 {
74 { CARD_TOMB, 5 },
75 { CARD_EXILE, 3 },
76 { CARD_ELIXIR, 5 },
77 { CARD_CLOUD, 5 },
78 { CARD_VELOCITY, 5 },
79 };
80
81 deck_archetype deck_of_destruction =
82 {
83 { CARD_VITRIOL, 5 },
84 { CARD_PAIN, 5 },
85 { CARD_ORB, 5 },
86 { CARD_DEGEN, 3 },
87 { CARD_WILD_MAGIC, 5 },
88 { CARD_STORM, 5 },
89 };
90
91 deck_archetype deck_of_summoning =
92 {
93 { CARD_ELEMENTS, 5 },
94 { CARD_SUMMON_DEMON, 5 },
95 { CARD_SUMMON_WEAPON, 5 },
96 { CARD_SUMMON_BEE, 5 },
97 { CARD_RANGERS, 5 },
98 { CARD_ILLUSION, 5 },
99 };
100
101 deck_archetype deck_of_punishment =
102 {
103 { CARD_WRAITH, 5 },
104 { CARD_WRATH, 5 },
105 { CARD_SWINE, 5 },
106 { CARD_TORMENT, 5 },
107 };
108
109 struct deck_type_data
110 {
111 string name;
112 string flavour;
113 /// The list of cards this deck contains.
114 deck_archetype cards;
115 int deck_max;
116 };
117
118 static map<deck_type, deck_type_data> all_decks =
119 {
120 { DECK_OF_ESCAPE, {
121 "escape", "mainly dealing with various forms of escape.",
122 deck_of_escape,
123 13,
124 } },
125 { DECK_OF_DESTRUCTION, {
126 "destruction", "most of which hurl death and destruction "
127 "at one's foes (or, if unlucky, at oneself).",
128 deck_of_destruction,
129 26,
130 } },
131 { DECK_OF_SUMMONING, {
132 "summoning", "depicting a range of weird and wonderful creatures.",
133 deck_of_summoning,
134 13,
135 } },
136 { DECK_OF_PUNISHMENT, {
137 "punishment", "which wreak havoc on the user.", deck_of_punishment,
138 0, // Not a user deck
139 } },
140 };
141
142 vector<ability_type> deck_ability = {
143 ABIL_NEMELEX_DRAW_ESCAPE,
144 ABIL_NEMELEX_DRAW_DESTRUCTION,
145 ABIL_NEMELEX_DRAW_SUMMONING,
146 ABIL_NON_ABILITY,
147 ABIL_NEMELEX_DRAW_STACK
148 };
149
card_name(card_type card)150 const char* card_name(card_type card)
151 {
152 switch (card)
153 {
154 case CARD_VELOCITY: return "Velocity";
155 case CARD_EXILE: return "Exile";
156 case CARD_ELIXIR: return "the Elixir";
157 case CARD_STAIRS: return "the Stairs";
158 case CARD_TOMB: return "the Tomb";
159 case CARD_WILD_MAGIC: return "Wild Magic";
160 case CARD_ELEMENTS: return "the Elements";
161 case CARD_SUMMON_DEMON: return "the Pentagram";
162 case CARD_SUMMON_WEAPON: return "the Dance";
163 case CARD_SUMMON_BEE: return "the Swarm";
164 case CARD_RANGERS: return "the Rangers";
165 case CARD_VITRIOL: return "Vitriol";
166 case CARD_CLOUD: return "the Cloud";
167 case CARD_STORM: return "the Storm";
168 case CARD_PAIN: return "Pain";
169 case CARD_TORMENT: return "Torment";
170 case CARD_WRATH: return "Wrath";
171 case CARD_WRAITH: return "the Wraith";
172 case CARD_SWINE: return "the Swine";
173 case CARD_ORB: return "the Orb";
174 case CARD_ILLUSION: return "the Illusion";
175 case CARD_DEGEN: return "Degeneration";
176
177 #if TAG_MAJOR_VERSION == 34
178 case CARD_FAMINE_REMOVED:
179 case CARD_SHAFT_REMOVED:
180 #endif
181 case NUM_CARDS: return "a buggy card";
182 }
183 return "a very buggy card";
184 }
185
name_to_card(string name)186 card_type name_to_card(string name)
187 {
188 for (int i = 0; i < NUM_CARDS; i++)
189 {
190 if (card_name(static_cast<card_type>(i)) == name)
191 return static_cast<card_type>(i);
192 }
193 return NUM_CARDS;
194 }
195
_cards_in_deck(deck_type deck)196 static const deck_archetype _cards_in_deck(deck_type deck)
197 {
198 deck_type_data *deck_data = map_find(all_decks, deck);
199
200 if (deck_data)
201 return deck_data->cards;
202
203 #ifdef ASSERTS
204 die("No cards found for %u", unsigned(deck));
205 #endif
206 return {};
207 }
208
stack_contents()209 const string stack_contents()
210 {
211 const auto& stack = you.props[NEMELEX_STACK_KEY].get_vector();
212
213 string output = "";
214 output += comma_separated_fn(
215 reverse_iterator<CrawlVector::const_iterator>(stack.end()),
216 reverse_iterator<CrawlVector::const_iterator>(stack.begin()),
217 [](const CrawlStoreValue& card) { return card_name((card_type)card.get_int()); });
218 if (!stack.empty())
219 output += ".";
220
221 return output;
222 }
223
stack_top()224 const string stack_top()
225 {
226 const auto& stack = you.props[NEMELEX_STACK_KEY].get_vector();
227 if (stack.empty())
228 return "none";
229 else
230 return card_name((card_type) stack[stack.size() - 1].get_int());
231 }
232
deck_contents(deck_type deck)233 const string deck_contents(deck_type deck)
234 {
235 if (deck == DECK_STACK)
236 return "Remaining cards: " + stack_contents();
237
238 string output = "It may contain the following cards: ";
239
240 // This way of doing things is intended to prevent a card
241 // that appears in multiple subdecks from showing up twice in the
242 // output.
243 set<card_type> cards;
244 const deck_archetype &pdeck =_cards_in_deck(deck);
245 for (const auto& cww : pdeck)
246 cards.insert(cww.first);
247
248 output += comma_separated_fn(cards.begin(), cards.end(), card_name);
249 output += ".";
250
251 return output;
252 }
253
deck_flavour(deck_type deck)254 const string deck_flavour(deck_type deck)
255 {
256 if (deck == DECK_STACK)
257 return "set aside for later.";
258
259 deck_type_data* deck_data = map_find(all_decks, deck);
260
261 if (deck_data)
262 return deck_data->flavour;
263
264 return "";
265 }
266
_random_card(deck_type deck)267 static card_type _random_card(deck_type deck)
268 {
269 const deck_archetype &pdeck = _cards_in_deck(deck);
270 return *random_choose_weighted(pdeck);
271 }
272
deck_cards(deck_type deck)273 int deck_cards(deck_type deck)
274 {
275 return deck == DECK_STACK ? you.props[NEMELEX_STACK_KEY].get_vector().size()
276 : you.props[deck_name(deck)].get_int();
277 }
278
gift_cards()279 bool gift_cards()
280 {
281 const int deal = random_range(MIN_GIFT_CARDS, MAX_GIFT_CARDS);
282 bool dealt_cards = false;
283
284 for (int i = 0; i < deal; i++)
285 {
286 deck_type choice = random_choose_weighted(
287 3, DECK_OF_DESTRUCTION,
288 1, DECK_OF_SUMMONING,
289 1, DECK_OF_ESCAPE);
290 if (deck_cards(choice) < all_decks[choice].deck_max)
291 {
292 you.props[deck_name(choice)]++;
293 dealt_cards = true;
294 }
295 }
296
297 return dealt_cards;
298 }
299
reset_cards()300 void reset_cards()
301 {
302 for (int i = FIRST_PLAYER_DECK; i <= LAST_PLAYER_DECK; i++)
303 you.props[deck_name((deck_type) i)] = 0;
304 you.props[NEMELEX_STACK_KEY].get_vector().clear();
305 }
306
deck_summary()307 string deck_summary()
308 {
309 vector<string> stats;
310 for (int i = FIRST_PLAYER_DECK; i <= LAST_PLAYER_DECK; i++)
311 {
312 int cards = deck_cards((deck_type) i);
313 const deck_type_data *deck_data = map_find(all_decks, (deck_type) i);
314 const string name = deck_data ? deck_data->name : "bugginess";
315 if (cards)
316 {
317 stats.push_back(make_stringf("%d %s card%s", cards,
318 name.c_str(), cards == 1 ? "" : "s"));
319 }
320 }
321 return comma_separated_line(stats.begin(), stats.end());
322 }
323
which_decks(card_type card)324 string which_decks(card_type card)
325 {
326 vector<string> decks;
327 string output = "\n";
328 bool punishment = false;
329 for (auto &deck_data : all_decks)
330 {
331 if (!deck_data.second.cards.count(card))
332 continue;
333
334 if (deck_data.first == DECK_OF_PUNISHMENT)
335 punishment = true;
336 else
337 decks.push_back(deck_data.second.name);
338 }
339
340 if (!decks.empty())
341 {
342 output += "It is found in decks of "
343 + comma_separated_line(decks.begin(), decks.end());
344 if (punishment)
345 output += ", or in Nemelex Xobeh's deck of punishment";
346 output += ".";
347 }
348 else if (punishment)
349 {
350 output += "It is only found in Nemelex Xobeh's deck of "
351 "punishment.";
352 }
353 else
354 output += "It is normally not part of any deck.";
355
356 return output;
357 }
358
_describe_cards(CrawlVector & cards)359 static void _describe_cards(CrawlVector& cards)
360 {
361 ASSERT(!cards.empty());
362
363 auto scroller = make_shared<Scroller>();
364 auto vbox = make_shared<Box>(Widget::VERT);
365
366 #ifdef USE_TILE_WEB
367 tiles.json_open_object();
368 tiles.json_open_array("cards");
369 #endif
370 bool seen[NUM_CARDS] = {0};
371 ostringstream data;
372 bool first = true;
373 for (auto& val : cards)
374 {
375 card_type card = (card_type) val.get_int();
376
377 if (seen[card])
378 continue;
379 seen[card] = true;
380
381 string name = card_name(card);
382 string desc = getLongDescription(name + " card");
383 if (desc.empty())
384 desc = "No description found.\n";
385 string decks = which_decks(card);
386
387 name = uppercase_first(name);
388 desc = desc + decks;
389
390 auto title_hbox = make_shared<Box>(Widget::HORZ);
391 #ifdef USE_TILE
392 auto icon = make_shared<Image>();
393 icon->set_tile(tile_def(TILEG_NEMELEX_CARD));
394 title_hbox->add_child(move(icon));
395 #endif
396 auto title = make_shared<Text>(formatted_string(name, WHITE));
397 title->set_margin_for_sdl(0, 0, 0, 10);
398 title_hbox->add_child(move(title));
399 title_hbox->set_cross_alignment(Widget::CENTER);
400 title_hbox->set_margin_for_crt(first ? 0 : 1, 0);
401 title_hbox->set_margin_for_sdl(first ? 0 : 20, 0);
402 vbox->add_child(move(title_hbox));
403
404 auto text = make_shared<Text>(desc);
405 text->set_wrap_text(true);
406 vbox->add_child(move(text));
407
408 #ifdef USE_TILE_WEB
409 tiles.json_open_object();
410 tiles.json_write_string("name", name);
411 tiles.json_write_string("desc", desc);
412 tiles.json_close_object();
413 #endif
414 first = false;
415 }
416
417 #ifdef USE_TILE_LOCAL
418 vbox->max_size().width = tiles.get_crt_font()->char_width()*80;
419 #endif
420
421 scroller->set_child(move(vbox));
422 auto popup = make_shared<ui::Popup>(scroller);
423
424 bool done = false;
425 popup->on_keydown_event([&done, &scroller](const KeyEvent& ev) {
426 done = !scroller->on_event(ev);
427 return true;
428 });
429
430 #ifdef USE_TILE_WEB
431 tiles.json_close_array();
432 tiles.push_ui_layout("describe-cards", 0);
433 popup->on_layout_pop([](){ tiles.pop_ui_layout(); });
434 #endif
435
436 ui::run_layout(move(popup), done);
437 }
438
deck_status(deck_type deck)439 string deck_status(deck_type deck)
440 {
441 const string name = deck_name(deck);
442 const int cards = deck_cards(deck);
443
444 ostringstream desc;
445
446 desc << chop_string(deck_name(deck), 24)
447 << to_string(cards);
448
449 return trimmed_string(desc.str());
450 }
451
deck_description(deck_type deck)452 string deck_description(deck_type deck)
453 {
454 ostringstream desc;
455
456 desc << "A deck of magical cards, ";
457 desc << deck_flavour(deck) << "\n\n";
458 desc << deck_contents(deck) << "\n";
459
460 if (deck != DECK_STACK)
461 {
462 const int cards = deck_cards(deck);
463 desc << "\n";
464
465 if (cards > 1)
466 desc << make_stringf("It currently has %d cards ", cards);
467 else if (cards == 1)
468 desc << "It currently has 1 card ";
469 else
470 desc << "It is currently empty ";
471
472 desc << make_stringf("and can contain up to %d cards.",
473 all_decks[deck].deck_max);
474 desc << "\n";
475 }
476
477 return desc.str();
478 }
479
480 /**
481 * The deck a given ability uses. Asserts if called on an ability that does not
482 * use decks.
483 *
484 * @param abil the ability
485 *
486 * @return the deck
487 */
ability_deck(ability_type abil)488 deck_type ability_deck(ability_type abil)
489 {
490 auto deck = find(deck_ability.begin(), deck_ability.end(), abil);
491
492 ASSERT(deck != deck_ability.end());
493 return (deck_type) distance(deck_ability.begin(), deck);
494 }
495
496 // This will assert if the player doesn't have the ability to draw from the
497 // deck passed.
_deck_hotkey(deck_type deck)498 static char _deck_hotkey(deck_type deck)
499 {
500 return get_talent(deck_ability[deck], false).hotkey;
501 }
502
_choose_deck(const string title="Draw")503 static deck_type _choose_deck(const string title = "Draw")
504 {
505 ToggleableMenu deck_menu(MF_SINGLESELECT
506 | MF_NO_WRAP_ROWS | MF_TOGGLE_ACTION | MF_ALWAYS_SHOW_MORE);
507 {
508 ToggleableMenuEntry* me =
509 new ToggleableMenuEntry(make_stringf("%s which deck? "
510 "Cards available", title.c_str()),
511 "Describe which deck? "
512 "Cards available",
513 MEL_TITLE);
514 deck_menu.set_title(me, true, true);
515 }
516 deck_menu.set_tag("deck");
517 deck_menu.add_toggle_key('!');
518 deck_menu.add_toggle_key('?');
519 deck_menu.menu_action = Menu::ACT_EXECUTE;
520
521 deck_menu.set_more(formatted_string::parse_string(
522 "Press '<w>!</w>' or '<w>?</w>' to toggle "
523 "between deck selection and description."));
524
525 int numbers[NUM_DECKS];
526
527 for (int i = FIRST_PLAYER_DECK; i <= LAST_PLAYER_DECK; i++)
528 {
529 ToggleableMenuEntry* me =
530 new ToggleableMenuEntry(deck_status(static_cast<deck_type>(i)),
531 deck_status(static_cast<deck_type>(i)),
532 MEL_ITEM, 1, _deck_hotkey(static_cast<deck_type>(i)));
533 numbers[i] = i;
534 me->data = &numbers[i];
535 if (!deck_cards((deck_type)i))
536 me->colour = COL_USELESS;
537
538 me->add_tile(tile_def(TILEG_NEMELEX_DECK + i - FIRST_PLAYER_DECK + 1));
539 deck_menu.add_entry(me);
540 }
541
542 int ret = NUM_DECKS;
543 deck_menu.on_single_selection = [&deck_menu, &ret](const MenuEntry& sel)
544 {
545 ASSERT(sel.hotkeys.size() == 1);
546 int selected = *(static_cast<int*>(sel.data));
547
548 if (deck_menu.menu_action == Menu::ACT_EXAMINE)
549 describe_deck((deck_type) selected);
550 else
551 ret = *(static_cast<int*>(sel.data));
552 return deck_menu.menu_action == Menu::ACT_EXAMINE;
553 };
554 deck_menu.show(false);
555 if (!crawl_state.doing_prev_cmd_again)
556 {
557 redraw_screen();
558 update_screen();
559 }
560 return (deck_type) ret;
561 }
562
563 /**
564 * Printed when a deck is exhausted
565 *
566 * @return A message to print;
567 * e.g. "the deck of cards disappears without a trace."
568 */
_empty_deck_msg()569 static string _empty_deck_msg()
570 {
571 string message = random_choose("disappears without a trace.",
572 "glows slightly and disappears.",
573 "glows with a rainbow of weird colours and disappears.");
574 return "The deck of cards " + message;
575 }
576
_evoke_deck(deck_type deck,bool dealt=false)577 static void _evoke_deck(deck_type deck, bool dealt = false)
578 {
579 ASSERT(deck_cards(deck) > 0);
580
581 mprf("You %s a card...", dealt ? "deal" : "draw");
582
583 if (deck == DECK_STACK)
584 {
585 auto& stack = you.props[NEMELEX_STACK_KEY].get_vector();
586 card_type card = (card_type) stack[stack.size() - 1].get_int();
587 stack.pop_back();
588 card_effect(card, dealt);
589 }
590 else
591 {
592 --you.props[deck_name(deck)];
593 card_effect(_random_card(deck), dealt);
594 }
595
596 if (!deck_cards(deck))
597 mpr(_empty_deck_msg());
598 }
599
600 // Draw one card from a deck, prompting the user for a choice
deck_draw(deck_type deck)601 bool deck_draw(deck_type deck)
602 {
603 if (!deck_cards(deck))
604 {
605 mpr("That deck is empty!");
606 return false;
607 }
608
609 _evoke_deck(deck);
610 return true;
611 }
612
deck_stack()613 bool deck_stack()
614 {
615 if (crawl_state.is_replaying_keys())
616 {
617 crawl_state.cancel_cmd_all("You can't repeat Stack Five.");
618 return false;
619 }
620
621 int total_cards = 0;
622
623 for (int i = FIRST_PLAYER_DECK; i <= LAST_PLAYER_DECK; ++i)
624 total_cards += deck_cards((deck_type) i);
625
626 if (deck_cards(DECK_STACK) && !yesno("Replace your current stack?",
627 false, 0))
628 {
629 canned_msg(MSG_OK);
630 return false;
631 }
632
633 if (!total_cards)
634 {
635 mpr("You are out of cards!");
636 return false;
637 }
638
639 if (total_cards < 5 && !yesno("You have fewer than five cards, "
640 "stack them anyway?", false, 0))
641 {
642 canned_msg(MSG_OK);
643 return false;
644 }
645
646 you.props[NEMELEX_STACK_KEY].get_vector().clear();
647 run_uncancel(UNC_STACK_FIVE, min(total_cards, 5));
648 return true;
649 }
650
651 class StackFiveMenu : public Menu
652 {
653 virtual bool process_key(int keyin) override;
654 CrawlVector& draws;
655 public:
StackFiveMenu(CrawlVector & d)656 StackFiveMenu(CrawlVector& d)
657 : Menu(MF_NOSELECT | MF_UNCANCEL | MF_ALWAYS_SHOW_MORE), draws(d) {};
658 };
659
process_key(int keyin)660 bool StackFiveMenu::process_key(int keyin)
661 {
662 if (keyin == CK_ENTER)
663 {
664 formatted_string old_more = more;
665 set_more(formatted_string::parse_string(
666 "Are you done? (press y or Y to confirm)"));
667 if (yesno(nullptr, true, 'n', false, false, true))
668 return false;
669 set_more(old_more);
670 }
671 else if (keyin == '?')
672 _describe_cards(draws);
673 else if (keyin >= '1' && keyin <= '0' + static_cast<int>(draws.size()))
674 {
675 const unsigned int i = keyin - '1';
676 for (unsigned int j = 0; j < items.size(); j++)
677 if (items[j]->selected())
678 {
679 swap(draws[i], draws[j]);
680 swap(items[i]->text, items[j]->text);
681 items[j]->colour = LIGHTGREY;
682 select_item_index(i, 0, false); // this also updates the item
683 select_item_index(j, 0, false);
684 return true;
685 }
686 items[i]->colour = WHITE;
687 select_item_index(i, 1, false);
688 }
689 else
690 Menu::process_key(keyin);
691 return true;
692 }
693
_draw_stack(int to_stack)694 static void _draw_stack(int to_stack)
695 {
696 ToggleableMenu deck_menu(MF_SINGLESELECT | MF_UNCANCEL
697 | MF_NO_WRAP_ROWS | MF_TOGGLE_ACTION | MF_ALWAYS_SHOW_MORE);
698 {
699 ToggleableMenuEntry* me =
700 new ToggleableMenuEntry("Draw which deck? "
701 "Cards available",
702 "Describe which deck? "
703 "Cards available",
704 MEL_TITLE);
705 deck_menu.set_title(me, true, true);
706 }
707 deck_menu.set_tag("deck");
708 deck_menu.add_toggle_key('!');
709 deck_menu.add_toggle_key('?');
710 deck_menu.menu_action = Menu::ACT_EXECUTE;
711
712 auto& stack = you.props[NEMELEX_STACK_KEY].get_vector();
713
714 if (!stack.empty())
715 {
716 string status = "Drawn so far: " + stack_contents();
717 deck_menu.set_more(formatted_string::parse_string(
718 status + "\n" +
719 "Press '<w>!</w>' or '<w>?</w>' to toggle "
720 "between deck selection and description."));
721 }
722 else
723 {
724 deck_menu.set_more(formatted_string::parse_string(
725 "Press '<w>!</w>' or '<w>?</w>' to toggle "
726 "between deck selection and description."));
727 }
728
729 int numbers[NUM_DECKS];
730
731 for (int i = FIRST_PLAYER_DECK; i <= LAST_PLAYER_DECK; i++)
732 {
733 ToggleableMenuEntry* me =
734 new ToggleableMenuEntry(deck_status((deck_type)i),
735 deck_status((deck_type)i),
736 MEL_ITEM, 1, _deck_hotkey((deck_type)i));
737 numbers[i] = i;
738 me->data = &numbers[i];
739 // TODO: update this if a deck is emptied while in this menu
740 if (!deck_cards((deck_type)i))
741 me->colour = COL_USELESS;
742
743 me->add_tile(tile_def(TILEG_NEMELEX_DECK + i - FIRST_PLAYER_DECK + 1));
744 deck_menu.add_entry(me);
745 }
746 deck_menu.on_single_selection = [&deck_menu, &stack, to_stack](const MenuEntry& sel)
747 {
748 ASSERT(sel.hotkeys.size() == 1);
749 deck_type selected = (deck_type) *(static_cast<int*>(sel.data));
750 // Need non-const access to the selection.
751 ToggleableMenuEntry* me =
752 static_cast<ToggleableMenuEntry*>(deck_menu.selected_entries()[0]);
753
754 if (deck_menu.menu_action == Menu::ACT_EXAMINE)
755 describe_deck(selected);
756 else
757 {
758 string status;
759 if (deck_cards(selected))
760 {
761 you.props[deck_name(selected)]--;
762 me->text = deck_status(selected);
763 me->alt_text = deck_status(selected);
764
765 card_type draw = _random_card(selected);
766 stack.push_back(draw);
767 }
768 else
769 status = "<lightred>That deck is empty!</lightred> ";
770
771 if (stack.size() > 0)
772 status += "Drawn so far: " + stack_contents();
773 deck_menu.set_more(formatted_string::parse_string(
774 status + "\n" +
775 "Press '<w>!</w>' or '<w>?</w>' to toggle "
776 "between deck selection and description."));
777 }
778 return stack.size() < to_stack
779 || deck_menu.menu_action == Menu::ACT_EXAMINE;
780 };
781 deck_menu.show(false);
782 }
783
stack_five(int to_stack)784 bool stack_five(int to_stack)
785 {
786 auto& stack = you.props[NEMELEX_STACK_KEY].get_vector();
787
788 while (stack.size() < to_stack)
789 {
790 if (crawl_state.seen_hups)
791 return false;
792
793 _draw_stack(to_stack);
794 }
795
796 StackFiveMenu menu(stack);
797 MenuEntry *const title = new MenuEntry("Select two cards to swap them:", MEL_TITLE);
798 menu.set_title(title);
799 for (unsigned int i = 0; i < stack.size(); i++)
800 {
801 MenuEntry * const entry =
802 new MenuEntry(card_name((card_type)stack[i].get_int()),
803 MEL_ITEM, 1, '1'+i);
804 entry->add_tile(tile_def(TILEG_NEMELEX_CARD));
805 menu.add_entry(entry);
806 }
807 menu.set_more(formatted_string::parse_string(
808 "<lightgrey>Press <w>?</w> for the card descriptions"
809 " or <w>Enter</w> to accept."));
810 menu.show();
811
812 if (crawl_state.seen_hups)
813 return false;
814 else
815 {
816 std::reverse(stack.begin(), stack.end());
817 return true;
818 }
819 }
820
821 // Draw the top four cards of an deck and play them all.
822 // Return false if the operation was failed/aborted along the way.
deck_deal()823 bool deck_deal()
824 {
825 deck_type choice = _choose_deck("Deal");
826
827 if (choice == NUM_DECKS)
828 return false;
829
830 int num_cards = deck_cards(choice);
831
832 if (!num_cards)
833 {
834 mpr("That deck is empty!");
835 return false;
836 }
837
838 const int num_to_deal = min(num_cards, 4);
839
840 for (int i = 0; i < num_to_deal; ++i)
841 _evoke_deck(choice, true);
842
843 return true;
844 }
845
846 // Draw the next three cards, discard two and pick one.
deck_triple_draw()847 bool deck_triple_draw()
848 {
849 if (crawl_state.is_replaying_keys())
850 {
851 crawl_state.cancel_cmd_all("You can't repeat Triple Draw.");
852 return false;
853 }
854
855 deck_type choice = _choose_deck();
856
857 if (choice == NUM_DECKS)
858 return false;
859
860 int num_cards = deck_cards(choice);
861
862 if (!num_cards)
863 {
864 mpr("That deck is empty!");
865 return false;
866 }
867
868 if (num_cards < 3 && !yesno("There's fewer than three cards, "
869 "still triple draw?", false, 0))
870 {
871 canned_msg(MSG_OK);
872 return false;
873 }
874
875 if (num_cards == 1)
876 {
877 // Only one card to draw, so just draw it.
878 mpr("There's only one card left!");
879 _evoke_deck(choice);
880 return true;
881 }
882
883 const int num_to_draw = min(num_cards, 3);
884
885 you.props[deck_name(choice)] = deck_cards(choice) - num_to_draw;
886
887 auto& draw = you.props[NEMELEX_TRIPLE_DRAW_KEY].get_vector();
888 draw.clear();
889
890 for (int i = 0; i < num_to_draw; ++i)
891 draw.push_back(_random_card(choice));
892
893 run_uncancel(UNC_DRAW_THREE, 0);
894 return true;
895 }
896
draw_three()897 bool draw_three()
898 {
899 auto& draws = you.props[NEMELEX_TRIPLE_DRAW_KEY].get_vector();
900
901 int selected = -1;
902 bool need_prompt_redraw = true;
903 while (true)
904 {
905 if (need_prompt_redraw)
906 {
907 mpr("You draw... (choose one card, ? for their descriptions)");
908 for (int i = 0; i < draws.size(); ++i)
909 {
910 msg::streams(MSGCH_PROMPT)
911 << msg::nocap << (static_cast<char>(i + 'a')) << " - "
912 << card_name((card_type)draws[i].get_int()) << endl;
913 }
914 need_prompt_redraw = false;
915 }
916 const int keyin = toalower(get_ch());
917
918 if (crawl_state.seen_hups)
919 return false;
920
921 if (keyin == '?')
922 {
923 _describe_cards(draws);
924 redraw_screen();
925 update_screen();
926 need_prompt_redraw = true;
927 }
928 else if (keyin >= 'a' && keyin < 'a' + draws.size())
929 {
930 selected = keyin - 'a';
931 break;
932 }
933 else
934 canned_msg(MSG_HUH);
935 }
936
937 card_effect((card_type) draws[selected].get_int());
938
939 return true;
940 }
941
942 // This is Nemelex retribution. If deal is true, use the word "deal"
943 // rather than "draw" (for the Deal Four out-of-cards situation).
draw_from_deck_of_punishment(bool deal)944 void draw_from_deck_of_punishment(bool deal)
945 {
946 card_type card = _random_card(DECK_OF_PUNISHMENT);
947
948 mprf("You %s a card...", deal ? "deal" : "draw");
949 card_effect(card, deal, true);
950 }
951
_get_power_level(int power)952 static int _get_power_level(int power)
953 {
954 int power_level = x_chance_in_y(power, 900) + x_chance_in_y(power, 2700);
955
956 // other functions in this file will break if this assertion is violated
957 ASSERT(power_level >= 0 && power_level <= 2);
958 dprf("power level: %d", power_level);
959 return power_level;
960 }
961
962 // Actual card implementations follow.
963
_velocity_card(int power)964 static void _velocity_card(int power)
965 {
966
967 const int power_level = _get_power_level(power);
968 bool did_something = false;
969
970 if (you.duration[DUR_SLOW] && x_chance_in_y(power_level, 2))
971 {
972 you.duration[DUR_SLOW] = 1;
973 did_something = true;
974 }
975
976 if (!apply_visible_monsters([=](monster& mon)
977 {
978 bool affected = false;
979 if (!mons_invuln_will(mon))
980 {
981 const bool hostile = !mon.wont_attack();
982 const bool haste_immune = (mon.stasis()
983 || mons_is_immotile(mon));
984
985 bool did_haste = false;
986
987 if (hostile)
988 {
989 if (x_chance_in_y(1 + power_level, 3))
990 {
991 do_slow_monster(mon, &you);
992 affected = true;
993 }
994 }
995 else //allies
996 {
997 if (!haste_immune && x_chance_in_y(power_level, 2))
998 {
999 mon.add_ench(ENCH_HASTE);
1000 affected = true;
1001 did_haste = true;
1002 }
1003 }
1004
1005 if (did_haste)
1006 simple_monster_message(mon, " seems to speed up.");
1007 }
1008 return affected;
1009 })
1010 && !did_something)
1011 {
1012 canned_msg(MSG_NOTHING_HAPPENS);
1013 }
1014 }
1015
_exile_card(int power)1016 static void _exile_card(int power)
1017 {
1018 if (player_in_branch(BRANCH_ABYSS))
1019 {
1020 canned_msg(MSG_NOTHING_HAPPENS);
1021 return;
1022 }
1023
1024 // Calculate how many extra banishments you get.
1025 const int power_level = _get_power_level(power);
1026 int extra_targets = power_level + random2(1 + power_level);
1027
1028 for (int i = 0; i < 1 + extra_targets; ++i)
1029 {
1030 // Pick a random monster nearby to banish.
1031 monster* mon_to_banish = choose_random_nearby_monster(1);
1032
1033 // Bonus banishments only banish monsters.
1034 if (i != 0 && !mon_to_banish)
1035 continue;
1036
1037 if (!mon_to_banish)
1038 break; // Don't banish anything else.
1039 else
1040 mon_to_banish->banish(&you);
1041 }
1042 }
1043
1044 static int stair_draw_count = 0;
1045
1046 // This does not describe an actual card. Instead, it only exists to test
1047 // the stair movement effect in wizard mode ("&c stairs").
_stairs_card(int)1048 static void _stairs_card(int /*power*/)
1049 {
1050 you.duration[DUR_REPEL_STAIRS_MOVE] = 0;
1051 you.duration[DUR_REPEL_STAIRS_CLIMB] = 0;
1052
1053 if (feat_stair_direction(env.grid(you.pos())) == CMD_NO_CMD)
1054 you.duration[DUR_REPEL_STAIRS_MOVE] = 1000;
1055 else
1056 you.duration[DUR_REPEL_STAIRS_CLIMB] = 500; // more annoying
1057
1058 vector<coord_def> stairs_avail;
1059
1060 for (radius_iterator ri(you.pos(), LOS_DEFAULT, true); ri; ++ri)
1061 {
1062 dungeon_feature_type feat = env.grid(*ri);
1063 if (feat_stair_direction(feat) != CMD_NO_CMD
1064 && feat != DNGN_ENTER_SHOP)
1065 {
1066 stairs_avail.push_back(*ri);
1067 }
1068 }
1069
1070 if (stairs_avail.empty())
1071 {
1072 mpr("No stairs available to move.");
1073 return;
1074 }
1075
1076 shuffle_array(stairs_avail);
1077
1078 for (coord_def stair : stairs_avail)
1079 move_stair(stair, stair_draw_count % 2, false);
1080
1081 stair_draw_count++;
1082 }
1083
_friendly(monster_type mt,int dur)1084 static monster* _friendly(monster_type mt, int dur)
1085 {
1086 return create_monster(mgen_data(mt, BEH_FRIENDLY, you.pos(), MHITYOU,
1087 MG_AUTOFOE)
1088 .set_summoned(&you, dur, 0));
1089 }
1090
_damaging_card(card_type card,int power,bool dealt=false)1091 static void _damaging_card(card_type card, int power,
1092 bool dealt = false)
1093 {
1094 const int power_level = _get_power_level(power);
1095 const char *participle = dealt ? "dealt" : "drawn";
1096
1097 bool done_prompt = false;
1098 string prompt = make_stringf("You have %s %s.", participle,
1099 card_name(card));
1100
1101 dist target;
1102 zap_type ztype = ZAP_DEBUGGING_RAY;
1103 const zap_type painzaps[2] = { ZAP_AGONY, ZAP_BOLT_OF_DRAINING };
1104 const zap_type acidzaps[3] = { ZAP_BREATHE_ACID, ZAP_CORROSIVE_BOLT,
1105 ZAP_CORROSIVE_BOLT };
1106
1107 switch (card)
1108 {
1109 case CARD_VITRIOL:
1110 if (power_level == 2)
1111 {
1112 done_prompt = true;
1113 mpr(prompt);
1114 mpr("You radiate a wave of entropy!");
1115 apply_visible_monsters([](monster& mons)
1116 {
1117 return !mons.wont_attack()
1118 && mons_is_threatening(mons)
1119 && coinflip()
1120 && mons.corrode_equipment();
1121 });
1122 }
1123 ztype = acidzaps[power_level];
1124 break;
1125
1126 case CARD_ORB:
1127 ztype = ZAP_IOOD;
1128 break;
1129
1130 case CARD_PAIN:
1131 if (power_level == 2)
1132 {
1133 mpr("You reveal a symbol of torment!");
1134 torment(&you, TORMENT_CARD_PAIN, you.pos());
1135 }
1136
1137 ztype = painzaps[min(power_level, (int)ARRAYSZ(painzaps)-1)];
1138 break;
1139
1140 default:
1141 break;
1142 }
1143
1144 bolt beam;
1145 beam.range = LOS_RADIUS;
1146
1147 direction_chooser_args args;
1148 args.mode = TARG_HOSTILE;
1149 if (!done_prompt)
1150 args.top_prompt = prompt;
1151
1152 // Confirm aborts as they waste the card.
1153 prompt = make_stringf("Aiming: %s", card_name(card));
1154 while (!(spell_direction(target, beam, &args)
1155 && player_tracer(ZAP_DEBUGGING_RAY, power/6, beam)))
1156 {
1157 if (crawl_state.seen_hups
1158 || yesno("Really abort (and waste the card)?", false, 0))
1159 {
1160 canned_msg(MSG_OK);
1161 return;
1162 }
1163 args.top_prompt = prompt;
1164 }
1165
1166 if (ztype == ZAP_IOOD)
1167 {
1168 if (power_level == 1)
1169 {
1170 cast_iood(&you, power/6, &beam, 0, 0,
1171 env.mgrid(beam.target), false, false);
1172 }
1173 else
1174 cast_iood_burst(power/6, beam.target);
1175 }
1176 else
1177 zapping(ztype, power/6, beam);
1178 }
1179
_elixir_card(int power)1180 static void _elixir_card(int power)
1181 {
1182 int power_level = _get_power_level(power);
1183
1184 you.set_duration(DUR_ELIXIR, 1 + 3 * power_level + random2(3));
1185 mpr("You begin rapidly regenerating health and magic.");
1186 }
1187
1188 // Special case for *your* god, maybe?
_godly_wrath()1189 static void _godly_wrath()
1190 {
1191 for (int tries = 0; tries < 100; tries++)
1192 {
1193 god_type god = random_god();
1194
1195 // Don't recursively make player draw from the Deck of Punishment.
1196 if (god != GOD_NEMELEX_XOBEH && divine_retribution(god))
1197 return; // Stop once we find a god willing to punish the player.
1198 }
1199
1200 mpr("You somehow manage to escape divine attention...");
1201 }
1202
_summon_demon_card(int power)1203 static void _summon_demon_card(int power)
1204 {
1205 const int power_level = _get_power_level(power);
1206 // one demon (potentially hostile), and one other demonic creature (always
1207 // friendly)
1208 monster_type dct, dct2;
1209 switch (power_level)
1210 {
1211 case 0:
1212 dct = random_demon_by_tier(4);
1213 dct2 = MONS_HELL_HOUND;
1214 break;
1215 case 1:
1216 dct = random_demon_by_tier(3);
1217 dct2 = MONS_RAKSHASA;
1218 break;
1219 default:
1220 dct = random_demon_by_tier(2);
1221 dct2 = MONS_PANDEMONIUM_LORD;
1222 }
1223
1224 // FIXME: The manual testing for message printing is there because
1225 // we can't rely on create_monster() to do it for us. This is
1226 // because if you are completely surrounded by walls, create_monster()
1227 // will never manage to give a position which isn't (-1,-1)
1228 // and thus not print the message.
1229 // This hack appears later in this file as well.
1230
1231 const bool hostile = one_chance_in(power_level + 4);
1232
1233 if (!create_monster(mgen_data(dct, hostile ? BEH_HOSTILE : BEH_FRIENDLY,
1234 you.pos(), MHITYOU, MG_AUTOFOE)
1235 .set_summoned(&you, 5 - power_level, 0)))
1236 {
1237 mpr("You see a puff of smoke.");
1238 }
1239 else if (hostile
1240 && mons_class_flag(dct, M_INVIS)
1241 && !you.can_see_invisible())
1242 {
1243 mpr("You sense the presence of something unfriendly.");
1244 }
1245
1246 _friendly(dct2, 5 - power_level);
1247 }
1248
_elements_card(int power)1249 static void _elements_card(int power)
1250 {
1251
1252 const int power_level = _get_power_level(power);
1253 const monster_type element_list[][3] =
1254 {
1255 {MONS_RAIJU, MONS_WIND_DRAKE, MONS_SHOCK_SERPENT},
1256 {MONS_BASILISK, MONS_CATOBLEPAS, MONS_IRON_GOLEM},
1257 {MONS_FIRE_VORTEX, MONS_MOLTEN_GARGOYLE, MONS_FIRE_DRAGON},
1258 {MONS_ICE_BEAST, MONS_POLAR_BEAR, MONS_ICE_DRAGON}
1259 };
1260
1261 int start = random2(ARRAYSZ(element_list));
1262 for (int i = 0; i < 3; ++i)
1263 {
1264 _friendly(element_list[start % ARRAYSZ(element_list)][power_level],
1265 power_level + 2);
1266 start++;
1267 }
1268
1269 }
1270
_summon_dancing_weapon(int power)1271 static void _summon_dancing_weapon(int power)
1272 {
1273 const int power_level = _get_power_level(power);
1274
1275 monster *mon =
1276 create_monster(
1277 mgen_data(MONS_DANCING_WEAPON, BEH_FRIENDLY, you.pos(), MHITYOU,
1278 MG_AUTOFOE).set_summoned(&you, power_level + 2, 0),
1279 false);
1280
1281 if (!mon)
1282 {
1283 mpr("You see a puff of smoke.");
1284 return;
1285 }
1286
1287 // Override the weapon.
1288 ASSERT(mon->weapon() != nullptr);
1289 item_def& wpn(*mon->weapon());
1290
1291 switch (power_level)
1292 {
1293 case 0:
1294 // Wimpy, negative-enchantment weapon.
1295 wpn.plus = random2(3) - 2;
1296 wpn.sub_type = random_choose(WPN_QUARTERSTAFF, WPN_HAND_AXE);
1297
1298 set_item_ego_type(wpn, OBJ_WEAPONS,
1299 random_choose(SPWPN_VENOM, SPWPN_NORMAL));
1300 break;
1301 case 1:
1302 // This is getting good.
1303 wpn.plus = random2(4) - 1;
1304 wpn.sub_type = random_choose(WPN_LONG_SWORD, WPN_TRIDENT);
1305
1306 if (coinflip())
1307 {
1308 set_item_ego_type(wpn, OBJ_WEAPONS,
1309 random_choose(SPWPN_FLAMING, SPWPN_FREEZING));
1310 }
1311 else
1312 set_item_ego_type(wpn, OBJ_WEAPONS, SPWPN_NORMAL);
1313 break;
1314 default:
1315 // Rare and powerful.
1316 wpn.plus = random2(4) + 2;
1317 wpn.sub_type = random_choose(WPN_DEMON_TRIDENT, WPN_EXECUTIONERS_AXE);
1318
1319 set_item_ego_type(wpn, OBJ_WEAPONS,
1320 random_choose(SPWPN_SPEED, SPWPN_ELECTROCUTION));
1321 }
1322
1323 item_colour(wpn); // this is probably not needed
1324
1325 // sometimes give a randart instead
1326 if (one_chance_in(3))
1327 {
1328 make_item_randart(wpn, true);
1329 set_ident_flags(wpn, ISFLAG_KNOW_PROPERTIES| ISFLAG_KNOW_TYPE);
1330 }
1331
1332 // Don't leave a trail of weapons behind. (Especially not randarts!)
1333 mon->flags |= MF_HARD_RESET;
1334
1335 ghost_demon newstats;
1336 newstats.init_dancing_weapon(wpn, power / 4);
1337
1338 mon->set_ghost(newstats);
1339 mon->ghost_demon_init();
1340 }
1341
_summon_bee(int power)1342 static void _summon_bee(int power)
1343 {
1344 const int power_level = _get_power_level(power);
1345 const int how_many = 1 + random2((power_level + 1) * 3);
1346
1347 for (int i = 0; i < how_many; ++i)
1348 _friendly(MONS_KILLER_BEE, 3);
1349 }
1350
_summon_rangers(int power)1351 static void _summon_rangers(int power)
1352 {
1353 const int power_level = _get_power_level(power);
1354 const monster_type dctr = random_choose(MONS_CENTAUR, MONS_YAKTAUR),
1355 dctr2 = random_choose(MONS_CENTAUR_WARRIOR, MONS_FAUN),
1356 dctr3 = random_choose(MONS_YAKTAUR_CAPTAIN,
1357 MONS_NAGA_SHARPSHOOTER),
1358 dctr4 = random_choose(MONS_SATYR,
1359 MONS_MERFOLK_JAVELINEER,
1360 MONS_DEEP_ELF_MASTER_ARCHER);
1361
1362 const monster_type base_choice = power_level == 2 ? dctr2 :
1363 dctr;
1364 monster_type placed_choice = power_level == 2 ? dctr3 :
1365 power_level == 1 ? dctr2 :
1366 dctr;
1367 const bool extra_monster = coinflip();
1368
1369 if (!extra_monster && power_level > 0)
1370 placed_choice = power_level == 2 ? dctr4 : dctr3;
1371
1372 for (int i = 0; i < 1 + extra_monster; ++i)
1373 _friendly(base_choice, 5 - power_level);
1374
1375 _friendly(placed_choice, 5 - power_level);
1376 }
1377
_cloud_card(int power)1378 static void _cloud_card(int power)
1379 {
1380 const int power_level = _get_power_level(power);
1381 bool something_happened = false;
1382
1383 for (radius_iterator di(you.pos(), LOS_NO_TRANS); di; ++di)
1384 {
1385 monster *mons = monster_at(*di);
1386 cloud_type cloudy;
1387 cloudy = CLOUD_BLACK_SMOKE;
1388
1389 if (!mons || mons->wont_attack() || !mons_is_threatening(*mons))
1390 continue;
1391
1392 for (adjacent_iterator ai(mons->pos(), false); ai; ++ai)
1393 {
1394 if (env.grid(*ai) == DNGN_FLOOR && !cloud_at(*ai))
1395 {
1396 const int cloud_power = 5 + random2avg(power_level * 6, 2);
1397 place_cloud(cloudy, *ai, cloud_power, &you);
1398
1399 if (you.see_cell(*ai))
1400 something_happened = true;
1401 }
1402 }
1403 }
1404
1405 if (something_happened)
1406 mpr("Clouds appear around you!");
1407 else
1408 canned_msg(MSG_NOTHING_HAPPENS);
1409 }
1410
_storm_card(int power)1411 static void _storm_card(int power)
1412 {
1413 const int power_level = _get_power_level(power);
1414
1415 wind_blast(&you, (power_level + 1) * 66, coord_def(), true);
1416 redraw_screen(); // Update monster positions
1417 update_screen();
1418
1419 // 1-3, 4-6, 7-9
1420 const int max_explosions = random_range((power_level * 3) + 1, (power_level + 1) * 3);
1421 // Select targets based on simultaneously running max_explosions resivoir
1422 // samples from the radius iterator over valid targets.
1423 //
1424 // Once the possible targets are drawn, the result is deduplicated into a
1425 // set of targets.
1426 vector<coord_def> target_draws (max_explosions, you.pos());
1427 int valid_targets = 0;
1428 for (radius_iterator ri(you.pos(), LOS_NO_TRANS, true); ri; ++ri)
1429 {
1430 if (grid_distance(*ri, you.pos()) > 3 && !cell_is_solid(*ri))
1431 {
1432 ++valid_targets;
1433 for (int i = 0; i < max_explosions; ++i)
1434 {
1435 if (one_chance_in(valid_targets))
1436 target_draws[i] = *ri;
1437 }
1438 }
1439 }
1440
1441 unordered_set<coord_def> targets (target_draws.begin(), target_draws.end());
1442 targets.erase(you.pos());
1443
1444 bool heard = false;
1445 for (auto p : targets)
1446 {
1447 bolt beam;
1448 beam.flavour = BEAM_ELECTRICITY;
1449 beam.is_tracer = false;
1450 beam.is_explosion = true;
1451 beam.glyph = dchar_glyph(DCHAR_FIRED_BURST);
1452 beam.name = "electrical discharge";
1453 beam.aux_source = "the storm";
1454 beam.explode_noise_msg = "You hear a clap of thunder!";
1455 beam.real_flavour = beam.flavour;
1456 beam.colour = LIGHTCYAN;
1457 beam.source_id = MID_PLAYER;
1458 beam.thrower = KILL_YOU;
1459 beam.is_explosion = true;
1460 beam.ex_size = 3;
1461 beam.damage = dice_def(3, 9 + 9 * power_level);
1462 beam.source = p;
1463 beam.target = p;
1464 beam.explode();
1465 heard = heard || player_can_hear(p);
1466 }
1467 // Lots of loud bangs, even if everything is silenced get a message.
1468 // Thunder comes after the animation runs.
1469 if (targets.size() > 0)
1470 {
1471 vector<string> thunder_adjectives = { "mighty",
1472 "violent",
1473 "cataclysmic" };
1474 mprf("You %s %s%s peal%s of thunder!",
1475 heard ? "hear" : "feel",
1476 targets.size() > 1 ? "" : "a ",
1477 thunder_adjectives[power_level].c_str(),
1478 targets.size() > 1 ? "s" : "");
1479 }
1480 }
1481
_illusion_card(int power)1482 static void _illusion_card(int power)
1483 {
1484 const int power_level = _get_power_level(power);
1485 monster* mon = get_free_monster();
1486
1487 if (!mon || monster_at(you.pos()))
1488 return;
1489
1490 mon->type = MONS_PLAYER;
1491 mon->behaviour = BEH_SEEK;
1492 mon->attitude = ATT_FRIENDLY;
1493 mon->set_position(you.pos());
1494 mon->mid = MID_PLAYER;
1495 env.mgrid(you.pos()) = mon->mindex();
1496
1497 mons_summon_illusion_from(mon, (actor *)&you, SPELL_NO_SPELL, power_level);
1498 mon->reset();
1499 }
1500
_degeneration_card(int power)1501 static void _degeneration_card(int power)
1502 {
1503 const int power_level = _get_power_level(power);
1504
1505 if (!apply_visible_monsters([power_level](monster& mons)
1506 {
1507 if (mons.wont_attack() || !mons_is_threatening(mons))
1508 return false;
1509
1510 if (!x_chance_in_y((power_level + 1) * 5 + random2(5),
1511 mons.get_hit_dice()))
1512 {
1513 return false;
1514 }
1515
1516 if (mons.can_polymorph())
1517 {
1518 mons.polymorph(PPT_LESS);
1519 mons.malmutate("");
1520 }
1521 else
1522 {
1523 const int daze_time = (5 + 5 * power_level) * BASELINE_DELAY;
1524 mons.add_ench(mon_enchant(ENCH_DAZED, 0, &you, daze_time));
1525 simple_monster_message(mons,
1526 " is dazed by the mutagenic energy.");
1527 }
1528 return true;
1529 }))
1530 {
1531 canned_msg(MSG_NOTHING_HAPPENS);
1532 }
1533 }
1534
_wild_magic_card(int power)1535 static void _wild_magic_card(int power)
1536 {
1537 const int power_level = _get_power_level(power);
1538 int num_affected = 0;
1539
1540 for (radius_iterator di(you.pos(), LOS_NO_TRANS); di; ++di)
1541 {
1542 monster *mons = monster_at(*di);
1543
1544 if (!mons || mons->wont_attack() || !mons_is_threatening(*mons))
1545 continue;
1546
1547 if (x_chance_in_y((power_level + 1) * 5 + random2(5),
1548 mons->get_hit_dice()))
1549 {
1550 // skip summoning and tlocs, only destructive forces
1551 spschool type = random_choose(spschool::conjuration,
1552 spschool::fire,
1553 spschool::ice,
1554 spschool::earth,
1555 spschool::air,
1556 spschool::poison,
1557 spschool::transmutation,
1558 spschool::hexes,
1559 spschool::necromancy);
1560
1561 miscast_effect(*mons, &you,
1562 {miscast_source::deck}, type,
1563 3 * (power_level + 1), random2(70),
1564 "a card of wild magic");
1565
1566 num_affected++;
1567 }
1568 }
1569
1570 if (num_affected > 0)
1571 {
1572 int mp = 0;
1573
1574 for (int i = 0; i < num_affected; ++i)
1575 mp += random2(5);
1576
1577 mpr("You feel a surge of magic.");
1578 if (mp && you.magic_points < you.max_magic_points)
1579 {
1580 inc_mp(mp);
1581 canned_msg(MSG_GAIN_MAGIC);
1582 }
1583 }
1584 else
1585 canned_msg(MSG_NOTHING_HAPPENS);
1586 }
1587
_torment_card()1588 static void _torment_card()
1589 {
1590 if (you.undead_or_demonic())
1591 holy_word_player(HOLY_WORD_CARD);
1592 else
1593 torment_player(&you, TORMENT_CARDS);
1594 }
1595
1596 // Punishment cards don't have their power adjusted depending on Nemelex piety,
1597 // and are based on experience level instead of invocations skill.
1598 // Max power = 200 * (2700+2500) / 2700 + 243 + 300 = 928
1599 // Min power = 1 * 2501 / 2700 + 1 + 0 = 2
_card_power(bool punishment)1600 static int _card_power(bool punishment)
1601 {
1602 if (punishment)
1603 return you.experience_level * 18;
1604
1605 int result = you.piety;
1606 result *= you.skill(SK_INVOCATIONS, 100) + 2500;
1607 result /= 2700;
1608 result += you.skill(SK_INVOCATIONS, 9);
1609 result += (you.piety * 3) / 2;
1610
1611 return result;
1612 }
1613
card_effect(card_type which_card,bool dealt,bool punishment,bool tell_card)1614 void card_effect(card_type which_card,
1615 bool dealt,
1616 bool punishment, bool tell_card)
1617 {
1618 const char *participle = dealt ? "dealt" : "drawn";
1619 const int power = _card_power(punishment);
1620
1621 dprf("Card power: %d", power);
1622
1623 if (tell_card)
1624 {
1625 // These card types will usually give this message in the targeting
1626 // prompt, and the cases where they don't are handled specially.
1627 if (which_card != CARD_VITRIOL
1628 && which_card != CARD_PAIN
1629 && which_card != CARD_ORB)
1630 {
1631 mprf("You have %s %s.", participle, card_name(which_card));
1632 }
1633 }
1634
1635 switch (which_card)
1636 {
1637 case CARD_VELOCITY: _velocity_card(power); break;
1638 case CARD_EXILE: _exile_card(power); break;
1639 case CARD_ELIXIR: _elixir_card(power); break;
1640 case CARD_STAIRS: _stairs_card(power); break;
1641 case CARD_TOMB: entomb(10 + power/20 + random2(power/4)); break;
1642 case CARD_WRAITH: drain_player(power / 4, false, true); break;
1643 case CARD_WRATH: _godly_wrath(); break;
1644 case CARD_SUMMON_DEMON: _summon_demon_card(power); break;
1645 case CARD_ELEMENTS: _elements_card(power); break;
1646 case CARD_RANGERS: _summon_rangers(power); break;
1647 case CARD_SUMMON_WEAPON: _summon_dancing_weapon(power); break;
1648 case CARD_SUMMON_BEE: _summon_bee(power); break;
1649 case CARD_TORMENT: _torment_card(); break;
1650 case CARD_CLOUD: _cloud_card(power); break;
1651 case CARD_STORM: _storm_card(power); break;
1652 case CARD_ILLUSION: _illusion_card(power); break;
1653 case CARD_DEGEN: _degeneration_card(power); break;
1654 case CARD_WILD_MAGIC: _wild_magic_card(power); break;
1655
1656 case CARD_VITRIOL:
1657 case CARD_PAIN:
1658 case CARD_ORB:
1659 _damaging_card(which_card, power, dealt);
1660 break;
1661
1662 case CARD_SWINE:
1663 if (transform(5 + power/10 + random2(power/10), transformation::pig, true))
1664 you.transform_uncancellable = true;
1665 else
1666 mpr("You feel a momentary urge to oink.");
1667 break;
1668
1669 #if TAG_MAJOR_VERSION == 34
1670 case CARD_FAMINE_REMOVED:
1671 case CARD_SHAFT_REMOVED:
1672 #endif
1673 case NUM_CARDS:
1674 // The compiler will complain if any card remains unhandled.
1675 mprf("You have %s a buggy card!", participle);
1676 break;
1677 }
1678 }
1679
1680 /**
1681 * Return the appropriate name for a known deck of the given type.
1682 *
1683 * @param sub_type The type of deck in question.
1684 * @return A name, e.g. "deck of destruction".
1685 * If the given type isn't a deck, return "deck of bugginess".
1686 */
deck_name(deck_type deck)1687 string deck_name(deck_type deck)
1688 {
1689 if (deck == DECK_STACK)
1690 return "stacked deck";
1691 const deck_type_data *deck_data = map_find(all_decks, deck);
1692 const string name = deck_data ? deck_data->name : "bugginess";
1693 return "deck of " + name;
1694 }
1695
1696 #if TAG_MAJOR_VERSION == 34
is_deck_type(uint8_t sub_type)1697 bool is_deck_type(uint8_t sub_type)
1698 {
1699 return (MISC_FIRST_DECK <= sub_type && sub_type <= MISC_LAST_DECK)
1700 || sub_type == MISC_DECK_OF_ODDITIES
1701 || sub_type == MISC_DECK_UNKNOWN;
1702 }
1703
is_deck(const item_def & item)1704 bool is_deck(const item_def &item)
1705 {
1706 return item.base_type == OBJ_MISCELLANY
1707 && is_deck_type(item.sub_type);
1708 }
1709
reclaim_decks_on_level()1710 void reclaim_decks_on_level()
1711 {
1712 for (auto &item : env.item)
1713 if (item.defined() && is_deck(item))
1714 destroy_item(item.index());
1715 }
1716
_reclaim_inventory_decks()1717 static void _reclaim_inventory_decks()
1718 {
1719 for (auto &item : you.inv)
1720 if (item.defined() && is_deck(item))
1721 dec_inv_item_quantity(item.link, 1);
1722 }
1723
reclaim_decks()1724 void reclaim_decks()
1725 {
1726 add_daction(DACT_RECLAIM_DECKS);
1727 _reclaim_inventory_decks();
1728 }
1729 #endif
1730