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