/* * File: decks.cc * Summary: Functions with decks of cards. */ #include "AppHdr.h" #include "decks.h" #include #include #include "externs.h" #include "beam.h" #include "cio.h" #include "coordit.h" #include "database.h" #include "dungeon.h" #include "effects.h" #include "env.h" #include "files.h" #include "food.h" #include "ghost.h" #include "godwrath.h" #include "invent.h" #include "it_use2.h" #include "item_use.h" #include "itemprop.h" #include "items.h" #include "makeitem.h" #include "maps.h" #include "message.h" #include "misc.h" #include "mon-iter.h" #include "mon-place.h" #include "mgen_data.h" #include "coord.h" #include "mon-stuff.h" #include "mutation.h" #include "options.h" #include "ouch.h" #include "player.h" #include "religion.h" #include "skills2.h" #include "spells1.h" #include "spells2.h" #include "spells3.h" #include "spells4.h" #include "spl-cast.h" #include "spl-mis.h" #include "spl-util.h" #include "state.h" #include "stuff.h" #include "terrain.h" #include "transform.h" #include "traps.h" #include "view.h" #include "xom.h" // DECK STRUCTURE: deck.plus is the number of cards the deck *started* // with, deck.plus2 is the number of cards drawn, deck.special is the // deck rarity, deck.props["cards"] holds the list of cards (with the // highest index card being the top card, and index 0 being the bottom // card), deck.props["drawn_cards"] holds the list of drawn cards // (with index 0 being the first drawn), deck.props["card_flags"] // holds the flags for each card, deck.props["num_marked"] is the // number of marked cards left in the deck, and // deck.props["non_brownie_draws"] is the number of non-marked draws // you have to make from that deck before earning brownie points from // it again. // // The card type and per-card flags are each stored as unsigned bytes, // for a maximum of 256 different kinds of cards and 8 bits of flags. static void _deck_ident(item_def& deck); struct card_with_weights { card_type card; int weight[3]; }; typedef card_with_weights deck_archetype; #define END_OF_DECK {NUM_CARDS, {0,0,0}} const deck_archetype deck_of_transport[] = { { CARD_PORTAL, {5, 5, 5} }, { CARD_WARP, {5, 5, 5} }, { CARD_SWAP, {5, 5, 5} }, { CARD_VELOCITY, {5, 5, 5} }, END_OF_DECK }; const deck_archetype deck_of_emergency[] = { { CARD_TOMB, {5, 5, 5} }, { CARD_BANSHEE, {5, 5, 5} }, { CARD_DAMNATION, {0, 1, 2} }, { CARD_SOLITUDE, {5, 5, 5} }, { CARD_WARPWRIGHT, {5, 5, 5} }, { CARD_FLIGHT, {5, 5, 5} }, END_OF_DECK }; const deck_archetype deck_of_destruction[] = { { CARD_VITRIOL, {5, 5, 5} }, { CARD_FLAME, {5, 5, 5} }, { CARD_FROST, {5, 5, 5} }, { CARD_VENOM, {5, 5, 5} }, { CARD_HAMMER, {5, 5, 5} }, { CARD_SPARK, {5, 5, 5} }, { CARD_PAIN, {5, 5, 5} }, { CARD_TORMENT, {5, 5, 5} }, END_OF_DECK }; const deck_archetype deck_of_battle[] = { { CARD_ELIXIR, {5, 5, 5} }, { CARD_BATTLELUST, {5, 5, 5} }, { CARD_METAMORPHOSIS, {5, 5, 5} }, { CARD_HELM, {5, 5, 5} }, { CARD_BLADE, {5, 5, 5} }, { CARD_SHADOW, {5, 5, 5} }, END_OF_DECK }; const deck_archetype deck_of_enchantments[] = { { CARD_ELIXIR, {5, 5, 5} }, END_OF_DECK }; const deck_archetype deck_of_summoning[] = { { CARD_CRUSADE, {5, 5, 5} }, { CARD_SUMMON_ANIMAL, {5, 5, 5} }, { CARD_SUMMON_DEMON, {5, 5, 5} }, { CARD_SUMMON_WEAPON, {5, 5, 5} }, { CARD_SUMMON_FLYING, {5, 5, 5} }, { CARD_SUMMON_SKELETON, {5, 5, 5} }, { CARD_SUMMON_UGLY, {5, 5, 5} }, END_OF_DECK }; const deck_archetype deck_of_wonders[] = { { CARD_POTION, {5, 5, 5} }, { CARD_FOCUS, {1, 1, 2} }, { CARD_SHUFFLE, {0, 1, 2} }, { CARD_EXPERIENCE, {5, 5, 5} }, { CARD_WILD_MAGIC, {5, 5, 5} }, { CARD_HELIX, {5, 5, 5} }, { CARD_SAGE, {5, 5, 5} }, END_OF_DECK }; const deck_archetype deck_of_dungeons[] = { { CARD_WATER, {5, 5, 5} }, { CARD_GLASS, {5, 5, 5} }, { CARD_MAP, {5, 5, 5} }, { CARD_DOWSING, {5, 5, 5} }, { CARD_SPADE, {5, 5, 5} }, { CARD_TROWEL, {5, 5, 5} }, { CARD_MINEFIELD, {5, 5, 5} }, END_OF_DECK }; const deck_archetype deck_of_oddities[] = { { CARD_GENIE, {5, 5, 5} }, { CARD_BARGAIN, {5, 5, 5} }, { CARD_WRATH, {5, 5, 5} }, { CARD_XOM, {5, 5, 5} }, { CARD_FEAST, {5, 5, 5} }, { CARD_FAMINE, {5, 5, 5} }, { CARD_CURSE, {5, 5, 5} }, END_OF_DECK }; const deck_archetype deck_of_punishment[] = { { CARD_WRAITH, {5, 5, 5} }, { CARD_WILD_MAGIC, {5, 5, 5} }, { CARD_WRATH, {5, 5, 5} }, { CARD_XOM, {5, 5, 5} }, { CARD_FAMINE, {5, 5, 5} }, { CARD_CURSE, {5, 5, 5} }, { CARD_TOMB, {5, 5, 5} }, { CARD_DAMNATION, {5, 5, 5} }, { CARD_PORTAL, {5, 5, 5} }, { CARD_MINEFIELD, {5, 5, 5} }, { CARD_SWINE, {5, 5, 5} }, END_OF_DECK }; static void _check_odd_card(unsigned char flags) { if ((flags & CFLAG_ODDITY) && !(flags & CFLAG_SEEN)) mpr("This card doesn't seem to belong here."); } int cards_in_deck(const item_def &deck) { ASSERT(is_deck(deck)); const CrawlHashTable &props = deck.props; ASSERT(props.exists("cards")); return (props["cards"].get_vector().size()); } static void _shuffle_deck(item_def &deck) { ASSERT(is_deck(deck)); CrawlHashTable &props = deck.props; ASSERT(props.exists("cards")); CrawlVector &cards = props["cards"].get_vector(); CrawlVector &flags = props["card_flags"].get_vector(); ASSERT(flags.size() == cards.size()); // Don't use std::shuffle(), since we want to apply exactly the // same shuffling to both the cards vector and the flags vector. std::vector pos; for (unsigned long i = 0; i < cards.size(); ++i) pos.push_back(random2(cards.size())); for (vec_size i = 0; i < pos.size(); ++i) { std::swap(cards[i], cards[pos[i]]); std::swap(flags[i], flags[pos[i]]); } } card_type get_card_and_flags(const item_def& deck, int idx, unsigned char& _flags) { const CrawlHashTable &props = deck.props; const CrawlVector &cards = props["cards"].get_vector(); const CrawlVector &flags = props["card_flags"].get_vector(); // Negative idx means read from the end. if (idx < 0) idx += static_cast(cards.size()); _flags = (unsigned char) flags[idx].get_byte(); return static_cast(cards[idx].get_byte()); } static void _set_card_and_flags(item_def& deck, int idx, card_type card, unsigned char _flags) { CrawlHashTable &props = deck.props; CrawlVector &cards = props["cards"].get_vector(); CrawlVector &flags = props["card_flags"].get_vector(); if (idx == -1) idx = static_cast(cards.size()) - 1; cards[idx] = (char) card; flags[idx] = (char) _flags; } const char* card_name(card_type card) { switch (card) { case CARD_PORTAL: return "the Portal"; case CARD_WARP: return "the Warp"; case CARD_SWAP: return "Swap"; case CARD_VELOCITY: return "Velocity"; case CARD_DAMNATION: return "Damnation"; case CARD_SOLITUDE: return "Solitude"; case CARD_ELIXIR: return "the Elixir"; case CARD_BATTLELUST: return "Battlelust"; case CARD_METAMORPHOSIS: return "Metamorphosis"; case CARD_HELM: return "the Helm"; case CARD_BLADE: return "the Blade"; case CARD_SHADOW: return "the Shadow"; case CARD_POTION: return "the Potion"; case CARD_FOCUS: return "Focus"; case CARD_SHUFFLE: return "Shuffle"; case CARD_EXPERIENCE: return "Experience"; case CARD_HELIX: return "the Helix"; case CARD_SAGE: return "the Sage"; case CARD_DOWSING: return "Dowsing"; case CARD_TROWEL: return "the Trowel"; case CARD_MINEFIELD: return "the Minefield"; case CARD_STAIRS: return "the Stairs"; case CARD_GENIE: return "the Genie"; case CARD_TOMB: return "the Tomb"; case CARD_WATER: return "Water"; case CARD_GLASS: return "Vitrification"; case CARD_MAP: return "the Map"; case CARD_BANSHEE: return "the Banshee"; case CARD_WILD_MAGIC: return "Wild Magic"; case CARD_CRUSADE: return "the Crusade"; case CARD_SUMMON_ANIMAL: return "the Herd"; case CARD_SUMMON_DEMON: return "the Pentagram"; case CARD_SUMMON_WEAPON: return "the Dance"; case CARD_SUMMON_FLYING: return "Foxfire"; case CARD_SUMMON_SKELETON: return "the Bones"; case CARD_SUMMON_UGLY: return "Repulsiveness"; case CARD_SUMMON_ANY: return "Summoning"; case CARD_XOM: return "Xom"; case CARD_FAMINE: return "Famine"; case CARD_FEAST: return "the Feast"; case CARD_WARPWRIGHT: return "Warpwright"; case CARD_FLIGHT: return "Flight"; case CARD_VITRIOL: return "Vitriol"; case CARD_FLAME: return "Flame"; case CARD_FROST: return "Frost"; case CARD_VENOM: return "Venom"; case CARD_SPARK: return "the Spark"; case CARD_HAMMER: return "the Hammer"; case CARD_PAIN: return "Pain"; case CARD_TORMENT: return "Torment"; case CARD_SPADE: return "the Spade"; case CARD_BARGAIN: return "the Bargain"; case CARD_WRATH: return "Wrath"; case CARD_WRAITH: return "the Wraith"; case CARD_CURSE: return "the Curse"; case CARD_SWINE: return "the Swine"; case NUM_CARDS: return "a buggy card"; } return "a very buggy card"; } static const deck_archetype* _random_sub_deck(unsigned char deck_type) { const deck_archetype *pdeck = NULL; switch (deck_type) { case MISC_DECK_OF_ESCAPE: pdeck = (coinflip() ? deck_of_transport : deck_of_emergency); break; case MISC_DECK_OF_DESTRUCTION: pdeck = deck_of_destruction; break; case MISC_DECK_OF_DUNGEONS: pdeck = deck_of_dungeons; break; case MISC_DECK_OF_SUMMONING: pdeck = deck_of_summoning; break; case MISC_DECK_OF_WONDERS: pdeck = deck_of_wonders; break; case MISC_DECK_OF_PUNISHMENT: pdeck = deck_of_punishment; break; case MISC_DECK_OF_WAR: switch (random2(6)) { case 0: pdeck = deck_of_destruction; break; case 1: pdeck = deck_of_enchantments; break; case 2: pdeck = deck_of_battle; break; case 3: pdeck = deck_of_summoning; break; case 4: pdeck = deck_of_transport; break; case 5: pdeck = deck_of_emergency; break; } break; case MISC_DECK_OF_CHANGES: switch (random2(3)) { case 0: pdeck = deck_of_battle; break; case 1: pdeck = deck_of_dungeons; break; case 2: pdeck = deck_of_wonders; break; } break; case MISC_DECK_OF_DEFENCE: pdeck = (coinflip() ? deck_of_emergency : deck_of_battle); break; } ASSERT(pdeck); return (pdeck); } static card_type _choose_from_archetype(const deck_archetype* pdeck, deck_rarity_type rarity) { // We assume here that common == 0, rare == 1, legendary == 2. // FIXME: We should use one of the various choose_random_weighted // functions here, probably with an iterator, instead of // duplicating the implementation. int totalweight = 0; card_type result = NUM_CARDS; for (int i = 0; pdeck[i].card != NUM_CARDS; ++i) { const card_with_weights& cww = pdeck[i]; totalweight += cww.weight[rarity]; if (x_chance_in_y(cww.weight[rarity], totalweight)) result = cww.card; } return result; } static card_type _random_card(unsigned char deck_type, deck_rarity_type rarity, bool &was_oddity) { const deck_archetype *pdeck = _random_sub_deck(deck_type); if (one_chance_in(100)) { pdeck = deck_of_oddities; was_oddity = true; } return _choose_from_archetype(pdeck, rarity); } static card_type _random_card(const item_def& item, bool &was_oddity) { return _random_card(item.sub_type, deck_rarity(item), was_oddity); } static card_type _draw_top_card(item_def& deck, bool message, unsigned char &_flags) { CrawlHashTable &props = deck.props; CrawlVector &cards = props["cards"].get_vector(); CrawlVector &flags = props["card_flags"].get_vector(); int num_cards = cards.size(); int idx = num_cards - 1; ASSERT(num_cards > 0); card_type card = get_card_and_flags(deck, idx, _flags); cards.pop_back(); flags.pop_back(); if (message) { if (_flags & CFLAG_MARKED) mprf("You draw %s.", card_name(card)); else mprf("You draw a card... It is %s.", card_name(card)); _check_odd_card(_flags); } return card; } static void _push_top_card(item_def& deck, card_type card, unsigned char _flags) { CrawlHashTable &props = deck.props; CrawlVector &cards = props["cards"].get_vector(); CrawlVector &flags = props["card_flags"].get_vector(); cards.push_back((char) card); flags.push_back((char) _flags); } static void _remember_drawn_card(item_def& deck, card_type card, bool allow_id) { ASSERT( is_deck(deck) ); CrawlHashTable &props = deck.props; CrawlVector &drawn = props["drawn_cards"].get_vector(); drawn.push_back( static_cast(card) ); // Once you've drawn two cards, you know the deck. if (allow_id && (drawn.size() >= 2 || origin_is_god_gift(deck))) _deck_ident(deck); } const std::vector get_drawn_cards(const item_def& deck) { std::vector result; if (is_deck(deck)) { const CrawlHashTable &props = deck.props; const CrawlVector &drawn = props["drawn_cards"].get_vector(); for (unsigned int i = 0; i < drawn.size(); ++i) { const char tmp = drawn[i]; result.push_back(static_cast(tmp)); } } return result; } static bool _check_buggy_deck(item_def& deck) { std::ostream& strm = msg::streams(MSGCH_DIAGNOSTICS); if (!is_deck(deck)) { crawl_state.zero_turns_taken(); strm << "This isn't a deck at all!" << std::endl; return (true); } CrawlHashTable &props = deck.props; if (!props.exists("cards") || props["cards"].get_type() != SV_VEC || props["cards"].get_vector().get_type() != SV_BYTE || cards_in_deck(deck) == 0) { crawl_state.zero_turns_taken(); if (!props.exists("cards")) strm << "Seems this deck never had any cards in the first place!"; else if (props["cards"].get_type() != SV_VEC) strm << "'cards' property isn't a vector."; else { if (props["cards"].get_vector().get_type() != SV_BYTE) strm << "'cards' vector doesn't contain bytes."; if (cards_in_deck(deck) == 0) { strm << "Strange, this deck is already empty."; int cards_left = 0; if (deck.plus2 >= 0) cards_left = deck.plus - deck.plus2; else cards_left = -deck.plus; if (cards_left != 0) { strm << " But there should have been " << cards_left << " cards left."; } } } strm << std::endl << "A swarm of software bugs snatches the deck from you " "and whisks it away." << std::endl; if (deck.link == you.equip[EQ_WEAPON]) unwield_item(); dec_inv_item_quantity( deck.link, 1 ); did_god_conduct(DID_CARDS, 1); return (true); } bool problems = false; CrawlVector &cards = props["cards"].get_vector(); CrawlVector &flags = props["card_flags"].get_vector(); vec_size num_cards = cards.size(); vec_size num_flags = flags.size(); unsigned int num_buggy = 0; unsigned int num_marked = 0; for (vec_size i = 0; i < num_cards; ++i) { unsigned char card = cards[i].get_byte(); unsigned char _flags = flags[i].get_byte(); if (card >= NUM_CARDS) { cards.erase(i); flags.erase(i); i--; num_cards--; num_buggy++; } else { if (_flags & CFLAG_MARKED) num_marked++; } } if (num_buggy > 0) { strm << num_buggy << " buggy cards found in the deck, discarding them." << std::endl; deck.plus2 += num_buggy; num_cards = cards.size(); num_flags = cards.size(); problems = true; } if (num_cards == 0) { crawl_state.zero_turns_taken(); strm << "Oops, all of the cards seem to be gone." << std::endl << "A swarm of software bugs snatches the deck from you " "and whisks it away." << std::endl; if (deck.link == you.equip[EQ_WEAPON]) unwield_item(); dec_inv_item_quantity( deck.link, 1 ); did_god_conduct(DID_CARDS, 1); return (true); } if (static_cast(num_cards) > deck.plus) { if (deck.plus == 0) strm << "Deck was created with zero cards???" << std::endl; else if (deck.plus < 0) strm << "Deck was created with *negative* cards?!" << std::endl; else { strm << "Deck has more cards than it was created with?" << std::endl; } deck.plus = num_cards; problems = true; } if (num_cards > num_flags) { #ifdef WIZARD strm << (num_cards - num_flags) << " more cards than flags."; #else strm << "More cards than flags."; #endif strm << std::endl; for (unsigned int i = num_flags + 1; i <= num_cards; ++i) flags[i] = static_cast(0); problems = true; } else if (num_flags > num_cards) { #ifdef WIZARD strm << (num_cards - num_flags) << " more cards than flags."; #else strm << "More cards than flags."; #endif strm << std::endl; for (unsigned int i = num_flags; i > num_cards; --i) flags.erase(i); problems = true; } if (props["num_marked"].get_byte() > static_cast(num_cards)) { strm << "More cards marked than in the deck?" << std::endl; props["num_marked"] = static_cast(num_marked); problems = true; } else if (props["num_marked"].get_byte() != static_cast(num_marked)) { #ifdef WIZARD strm << "Oops, counted " << static_cast(num_marked) << " marked cards, but num_marked is " << (static_cast(props["num_marked"].get_byte())); #else strm << "Oops, book-keeping on marked cards is wrong."; #endif strm << std::endl; props["num_marked"] = static_cast(num_marked); problems = true; } if (deck.plus2 >= 0) { if (deck.plus != (deck.plus2 + static_cast(num_cards))) { #ifdef WIZARD strm << "Have you used " << deck.plus2 << " cards, or " << (deck.plus - num_cards) << "? Oops."; #else strm << "Oops, book-keeping on used cards is wrong."; #endif strm << std::endl; deck.plus2 = deck.plus - num_cards; problems = true; } } else { if (-deck.plus2 != static_cast(num_cards)) { #ifdef WIZARD strm << "There are " << num_cards << " cards left, not " << (-deck.plus2) << ". Oops."; #else strm << "Oops, book-keeping on cards left is wrong."; #endif strm << std::endl; deck.plus2 = -num_cards; problems = true; } } if (!problems) return (false); you.wield_change = true; if (!yesno("Problems might not have been completely fixed; " "still use deck?", true, 'n')) { crawl_state.zero_turns_taken(); return (true); } return (false); } // Choose a deck from inventory and return its slot (or -1). static int _choose_inventory_deck( const char* prompt ) { const int slot = prompt_invent_item( prompt, MT_INVLIST, OSEL_DRAW_DECK, true, true, true, 0, -1, NULL, OPER_EVOKE ); if (prompt_failed(slot)) return -1; if (!is_deck(you.inv[slot])) { mpr("That isn't a deck!"); return -1; } return slot; } // Select a deck from inventory and draw a card from it. bool choose_deck_and_draw() { const int slot = _choose_inventory_deck( "Draw from which deck?" ); if (slot == -1) { crawl_state.zero_turns_taken(); return (false); } evoke_deck(you.inv[slot]); return (true); } static void _deck_ident(item_def& deck) { if (in_inventory(deck) && !item_ident(deck, ISFLAG_KNOW_TYPE)) { set_ident_flags(deck, ISFLAG_KNOW_TYPE); mprf("This is %s.", deck.name(DESC_NOCAP_A).c_str()); you.wield_change = true; } } // This also shuffles the deck. static void _deck_lose_card(item_def& deck) { unsigned char flags = 0; // Seen cards are only half as likely to fall out, // marked cards only one-quarter as likely (note that marked // cards are also seen.) do { _shuffle_deck(deck); get_card_and_flags(deck, -1, flags); } while ( (flags & CFLAG_MARKED) && coinflip() || (flags & CFLAG_SEEN) && coinflip() ); _draw_top_card(deck, false, flags); deck.plus2++; } // Peek at two cards in a deck, then shuffle them back in. // Return false if the operation was failed/aborted along the way. bool deck_peek() { const int slot = _choose_inventory_deck( "Peek at which deck?" ); if (slot == -1) { crawl_state.zero_turns_taken(); return (false); } item_def& deck(you.inv[slot]); if (_check_buggy_deck(deck)) return (false); if (cards_in_deck(deck) > 2) { _deck_lose_card(deck); mpr("A card falls out of the deck."); } CrawlVector &cards = deck.props["cards"].get_vector(); const int num_cards = cards.size(); card_type card1, card2; unsigned char flags1, flags2; card1 = get_card_and_flags(deck, 0, flags1); if (num_cards == 1) { mpr("There's only one card in the deck!"); _set_card_and_flags(deck, 0, card1, flags1 | CFLAG_SEEN | CFLAG_MARKED); deck.props["num_marked"]++; deck.plus2 = -1; you.wield_change = true; return (true); } card2 = get_card_and_flags(deck, 1, flags2); int already_seen = 0; if (flags1 & CFLAG_SEEN) already_seen++; if (flags2 & CFLAG_SEEN) already_seen++; // Always increase if seen 2, 50% increase if seen 1. if (already_seen && x_chance_in_y(already_seen, 2)) deck.props["non_brownie_draws"]++; mprf("You draw two cards from the deck. They are: %s and %s.", card_name(card1), card_name(card2)); _set_card_and_flags(deck, 0, card1, flags1 | CFLAG_SEEN); _set_card_and_flags(deck, 1, card2, flags2 | CFLAG_SEEN); mpr("You shuffle the cards back into the deck."); _shuffle_deck(deck); // Peeking identifies the deck. _deck_ident(deck); you.wield_change = true; return (true); } // Mark a deck: look at the next four cards, mark them, and shuffle // them back into the deck. The player won't know what order they're // in, and if the top card is non-marked then the player won't // know what the next card is. Return false if the operation was // failed/aborted along the way. bool deck_mark() { const int slot = _choose_inventory_deck( "Mark which deck?" ); if (slot == -1) { crawl_state.zero_turns_taken(); return (false); } item_def& deck(you.inv[slot]); if (_check_buggy_deck(deck)) return (false); CrawlHashTable &props = deck.props; if (props["num_marked"].get_byte() > 0) { mpr("The deck is already marked."); crawl_state.zero_turns_taken(); return (false); } // Lose some cards, but keep at least two. if (cards_in_deck(deck) > 2) { const int num_lost = std::min(cards_in_deck(deck)-2, random2(3) + 1); for (int i = 0; i < num_lost; ++i) _deck_lose_card(deck); if (num_lost == 1) mpr("A card falls out of the deck."); else if (num_lost > 1) mpr("Some cards fall out of the deck."); } const int num_cards = cards_in_deck(deck); const int num_to_mark = (num_cards < 4 ? num_cards : 4); if (num_cards == 1) mpr("There's only one card left!"); else if (num_cards < 4) mprf("The deck only has %d cards.", num_cards); std::vector names; for (int i = 0; i < num_to_mark; ++i) { unsigned char flags; card_type card = get_card_and_flags(deck, i, flags); flags |= CFLAG_SEEN | CFLAG_MARKED; _set_card_and_flags(deck, i, card, flags); names.push_back(card_name(card)); } mpr_comma_separated_list("You draw and mark ", names); props["num_marked"] = (char) num_to_mark; if (num_cards == 1) ; else if (num_cards < 4) { mprf("You shuffle the deck."); deck.plus2 = -num_cards; } else mprf("You shuffle the cards back into the deck."); _shuffle_deck(deck); _deck_ident(deck); you.wield_change = true; return (true); } static void _redraw_stacked_cards(const std::vector& draws, unsigned int selected) { for (unsigned int i = 0; i < draws.size(); ++i) { cgotoxy(1, i+2); textcolor(selected == i ? WHITE : LIGHTGREY); cprintf("%u - %s", i+1, card_name(draws[i]) ); clear_to_end_of_line(); } } static void _describe_cards(std::vector cards) { ASSERT(!cards.empty()); std::ostringstream data; for (unsigned int i = 0; i < cards.size(); ++i) { std::string name = card_name(cards[i]); std::string desc = getLongDescription(name + " card"); if (desc.empty()) desc = "No description found."; name = uppercase_first(name); data << "" << name << "\n" << get_linebreak_string(desc, get_number_of_cols()) << EOL; } formatted_string fs = formatted_string::parse_string(data.str()); clrscr(); fs.display(); if (getch() == 0) getch(); redraw_screen(); } // Stack a deck: look at the next five cards, put them back in any // order, discard the rest of the deck. // Return false if the operation was failed/aborted along the way. bool deck_stack() { const int slot = _choose_inventory_deck("Stack which deck?"); if (slot == -1) { crawl_state.zero_turns_taken(); return (false); } item_def& deck(you.inv[slot]); if (_check_buggy_deck(deck)) return (false); CrawlHashTable &props = deck.props; if (props["num_marked"].get_byte() > 0) { mpr("You can't stack a marked deck."); crawl_state.zero_turns_taken(); return (false); } _deck_ident(deck); const int num_cards = cards_in_deck(deck); const int num_to_stack = (num_cards < 5 ? num_cards : 5); std::vector draws; std::vector flags; for (int i = 0; i < num_cards; ++i) { unsigned char _flags; card_type card = _draw_top_card(deck, false, _flags); if (i < num_to_stack) { draws.push_back(card); flags.push_back(_flags | CFLAG_SEEN | CFLAG_MARKED); } // Rest of deck is discarded. } if (num_cards == 1) mpr("There's only one card left!"); else if (num_cards < 5) mprf("The deck only has %d cards.", num_to_stack); else if (num_cards == 5) mpr("The deck has exactly five cards."); else { mprf("You draw the first five cards out of %d and discard the rest.", num_cards); } more(); if (draws.size() > 1) { bool need_prompt_redraw = true; unsigned int selected = draws.size(); while (true) { if (need_prompt_redraw) { clrscr(); cgotoxy(1,1); textcolor(WHITE); cprintf("Press a digit to select a card, then another digit " "to swap it."); cgotoxy(1,10); cprintf("Press ? for the card descriptions, or Enter to " "accept."); _redraw_stacked_cards(draws, selected); need_prompt_redraw = false; } // Hand-hacked implementation, instead of using Menu. Oh well. const int c = getch(); if (c == CK_ENTER) { cgotoxy(1,11); textcolor(LIGHTGREY); cprintf("Are you sure? (press y or Y to confirm)"); if (toupper(getch()) == 'Y') break; cgotoxy(1,11); clear_to_end_of_line(); continue; } if (c == '?') { _describe_cards(draws); need_prompt_redraw = true; } else if (c >= '1' && c <= '0' + static_cast(draws.size())) { const unsigned int new_selected = c - '1'; if (selected < draws.size()) { std::swap(draws[selected], draws[new_selected]); std::swap(flags[selected], flags[new_selected]); selected = draws.size(); } else selected = new_selected; _redraw_stacked_cards(draws, selected); } } redraw_screen(); } deck.plus2 = -num_to_stack; for (unsigned int i = 0; i < draws.size(); ++i) { _push_top_card(deck, draws[draws.size() - 1 - i], flags[flags.size() - 1 - i]); } props["num_marked"] = static_cast(num_to_stack); you.wield_change = true; _check_buggy_deck(deck); return (true); } // Draw the next three cards, discard two and pick one. bool deck_triple_draw() { const int slot = _choose_inventory_deck("Triple draw from which deck?"); if (slot == -1) { crawl_state.zero_turns_taken(); return (false); } item_def& deck(you.inv[slot]); if (_check_buggy_deck(deck)) return (false); const int num_cards = cards_in_deck(deck); // We have to identify the deck before removing cards from it. // Otherwise, _remember_drawn_card() will implicitly call // _deck_ident() when the deck might have no cards left. _deck_ident(deck); if (num_cards == 1) { // Only one card to draw, so just draw it. evoke_deck(deck); return (true); } const int num_to_draw = (num_cards < 3 ? num_cards : 3); std::vector draws; std::vector flags; for (int i = 0; i < num_to_draw; ++i) { unsigned char _flags; card_type card = _draw_top_card(deck, false, _flags); draws.push_back(card); flags.push_back(_flags); } int selected = -1; bool need_prompt_redraw = true; while (true) { if (need_prompt_redraw) { mpr("You draw... (choose one card, ? for their descriptions)"); for (int i = 0; i < num_to_draw; ++i) { msg::streams(MSGCH_PROMPT) << (static_cast(i + 'a')) << " - " << card_name(draws[i]) << std::endl; } need_prompt_redraw = false; } const int keyin = tolower(get_ch()); if (keyin == '?') { _describe_cards(draws); need_prompt_redraw = true; } else if (keyin >= 'a' && keyin < 'a' + num_to_draw) { selected = keyin - 'a'; break; } else canned_msg(MSG_HUH); } // Note how many cards were removed from the deck. deck.plus2 += num_to_draw; // Don't forget to update the number of marked ones, too. // But don't reduce the number of non-brownie draws. char num_marked_left = deck.props["num_marked"].get_byte(); for (int i = 0; i < num_to_draw; ++i) { _remember_drawn_card(deck, draws[i], false); if (flags[i] & CFLAG_MARKED) { ASSERT(num_marked_left > 0); --num_marked_left; } } deck.props["num_marked"] = num_marked_left; you.wield_change = true; // Make deck disappear *before* the card effect, since we // don't want to unwield an empty deck. deck_rarity_type rarity = deck_rarity(deck); if (cards_in_deck(deck) == 0) { mpr("The deck of cards disappears in a puff of smoke."); if (slot == you.equip[EQ_WEAPON]) unwield_item(); dec_inv_item_quantity( slot, 1 ); } // Note that card_effect() might cause you to unwield the deck. card_effect(draws[selected], rarity, flags[selected] | CFLAG_SEEN | CFLAG_MARKED, false); return (true); } // This is Nemelex retribution. void draw_from_deck_of_punishment() { bool oddity; card_type card = _random_card(MISC_DECK_OF_PUNISHMENT, DECK_RARITY_COMMON, oddity); mpr("You draw a card..."); card_effect(card, DECK_RARITY_COMMON); } static int _xom_check_card(item_def &deck, card_type card, unsigned char flags) { int amusement = 64; if (!item_type_known(deck)) amusement *= 2; // Expecting one type of card but got another, real funny. else if (flags & CFLAG_ODDITY) amusement = 255; if (player_in_a_dangerous_place()) amusement *= 2; switch (card) { case CARD_XOM: // Handled elsewhere amusement = 0; break; case CARD_DAMNATION: // Nothing happened, boring. if (you.level_type != LEVEL_DUNGEON) amusement = 0; break; case CARD_MINEFIELD: case CARD_FAMINE: case CARD_CURSE: case CARD_SWINE: // Always hilarious. amusement = 255; default: break; } return amusement; } void evoke_deck( item_def& deck ) { if (_check_buggy_deck(deck)) return; int brownie_points = 0; bool allow_id = in_inventory(deck) && !item_ident(deck, ISFLAG_KNOW_TYPE); const deck_rarity_type rarity = deck_rarity(deck); CrawlHashTable &props = deck.props; unsigned char flags = 0; card_type card = _draw_top_card(deck, true, flags); // Oddity cards don't give any information about the deck. if (flags & CFLAG_ODDITY) allow_id = false; // Passive Nemelex retribution: sometimes a card gets swapped out. // More likely to happen with marked decks. if (you.penance[GOD_NEMELEX_XOBEH]) { int c = 1; if ((flags & (CFLAG_MARKED | CFLAG_SEEN)) || props["num_marked"].get_byte() > 0) { c = 3; } if (x_chance_in_y(c * you.penance[GOD_NEMELEX_XOBEH], 3000)) { card_type old_card = card; card = _choose_from_archetype(deck_of_punishment, rarity); if (card != old_card) { simple_god_message(" seems to have exchanged this card " "behind your back!", GOD_NEMELEX_XOBEH); mprf("It's actually %s.", card_name(card)); // You never completely appease Nemelex, but the effects // get less frequent. you.penance[GOD_NEMELEX_XOBEH] -= random2( (you.penance[GOD_NEMELEX_XOBEH]+18) / 10); } } } const int amusement = _xom_check_card(deck, card, flags); const bool no_brownie = (props["non_brownie_draws"].get_byte() > 0); // Do these before the deck item_def object is gone. if (flags & CFLAG_MARKED) props["num_marked"]--; if (no_brownie) props["non_brownie_draws"]--; deck.plus2++; _remember_drawn_card(deck, card, allow_id); // Get rid of the deck *before* the card effect because a card // might cause a wielded deck to be swapped out for something else, // in which case we don't want an empty deck to go through the // swapping process. const bool deck_gone = (cards_in_deck(deck) == 0); if (deck_gone) { mpr("The deck of cards disappears in a puff of smoke."); dec_inv_item_quantity( deck.link, 1 ); // Finishing the deck will earn a point, even if it // was marked or stacked. brownie_points++; } const bool fake_draw = !card_effect(card, rarity, flags, false); if (fake_draw && !deck_gone) props["non_brownie_draws"]++; if (!(flags & CFLAG_MARKED)) { // Could a Xom worshipper ever get a stacked deck in the first // place? xom_is_stimulated(amusement); // Nemelex likes gamblers. if (!no_brownie) { brownie_points++; if (one_chance_in(3)) brownie_points++; } // You can't ID off a marked card allow_id = false; } if (!deck_gone && allow_id && you.skills[SK_EVOCATIONS] > 5 + random2(35)) { mpr("Your skill with magical items lets you identify the deck."); set_ident_flags( deck, ISFLAG_KNOW_TYPE ); msg::streams(MSGCH_EQUIPMENT) << deck.name(DESC_INVENTORY) << std::endl; } if (!fake_draw) did_god_conduct(DID_CARDS, brownie_points); // Always wield change, since the number of cards used/left has // changed. you.wield_change = true; } int get_power_level(int power, deck_rarity_type rarity) { int power_level = 0; switch (rarity) { case DECK_RARITY_COMMON: break; case DECK_RARITY_LEGENDARY: if (x_chance_in_y(power, 500)) ++power_level; // deliberate fall-through case DECK_RARITY_RARE: if (x_chance_in_y(power, 700)) ++power_level; break; } return power_level; } // Actual card implementations follow. static void _portal_card(int power, deck_rarity_type rarity) { const int control_level = get_power_level(power, rarity); bool instant = false; bool controlled = false; if (control_level >= 2) { instant = true; controlled = true; } else if (control_level == 1) { if (coinflip()) instant = true; else controlled = true; } int threshold = 6; const bool was_controlled = player_control_teleport(); const bool short_control = (you.duration[DUR_CONTROL_TELEPORT] > 0 && you.duration[DUR_CONTROL_TELEPORT] < threshold * BASELINE_DELAY); if (controlled && (!was_controlled || short_control)) you.set_duration(DUR_CONTROL_TELEPORT, threshold); // Long enough to kick in. if (instant) you_teleport_now( true ); else you_teleport(); } static void _warp_card(int power, deck_rarity_type rarity) { const int control_level = get_power_level(power, rarity); if (control_level >= 2) blink(1000, false); else if (control_level == 1) cast_semi_controlled_blink(power / 4); else random_blink(false); } static void _swap_monster_card(int power, deck_rarity_type rarity) { // Swap between you and another monster. // Don't choose yourself unless there are no monsters nearby. monsters *mon_to_swap = choose_random_nearby_monster(0); if (!mon_to_swap) mpr("You spin around."); else swap_with_monster(mon_to_swap); } static void _velocity_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); if (power_level >= 2) potion_effect(POT_SPEED, random2(power / 4)); else if (power_level == 1) { cast_fly(random2(power / 4)); cast_swiftness(random2(power / 4)); } else cast_swiftness(random2(power / 4)); } static void _damnation_card(int power, deck_rarity_type rarity) { if (you.level_type != LEVEL_DUNGEON) { canned_msg(MSG_NOTHING_HAPPENS); return; } // Calculate how many extra banishments you get. const int power_level = get_power_level(power, rarity); int nemelex_bonus = 0; if (you.religion == GOD_NEMELEX_XOBEH && !player_under_penance()) nemelex_bonus = you.piety / 20; int extra_targets = power_level + random2(you.skills[SK_EVOCATIONS] + nemelex_bonus) / 12; for (int i = 0; i < 1 + extra_targets; ++i) { // Pick a random monster nearby to banish (or yourself). monsters *mon_to_banish = choose_random_nearby_monster(1); // Bonus banishments only banish monsters. if (i != 0 && !mon_to_banish) continue; if (!mon_to_banish) // Banish yourself! { banished(DNGN_ENTER_ABYSS, "drawing a card"); break; // Don't banish anything else. } else mon_to_banish->banish(); } } static void _warpwright_card(int power, deck_rarity_type rarity) { if (you.level_type == LEVEL_ABYSS) { mpr("The power of the Abyss blocks your magic."); return; } int count = 0; coord_def f; for (adjacent_iterator ai(you.pos()); ai; ++ai) if (grd(*ai) == DNGN_FLOOR && !find_trap(*ai) && one_chance_in(++count)) f = *ai; if (count > 0) // found a spot { if (place_specific_trap(f, TRAP_TELEPORT)) { // Mark it discovered if enough power. if (get_power_level(power, rarity) >= 1) find_trap(f)->reveal(); } } } static void _flight_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); // Assume something _will_ happen. bool success = true; if (power_level == 0) { if (!transform(random2(power/4), coinflip() ? TRAN_SPIDER : TRAN_BAT, true)) { // Oops, something went wrong here (either because of cursed // equipment or the possibility of stat loss). success = false; } } else if (power_level >= 1) { cast_fly(random2(power/4)); cast_swiftness(random2(power/4)); } if (power_level == 2) // Stacks with the above. { if (is_valid_shaft_level() && grd(you.pos()) == DNGN_FLOOR) { if (place_specific_trap(you.pos(), TRAP_SHAFT)) { find_trap(you.pos())->reveal(); mpr("A shaft materialises beneath you!"); } } } if (one_chance_in(4 - power_level)) potion_effect(POT_INVISIBILITY, random2(power)/4); else if (!success) canned_msg(MSG_NOTHING_HAPPENS); } static void _minefield_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); const int radius = power_level * 2 + 2; for (radius_iterator ri(you.pos(), radius, false, false, false); ri; ++ri) { if (*ri == you.pos()) continue; if (grd(*ri) == DNGN_FLOOR && !find_trap(*ri) && one_chance_in(4 - power_level)) { if (you.level_type == LEVEL_ABYSS) grd(*ri) = coinflip() ? DNGN_DEEP_WATER : DNGN_LAVA; else place_specific_trap(*ri, TRAP_RANDOM); } } } static int stair_draw_count = 0; // This does not describe an actual card. Instead, it only exists to test // the stair movement effect in wizard mode ("&c stairs"). static void _stairs_card(int power, deck_rarity_type rarity) { UNUSED(power); UNUSED(rarity); you.duration[DUR_REPEL_STAIRS_MOVE] = 0; you.duration[DUR_REPEL_STAIRS_CLIMB] = 0; if (feat_stair_direction(grd(you.pos())) == CMD_NO_CMD) you.duration[DUR_REPEL_STAIRS_MOVE] = 1000; else you.duration[DUR_REPEL_STAIRS_CLIMB] = 500; // more annoying std::vector stairs_avail; for (radius_iterator ri(you.pos(), LOS_RADIUS, false, true, true); ri; ++ri) { dungeon_feature_type feat = grd(*ri); if (feat_stair_direction(feat) != CMD_NO_CMD && feat != DNGN_ENTER_SHOP) { stairs_avail.push_back(*ri); } } if (stairs_avail.size() == 0) { mpr("No stairs available to move."); return; } std::random_shuffle(stairs_avail.begin(), stairs_avail.end()); for (unsigned int i = 0; i < stairs_avail.size(); ++i) move_stair(stairs_avail[i], stair_draw_count % 2, false); stair_draw_count++; } static int _drain_monsters(coord_def where, int pow, int, actor *) { if (where == you.pos()) drain_exp(); else { monsters* mon = monster_at(where); if (mon == NULL) return (0); if (!mon->drain_exp(&you, false, pow / 50)) simple_monster_message(mon, " is unaffected."); } return (1); } static void _mass_drain(int pow) { apply_area_visible(_drain_monsters, pow); } // Return true if it was a "genuine" draw, i.e., there was a monster // to target. This is still exploitable by finding popcorn monsters. static bool _damaging_card(card_type card, int power, deck_rarity_type rarity) { bool rc = there_are_monsters_nearby(true, false); const int power_level = get_power_level(power, rarity); dist target; zap_type ztype = ZAP_DEBUGGING_RAY; const zap_type firezaps[3] = { ZAP_FLAME, ZAP_STICKY_FLAME, ZAP_FIRE }; const zap_type frostzaps[3] = { ZAP_FROST, ZAP_THROW_ICICLE, ZAP_COLD }; const zap_type hammerzaps[3] = { ZAP_STRIKING, ZAP_STONE_ARROW, ZAP_CRYSTAL_SPEAR }; const zap_type venomzaps[3] = { ZAP_STING, ZAP_VENOM_BOLT, ZAP_POISON_ARROW }; const zap_type sparkzaps[3] = { ZAP_ELECTRICITY, ZAP_LIGHTNING, ZAP_ORB_OF_ELECTRICITY }; const zap_type painzaps[2] = { ZAP_AGONY, ZAP_NEGATIVE_ENERGY }; switch (card) { case CARD_VITRIOL: ztype = (one_chance_in(3) ? ZAP_DEGENERATION : ZAP_BREATHE_ACID); break; case CARD_FLAME: ztype = (coinflip() ? ZAP_FIREBALL : firezaps[power_level]); break; case CARD_FROST: ztype = frostzaps[power_level]; break; case CARD_HAMMER: ztype = hammerzaps[power_level]; break; case CARD_VENOM: ztype = venomzaps[power_level]; break; case CARD_SPARK: ztype = sparkzaps[power_level]; break; case CARD_PAIN: if (power_level == 2) { mprf("You have drawn %s.", card_name(card)); _mass_drain(power); return (true); } else ztype = painzaps[power_level]; break; default: break; } snprintf(info, INFO_SIZE, "You have drawn %s. Aim where? ", card_name(card)); bolt beam; beam.range = LOS_RADIUS; if (spell_direction(target, beam, DIR_NONE, TARG_HOSTILE, LOS_RADIUS, true, true, false, info) && player_tracer(ZAP_DEBUGGING_RAY, power/4, beam)) { zapping(ztype, random2(power/4), beam); } else rc = false; return (rc); } static void _elixir_card(int power, deck_rarity_type rarity) { int power_level = get_power_level(power, rarity); if (power_level == 1 && you.hp * 2 > you.hp_max) power_level = 0; if (power_level == 0) { if (coinflip()) potion_effect(POT_HEAL_WOUNDS, 40); // power doesn't matter else cast_regen(random2(power / 4)); } else if (power_level == 1) { set_hp(you.hp_max, false); you.magic_points = 0; } else if (power_level >= 2) { set_hp(you.hp_max, false); you.magic_points = you.max_magic_points; } you.redraw_hit_points = true; you.redraw_magic_points = true; } static void _battle_lust_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); if (power_level >= 2) { you.set_duration(DUR_SLAYING, random2(power/6) + 1, 0, "You feel deadly."); } else if (power_level == 1) { you.set_duration(DUR_BUILDING_RAGE, 1, 0, "You feel your rage building."); } else if (power_level == 0) potion_effect(POT_MIGHT, random2(power/4)); } static void _metamorphosis_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); transformation_type trans; if (power_level >= 2) trans = coinflip() ? TRAN_DRAGON : TRAN_LICH; else if (power_level == 1) trans = coinflip() ? TRAN_STATUE : TRAN_BLADE_HANDS; else { trans = one_chance_in(3) ? TRAN_SPIDER : coinflip() ? TRAN_ICE_BEAST : TRAN_BAT; } // Might fail, e.g. because of cursed equipment or potential death by // stat loss. Aren't we being nice? (jpeg) if (!transform(random2(power/4), trans, true)) canned_msg(MSG_NOTHING_HAPPENS); } static void _helm_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); bool do_phaseshift = false; bool do_stoneskin = false; bool do_shield = false; int num_resists = 0; // Chances are cumulative. if (power_level >= 2) { if (coinflip()) do_phaseshift = true; if (coinflip()) do_stoneskin = true; if (coinflip()) do_shield = true; num_resists = random2(4); } if (power_level >= 1) { if (coinflip()) do_phaseshift = true; if (coinflip()) do_stoneskin = true; if (coinflip()) do_shield = true; } if (power_level >= 0) { if (coinflip()) do_phaseshift = true; else do_stoneskin = true; } if (do_phaseshift) cast_phase_shift( random2(power/4) ); if (do_stoneskin) cast_stoneskin( random2(power/4) ); if (num_resists) { const duration_type possible_resists[4] = { DUR_RESIST_POISON, DUR_INSULATION, DUR_RESIST_FIRE, DUR_RESIST_COLD }; const char* resist_names[4] = { "poison", "electricity", "fire", "cold" }; for (int i = 0; i < 4 && num_resists; ++i) { // If there are n left, of which we need to choose // k, we have chance k/n of selecting the next item. if (x_chance_in_y(num_resists, 4-i)) { // Add a temporary resistance. you.increase_duration(possible_resists[i], random2(power/7) +1); msg::stream << "You feel resistant to " << resist_names[i] << '.' << std::endl; --num_resists; } } } if (do_shield) { if (you.duration[DUR_MAGIC_SHIELD] == 0) mpr("A magical shield forms in front of you."); you.increase_duration(DUR_MAGIC_SHIELD, random2(power/6) + 1); } } static void _blade_card(int power, deck_rarity_type rarity) { // Pause before jumping to the list. if (Options.auto_list) more(); wield_weapon(false); const int power_level = get_power_level(power, rarity); if (power_level >= 2) { cast_tukimas_dance( random2(power/4) ); } else if (power_level == 1) { cast_sure_blade( random2(power/4) ); } else { const brand_type brands[] = { SPWPN_FLAMING, SPWPN_FREEZING, SPWPN_VENOM, SPWPN_DRAINING, SPWPN_VORPAL, SPWPN_DISTORTION, SPWPN_PAIN, SPWPN_DUMMY_CRUSHING }; if (!brand_weapon(RANDOM_ELEMENT(brands), random2(power/4))) { if (!you.weapon()) mprf("Your %s twitch.", your_hand(true).c_str()); else mpr("Your weapon vibrates for a moment."); } } } static void _shadow_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); if (power_level >= 1) { mpr(you.duration[DUR_STEALTH] ? "You feel more catlike." : "You feel stealthy."); you.increase_duration(DUR_STEALTH, random2(power/4) + 1); } potion_effect(POT_INVISIBILITY, random2(power/4)); } static void _potion_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); potion_type pot_effects[] = { POT_HEAL_WOUNDS, POT_HEAL_WOUNDS, POT_HEAL_WOUNDS, POT_HEALING, POT_HEALING, POT_HEALING, POT_RESTORE_ABILITIES, POT_RESTORE_ABILITIES, POT_POISON, POT_CONFUSION, POT_DEGENERATION }; potion_type pot = RANDOM_ELEMENT(pot_effects); if (power_level >= 1 && coinflip()) pot = (coinflip() ? POT_CURE_MUTATION : POT_MUTATION); if (power_level >= 2 && one_chance_in(5)) { // +1 to a random stat. const potion_type gain_stat_pots[] = { POT_GAIN_STRENGTH, POT_GAIN_DEXTERITY, POT_GAIN_INTELLIGENCE }; pot = RANDOM_ELEMENT(gain_stat_pots); } potion_effect(pot, random2(power/4)); } static void _focus_card(int power, deck_rarity_type rarity) { char* max_statp[] = { &you.max_strength, &you.max_intel, &you.max_dex }; char* base_statp[] = { &you.strength, &you.intel, &you.dex }; int best_stat = 0; int worst_stat = 0; for (int i = 1; i < 3; ++i) { const int best_diff = *max_statp[i] - *max_statp[best_stat]; if (best_diff > 0 || best_diff == 0 && coinflip()) best_stat = i; const int worst_diff = *max_statp[i] - *max_statp[worst_stat]; if (worst_diff < 0 || worst_diff == 0 && coinflip()) worst_stat = i; } while (best_stat == worst_stat) { best_stat = random2(3); worst_stat = random2(3); } (*max_statp[best_stat])++; (*max_statp[worst_stat])--; (*base_statp[best_stat])++; (*base_statp[worst_stat])--; // Did focusing kill the player? const kill_method_type kill_types[3] = { KILLED_BY_WEAKNESS, KILLED_BY_CLUMSINESS, KILLED_BY_STUPIDITY }; std::string cause = "the Focus card"; if (crawl_state.is_god_acting()) { god_type which_god = crawl_state.which_god_acting(); if (crawl_state.is_god_retribution()) cause = "the wrath of " + god_name(which_god); else { if (which_god == GOD_XOM) cause = "the capriciousness of Xom"; else cause = "the 'helpfulness' of " + god_name(which_god); } } for (int i = 0; i < 3; ++i) if (*max_statp[i] < 1 || *base_statp[i] < 1) { ouch(INSTANT_DEATH, NON_MONSTER, kill_types[i], cause.c_str(), true); } // The player survived! Yay! you.redraw_strength = true; you.redraw_intelligence = true; you.redraw_dexterity = true; burden_change(); } static void _shuffle_card(int power, deck_rarity_type rarity) { stat_type stats[3] = {STAT_STRENGTH, STAT_DEXTERITY, STAT_INTELLIGENCE}; int old_base[3] = {you.strength, you.dex, you.intel}; int old_max[3] = {you.max_strength, you.max_dex, you.max_intel}; int modifiers[3]; int perm[3] = { 0, 1, 2 }; for (int i = 0; i < 3; ++i) modifiers[i] = stat_modifier(stats[i]); std::random_shuffle( perm, perm + 3 ); int new_base[3]; int new_max[3]; for (int i = 0; i < 3; ++i) { new_base[perm[i]] = old_base[i] - modifiers[i] + modifiers[perm[i]]; new_max[perm[i]] = old_max[i] - modifiers[i] + modifiers[perm[i]]; } // Did the shuffling kill the player? kill_method_type kill_types[3] = { KILLED_BY_WEAKNESS, KILLED_BY_CLUMSINESS, KILLED_BY_STUPIDITY }; std::string cause = "the Shuffle card"; if (crawl_state.is_god_acting()) { god_type which_god = crawl_state.which_god_acting(); if (crawl_state.is_god_retribution()) cause = "the wrath of " + god_name(which_god); else { if (which_god == GOD_XOM) cause = "the capriciousness of Xom"; else cause = "the 'helpfulness' of " + god_name(which_god); } } for (int i = 0; i < 3; ++i) if (new_base[i] < 1 || new_max[i] < 1) { ouch(INSTANT_DEATH, NON_MONSTER, kill_types[i], cause.c_str(), true); } // The player survived! // Sometimes you just long for Python. you.strength = new_base[0]; you.dex = new_base[1]; you.intel = new_base[2]; you.max_strength = new_max[0]; you.max_dex = new_max[1]; you.max_intel = new_max[2]; you.redraw_strength = true; you.redraw_intelligence = true; you.redraw_dexterity = true; you.redraw_evasion = true; burden_change(); } static void _experience_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); if (you.experience_level < 27) { mpr("You feel more experienced."); const unsigned long xp_cap = 1 + exp_needed(2 + you.experience_level); // power_level 2 means automatic level gain. if (power_level == 2) you.experience = xp_cap; else { // Likely to give a level gain (power of ~500 is reasonable // at high levels even for non-Nemelexites, so 50,000 XP.) // But not guaranteed. // Overrides archmagi effect, like potions of experience. you.experience += power * 100; if (you.experience > xp_cap) you.experience = xp_cap; } } else mpr("You feel knowledgeable."); // Put some free XP into pool; power_level 2 means fill pool you.exp_available += power * 50; if (power_level >= 2 || you.exp_available > FULL_EXP_POOL) you.exp_available = FULL_EXP_POOL; level_change(); } static void _remove_bad_mutation() { // Ensure that only bad mutations are removed. if (!delete_mutation(RANDOM_BAD_MUTATION, false, false, false, true)) mpr("You feel transcendent for a moment."); } static void _helix_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); if (power_level == 0) { switch (how_mutated() ? random2(3) : 0) { case 0: mutate(RANDOM_MUTATION); break; case 1: delete_mutation(RANDOM_MUTATION); mutate(RANDOM_MUTATION); break; case 2: delete_mutation(RANDOM_MUTATION); break; } } else if (power_level == 1) { switch (how_mutated() ? random2(3) : 0) { case 0: mutate(coinflip() ? RANDOM_GOOD_MUTATION : RANDOM_MUTATION); break; case 1: if (coinflip()) _remove_bad_mutation(); else delete_mutation(RANDOM_MUTATION); break; case 2: if (coinflip()) { if (coinflip()) { _remove_bad_mutation(); mutate(RANDOM_MUTATION); } else { delete_mutation(RANDOM_MUTATION); mutate(RANDOM_GOOD_MUTATION); } } else { delete_mutation(RANDOM_MUTATION); mutate(RANDOM_MUTATION); } break; } } else { switch (random2(3)) { case 0: _remove_bad_mutation(); break; case 1: mutate(RANDOM_GOOD_MUTATION); break; case 2: if (coinflip()) { // If you get unlucky, you could get here with no bad // mutations and simply get a mutation effect. Oh well. _remove_bad_mutation(); mutate(RANDOM_MUTATION); } else { delete_mutation(RANDOM_MUTATION); mutate(RANDOM_GOOD_MUTATION); } break; } } } static void _sage_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); int c; // how much to weight your skills if (power_level == 0) c = 0; else if (power_level == 1) c = random2(10) + 1; else c = 10; // FIXME: yet another reproduction of random_choose_weighted // Ah for Python: // skill = random_choice([x*(40-x)*c/10 for x in skill_levels]) int totalweight = 0; int result = -1; for (int i = 0; i < NUM_SKILLS; ++i ) { if (skill_name(i) == NULL) continue; if (you.skills[i] < MAX_SKILL_LEVEL) { // Choosing a skill is likelier if you are somewhat skilled in it. const int curweight = 1 + you.skills[i] * (40 - you.skills[i]) * c; totalweight += curweight; if (x_chance_in_y(curweight, totalweight)) result = i; } } if (result == -1) mpr("You feel omnipotent."); // All skills maxed. else { you.set_duration(DUR_SAGE, random2(1800) + 200); you.sage_bonus_skill = static_cast(result); you.sage_bonus_degree = power / 25; mprf(MSGCH_PLAIN, "You feel studious about %s.", skill_name(result)); } } static void _create_pond(const coord_def& center, int radius, bool allow_deep) { for (radius_iterator ri(center, radius, false); ri; ++ri) { const coord_def p = *ri; if (p != you.pos() && coinflip()) { if (grd(p) == DNGN_FLOOR) { dungeon_feature_type feat; if (allow_deep && coinflip()) feat = DNGN_DEEP_WATER; else feat = DNGN_SHALLOW_WATER; dungeon_terrain_changed(p, feat); } } } } static void _deepen_water(const coord_def& center, int radius) { for (radius_iterator ri(center, radius, false); ri; ++ri) { // FIXME The iteration shouldn't affect the later squares in the // same iteration, i.e., a newly-flooded square shouldn't count // in the decision as to whether to make the next square flooded. const coord_def p = *ri; if (grd(p) == DNGN_SHALLOW_WATER && p != you.pos() && x_chance_in_y(1+count_neighbours(p, DNGN_DEEP_WATER), 8)) { dungeon_terrain_changed(p, DNGN_DEEP_WATER); } if (grd(p) == DNGN_FLOOR && random2(3) < random2(count_neighbours(p, DNGN_DEEP_WATER) + count_neighbours(p, DNGN_SHALLOW_WATER))) { dungeon_terrain_changed(p, DNGN_SHALLOW_WATER); } } } static void _water_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); if (power_level == 0) { mpr("You create a pond!"); _create_pond(you.pos(), 4, false); } else if (power_level == 1) { mpr("You feel the tide rushing in!"); _create_pond(you.pos(), 6, true); for (int i = 0; i < 2; ++i) _deepen_water(you.pos(), 6); } else { mpr("Water floods your area!"); // Flood all visible squares. for (radius_iterator ri( you.pos(), LOS_RADIUS, false ); ri; ++ri) { coord_def p = *ri; destroy_trap(p); if (grd(p) == DNGN_FLOOR) { dungeon_feature_type new_feature = DNGN_SHALLOW_WATER; if (p != you.pos() && coinflip()) new_feature = DNGN_DEEP_WATER; dungeon_terrain_changed(p, new_feature); } } } } static void _glass_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); const int radius = (power_level == 2) ? 1000 : random2(power/40) + 2; vitrify_area(radius); } static void _dowsing_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); bool things_to_do[3] = { false, false, false }; things_to_do[random2(3)] = true; if (power_level == 1) things_to_do[random2(3)] = true; if (power_level >= 2) { for (int i = 0; i < 3; ++i) things_to_do[i] = true; } if (things_to_do[0]) cast_detect_secret_doors( random2(power/4) ); if (things_to_do[1]) detect_traps( random2(power/4) ); if (things_to_do[2]) { you.set_duration(DUR_TELEPATHY, random2(power/4), 0, "You feel telepathic!"); } } static bool _trowel_card(int power, deck_rarity_type rarity) { // Early exit: don't clobber important features. if (is_critical_feature(grd(you.pos()))) { mpr("The dungeon trembles momentarily."); return (false); } const int power_level = get_power_level(power, rarity); bool done_stuff = false; if (power_level >= 2) { // Generate a portal to something. const map_def *map = random_map_for_tag("trowel_portal", false); if (!map) { mpr("A buggy portal flickers into view, then vanishes."); } else { { no_messages n; dgn_place_map(map, true, true, you.pos()); } mpr("A mystic portal forms."); } done_stuff = true; } else if (power_level == 1) { if (coinflip()) { // Create a random bad statue and a friendly, timed golem. // This could be really bad, because they're placed adjacent // to you... int num_made = 0; const monster_type statues[] = { MONS_ORANGE_STATUE, MONS_SILVER_STATUE, MONS_ICE_STATUE }; if (create_monster( mgen_data::hostile_at( RANDOM_ELEMENT(statues), "the Trowel card", true, 0, 0, you.pos())) != -1) { mpr("A menacing statue appears!"); num_made++; } const monster_type golems[] = { MONS_CLAY_GOLEM, MONS_WOOD_GOLEM, MONS_STONE_GOLEM, MONS_IRON_GOLEM, MONS_CRYSTAL_GOLEM, MONS_TOENAIL_GOLEM }; if (create_monster( mgen_data(RANDOM_ELEMENT(golems), BEH_FRIENDLY, &you, 5, 0, you.pos(), MHITYOU)) != -1) { mpr("You construct a golem!"); num_made++; } if (num_made == 2) mpr("The constructs glare at each other."); done_stuff = (num_made > 0); } else { // Do-nothing (effectively): create a cosmetic feature const coord_def pos = pick_adjacent_free_square(you.pos()); if (in_bounds(pos)) { const dungeon_feature_type statfeat[] = { DNGN_GRANITE_STATUE, DNGN_ORCISH_IDOL }; // We leave the items on the square grd(pos) = RANDOM_ELEMENT(statfeat); mpr("A statue takes form beside you."); done_stuff = true; } } } else { // Generate an altar. if (grd(you.pos()) == DNGN_FLOOR) { // Might get GOD_NO_GOD and no altar. god_type rgod = static_cast(random2(NUM_GODS)); if (rgod == GOD_JIYVA && jiyva_is_dead()) rgod = GOD_NO_GOD; grd(you.pos()) = altar_for_god(rgod); if (grd(you.pos()) != DNGN_FLOOR) { done_stuff = true; mprf("An altar to %s grows from the floor before you!", god_name(rgod).c_str()); } } } if (!done_stuff) canned_msg(MSG_NOTHING_HAPPENS); return (done_stuff); } static void _genie_card(int power, deck_rarity_type rarity) { if (coinflip()) { mpr("A genie takes form and thunders: " "\"Choose your reward, mortal!\""); more(); acquirement( OBJ_RANDOM, AQ_CARD_GENIE ); } else { mpr("A genie takes form and thunders: " "\"You disturbed me, fool!\""); potion_effect(coinflip() ? POT_DEGENERATION : POT_DECAY, 40); } } // Special case for *your* god, maybe? static void _godly_wrath() { int tries = 100; while (tries-- > 0) { god_type god = static_cast(random2(NUM_GODS - 1) + 1); // Don't recursively make player draw from the Deck of Punishment. if (god == GOD_NEMELEX_XOBEH) continue; // Stop once we find a god willing to punish the player. if (divine_retribution(god)) break; } if (tries <= 0) mpr("You somehow manage to escape divine attention..."); } static void _curse_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); mpr("You feel a malignant aura surround you."); if (power_level >= 2) { // Curse (almost) everything + chance of decay. while (curse_an_item(one_chance_in(6), true) && !one_chance_in(1000)) ; } else if (power_level == 1) { // Curse an average of four items. do curse_an_item(false); while (!one_chance_in(4)); } else { // Curse 1.5 items on average. curse_an_item(false); if (coinflip()) curse_an_item(false); } } static void _crusade_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); if (power_level >= 1) { // A chance to convert opponents. for (monster_iterator mi(&you.get_los()); mi; ++mi) { if (mi->friendly() || mi->holiness() != MH_NATURAL || mons_is_unique(mi->type) || mons_immune_magic(*mi) || player_will_anger_monster(*mi)) { continue; } // Note that this bypasses the magic resistance // (though not immunity) check. Specifically, // you can convert Killer Klowns this way. // Might be too good. if (mi->hit_dice * 35 < random2(power)) { simple_monster_message(*mi, " is converted."); if (one_chance_in(5 - power_level)) { mi->attitude = ATT_FRIENDLY; // If you worship a god that lets you recruit // permanent followers, or a god allied with one, // count this as a recruitment. if (is_good_god(you.religion) || you.religion == GOD_BEOGH && mons_species(mi->type) == MONS_ORC && !mi->is_summoned() && !mi->is_shapeshifter()) { // Prevent assertion if the monster was // previously worshipping a different god, // rather than already worshipping your god or // being an atheist. mi->god = GOD_NO_GOD; mons_make_god_gift(*mi, is_good_god(you.religion) ? GOD_SHINING_ONE : GOD_BEOGH); } } else mi->add_ench(ENCH_CHARM); } } } abjuration(power/4); } static void _summon_demon_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); demon_class_type dct; if (power_level >= 2) dct = DEMON_GREATER; else if (power_level == 1) dct = DEMON_COMMON; else dct = DEMON_LESSER; // FIXME: The manual testing for message printing is there because // we can't rely on create_monster() to do it for us. This is // because if you are completely surrounded by walls, create_monster() // will never manage to give a position which isn't (-1,-1) // and thus not print the message. // This hack appears later in this file as well. if (create_monster( mgen_data(summon_any_demon(dct), BEH_FRIENDLY, &you, std::min(power/50 + 1, 6), 0, you.pos(), MHITYOU), false) == -1) { mpr("You see a puff of smoke."); } } static void _summon_any_monster(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); monster_type mon_chosen = NUM_MONSTERS; coord_def chosen_spot; int num_tries; if (power_level == 0) num_tries = 1; else if (power_level == 1) num_tries = 4; else num_tries = 18; for (int i = 0; i < num_tries; ++i) { int dx, dy; do { dx = random2(3) - 1; dy = random2(3) - 1; } while (dx == 0 && dy == 0); coord_def delta(dx,dy); monster_type cur_try; do { cur_try = random_monster_at_grid(you.pos() + delta); } while (mons_is_unique(cur_try)); if (mon_chosen == NUM_MONSTERS || mons_power(mon_chosen) < mons_power(cur_try)) { mon_chosen = cur_try; chosen_spot = you.pos(); } } if (mon_chosen == NUM_MONSTERS) // Should never happen. return; const bool friendly = (power_level > 0 || !one_chance_in(4)); if (create_monster(mgen_data(mon_chosen, friendly ? BEH_FRIENDLY : BEH_HOSTILE, &you, 3, 0, chosen_spot, MHITYOU), false) == -1) { mpr("You see a puff of smoke."); } } static void _summon_dancing_weapon(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); const bool friendly = (power_level > 0 || !one_chance_in(4)); const int mon = create_monster( mgen_data(MONS_DANCING_WEAPON, friendly ? BEH_FRIENDLY : BEH_HOSTILE, &you, power_level + 3, 0, you.pos(), MHITYOU), false); // Given the abundance of Nemelex decks, not setting hard reset // leaves a trail of weapons behind, most of which just get // offered to Nemelex again, adding an unnecessary source of // piety. if (mon != -1) { // Override the weapon. ASSERT(menv[mon].weapon() != NULL); item_def& wpn(*menv[mon].weapon()); // FIXME: Mega-hack (breaks encapsulation too). wpn.flags &= ~ISFLAG_RACIAL_MASK; if (power_level == 0) { // Wimpy, negative-enchantment weapon. wpn.plus = -random2(4); wpn.plus2 = -random2(4); wpn.sub_type = (coinflip() ? WPN_DAGGER : WPN_CLUB); set_item_ego_type(wpn, OBJ_WEAPONS, SPWPN_NORMAL); } else if (power_level == 1) { // This is getting good. wpn.plus = random2(4) - 1; wpn.plus2 = random2(4) - 1; wpn.sub_type = (coinflip() ? WPN_LONG_SWORD : WPN_HAND_AXE); if (coinflip()) { set_item_ego_type(wpn, OBJ_WEAPONS, coinflip() ? SPWPN_FLAMING : SPWPN_FREEZING); } } else if (power_level == 2) { // Rare and powerful. wpn.plus = random2(4) + 2; wpn.plus2 = random2(4) + 2; wpn.sub_type = (coinflip() ? WPN_KATANA : WPN_EXECUTIONERS_AXE); set_item_ego_type(wpn, OBJ_WEAPONS, coinflip() ? SPWPN_SPEED : SPWPN_ELECTROCUTION); } item_colour(wpn); menv[mon].flags |= MF_HARD_RESET; ghost_demon newstats; newstats.init_dancing_weapon(wpn, power / 4); menv[mon].set_ghost(newstats); menv[mon].dancing_weapon_init(); } else mpr("You see a puff of smoke."); } static void _summon_flying(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); const bool friendly = (power_level > 0 || !one_chance_in(4)); const monster_type flytypes[] = { MONS_BUTTERFLY, MONS_BUMBLEBEE, MONS_INSUBSTANTIAL_WISP, MONS_VAPOUR, MONS_YELLOW_WASP, MONS_RED_WASP }; // Choose what kind of monster. // Be nice and don't summon friendly invisibles. monster_type result = MONS_PROGRAM_BUG; do result = flytypes[random2(4) + power_level]; while (friendly && mons_class_flag(result, M_INVIS) && !you.can_see_invisible()); for (int i = 0; i < power_level * 5 + 2; ++i) { create_monster( mgen_data(result, friendly ? BEH_FRIENDLY : BEH_HOSTILE, &you, std::min(power/50 + 1, 6), 0, you.pos(), MHITYOU)); } } static void _summon_skeleton(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); const bool friendly = (power_level > 0 || !one_chance_in(4)); const monster_type skeltypes[] = { MONS_SKELETON_LARGE, MONS_SKELETAL_WARRIOR, MONS_SKELETAL_DRAGON }; if (create_monster(mgen_data(skeltypes[power_level], friendly ? BEH_FRIENDLY : BEH_HOSTILE, &you, std::min(power/50 + 1, 6), 0, you.pos(), MHITYOU), false) == -1) { mpr("You see a puff of smoke."); } } static void _summon_ugly(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); const bool friendly = (power_level > 0 || !one_chance_in(4)); monster_type ugly; if (power_level >= 2) ugly = MONS_VERY_UGLY_THING; else if (power_level == 1) ugly = coinflip() ? MONS_VERY_UGLY_THING : MONS_UGLY_THING; else ugly = MONS_UGLY_THING; if (create_monster(mgen_data(ugly, friendly ? BEH_FRIENDLY : BEH_HOSTILE, &you, std::min(power/50 + 1, 6), 0, you.pos(), MHITYOU), false) == -1) { mpr("You see a puff of smoke."); } } static int _card_power(deck_rarity_type rarity) { int result = 0; if (you.penance[GOD_NEMELEX_XOBEH]) { result -= you.penance[GOD_NEMELEX_XOBEH]; } else if (you.religion == GOD_NEMELEX_XOBEH) { result = you.piety; result *= (you.skills[SK_EVOCATIONS] + 25); result /= 27; } result += you.skills[SK_EVOCATIONS] * 9; if (rarity == DECK_RARITY_RARE) result += 150; else if (rarity == DECK_RARITY_LEGENDARY) result += 300; return (result); } bool card_effect(card_type which_card, deck_rarity_type rarity, unsigned char flags, bool tell_card) { bool rc = true; const int power = _card_power(rarity); const god_type god = (crawl_state.is_god_acting()) ? crawl_state.which_god_acting() : GOD_NO_GOD; #ifdef DEBUG_DIAGNOSTICS msg::streams(MSGCH_DIAGNOSTICS) << "Card power: " << power << ", rarity: " << static_cast(rarity) << std::endl; #endif if (tell_card) { // These card types will usually give this message in the targetting // prompt, and the cases where they don't are handled specially. if (which_card != CARD_VITRIOL && which_card != CARD_FLAME && which_card != CARD_FROST && which_card != CARD_HAMMER && which_card != CARD_SPARK && which_card != CARD_PAIN && which_card != CARD_VENOM) { mprf("You have drawn %s.", card_name(which_card)); } } if (which_card == CARD_XOM && !crawl_state.is_god_acting()) { if (you.religion == GOD_XOM) { // Being a self-centered deity, Xom *always* finds this // maximally hilarious. god_speaks(GOD_XOM, "Xom roars with laughter!"); you.gift_timeout = 255; } else if (you.penance[GOD_XOM]) god_speaks(GOD_XOM, "Xom laughs nastily."); } switch (which_card) { case CARD_PORTAL: _portal_card(power, rarity); break; case CARD_WARP: _warp_card(power, rarity); break; case CARD_SWAP: _swap_monster_card(power, rarity); break; case CARD_VELOCITY: _velocity_card(power, rarity); break; case CARD_DAMNATION: _damnation_card(power, rarity); break; case CARD_SOLITUDE: cast_dispersal(power/4); break; case CARD_ELIXIR: _elixir_card(power, rarity); break; case CARD_BATTLELUST: _battle_lust_card(power, rarity); break; case CARD_METAMORPHOSIS: _metamorphosis_card(power, rarity); break; case CARD_HELM: _helm_card(power, rarity); break; case CARD_BLADE: _blade_card(power, rarity); break; case CARD_SHADOW: _shadow_card(power, rarity); break; case CARD_POTION: _potion_card(power, rarity); break; case CARD_FOCUS: _focus_card(power, rarity); break; case CARD_SHUFFLE: _shuffle_card(power, rarity); break; case CARD_EXPERIENCE: _experience_card(power, rarity); break; case CARD_HELIX: _helix_card(power, rarity); break; case CARD_SAGE: _sage_card(power, rarity); break; case CARD_WATER: _water_card(power, rarity); break; case CARD_GLASS: _glass_card(power, rarity); break; case CARD_DOWSING: _dowsing_card(power, rarity); break; case CARD_MINEFIELD: _minefield_card(power, rarity); break; case CARD_STAIRS: _stairs_card(power, rarity); break; case CARD_GENIE: _genie_card(power, rarity); break; case CARD_CURSE: _curse_card(power, rarity); break; case CARD_WARPWRIGHT: _warpwright_card(power, rarity); break; case CARD_FLIGHT: _flight_card(power, rarity); break; case CARD_TOMB: entomb(power); break; case CARD_WRAITH: drain_exp(false); lose_level(); break; case CARD_WRATH: _godly_wrath(); break; case CARD_CRUSADE: _crusade_card(power, rarity); break; case CARD_SUMMON_DEMON: _summon_demon_card(power, rarity); break; case CARD_SUMMON_ANIMAL: summon_animals(random2(power/3)); break; case CARD_SUMMON_ANY: _summon_any_monster(power, rarity); break; case CARD_SUMMON_WEAPON: _summon_dancing_weapon(power, rarity); break; case CARD_SUMMON_FLYING: _summon_flying(power, rarity); break; case CARD_SUMMON_SKELETON: _summon_skeleton(power, rarity); break; case CARD_SUMMON_UGLY: _summon_ugly(power, rarity); break; case CARD_XOM: xom_acts(5 + random2(power/10)); break; case CARD_TROWEL: rc = _trowel_card(power, rarity); break; case CARD_SPADE: your_spells(SPELL_DIG, random2(power/4), false); break; case CARD_BANSHEE: mass_enchantment(ENCH_FEAR, power, MHITYOU); break; case CARD_TORMENT: torment(TORMENT_CARDS, you.pos()); break; case CARD_VENOM: if (coinflip()) { mprf("You have drawn %s.", card_name(which_card)); your_spells(SPELL_OLGREBS_TOXIC_RADIANCE, random2(power/4), false); } else rc = _damaging_card(which_card, power, rarity); break; case CARD_VITRIOL: case CARD_FLAME: case CARD_FROST: case CARD_HAMMER: case CARD_SPARK: case CARD_PAIN: rc = _damaging_card(which_card, power, rarity); break; case CARD_BARGAIN: you.increase_duration(DUR_BARGAIN, random2(power) + random2(power) + 2); break; case CARD_MAP: if (!magic_mapping( random2(power/10) + 15, random2(power), true)) mpr("The map is blank."); break; case CARD_WILD_MAGIC: // Yes, high power is bad here. MiscastEffect( &you, god == GOD_NO_GOD ? NON_MONSTER : -god, SPTYP_RANDOM, random2(power/15) + 5, random2(power), "a card of wild magic" ); break; case CARD_FAMINE: if (you.is_undead == US_UNDEAD) mpr("You feel rather smug."); else set_hunger(500, true); break; case CARD_FEAST: if (you.is_undead == US_UNDEAD) mpr("You feel a horrible emptiness."); else set_hunger(12000, true); break; case CARD_SWINE: if (!transform(1 + power/2 + random2(power/2), TRAN_PIG, true)) { mpr("You feel like a pig."); break; } break; case NUM_CARDS: // The compiler will complain if any card remains unhandled. mpr("You have drawn a buggy card!"); break; } if (you.religion == GOD_XOM && !rc) { god_speaks(GOD_XOM, "\"How boring, let's spice things up a little.\""); xom_acts(abs(you.piety - HALF_MAX_PIETY)); } if (you.religion == GOD_NEMELEX_XOBEH && !rc) simple_god_message(" seems disappointed in you."); return rc; } bool top_card_is_known(const item_def &deck) { if (!is_deck(deck)) return (false); unsigned char flags; get_card_and_flags(deck, -1, flags); return (flags & CFLAG_MARKED); } card_type top_card(const item_def &deck) { if (!is_deck(deck)) return (NUM_CARDS); unsigned char flags; card_type card = get_card_and_flags(deck, -1, flags); UNUSED(flags); return (card); } bool is_deck(const item_def &item) { return (item.base_type == OBJ_MISCELLANY && item.sub_type >= MISC_DECK_OF_ESCAPE && item.sub_type <= MISC_DECK_OF_DEFENCE); } bool bad_deck(const item_def &item) { if (!is_deck(item)) return (false); return (!item.props.exists("cards") || item.props["cards"].get_type() != SV_VEC || item.props["cards"].get_vector().get_type() != SV_BYTE || cards_in_deck(item) == 0); } deck_rarity_type deck_rarity(const item_def &item) { ASSERT( is_deck(item) ); return static_cast(item.special); } unsigned char deck_rarity_to_color(deck_rarity_type rarity) { switch (rarity) { case DECK_RARITY_COMMON: { const unsigned char colours[] = {LIGHTBLUE, GREEN, CYAN, RED}; return RANDOM_ELEMENT(colours); } case DECK_RARITY_RARE: return (coinflip() ? MAGENTA : BROWN); case DECK_RARITY_LEGENDARY: return LIGHTMAGENTA; } return (WHITE); } void init_deck(item_def &item) { CrawlHashTable &props = item.props; ASSERT(is_deck(item)); ASSERT(!props.exists("cards")); ASSERT(item.plus > 0); ASSERT(item.plus <= 127); ASSERT(item.special >= DECK_RARITY_COMMON && item.special <= DECK_RARITY_LEGENDARY); const store_flags fl = SFLAG_CONST_TYPE; props["cards"].new_vector(SV_BYTE, fl).resize((vec_size)item.plus); props["card_flags"].new_vector(SV_BYTE, fl).resize((vec_size)item.plus); props["drawn_cards"].new_vector(SV_BYTE, fl); for (int i = 0; i < item.plus; ++i) { bool was_odd = false; card_type card = _random_card(item, was_odd); unsigned char flags = 0; if (was_odd) flags = CFLAG_ODDITY; _set_card_and_flags(item, i, card, flags); } ASSERT(cards_in_deck(item) == item.plus); props["num_marked"] = (char) 0; props["non_brownie_draws"] = (char) 0; props.assert_validity(); item.plus2 = 0; item.colour = deck_rarity_to_color((deck_rarity_type) item.special); } static void _unmark_deck(item_def& deck) { if (!is_deck(deck)) return; CrawlHashTable &props = deck.props; if (!props.exists("card_flags")) return; CrawlVector &flags = props["card_flags"].get_vector(); for (unsigned int i = 0; i < flags.size(); ++i) { flags[i] = static_cast((static_cast(flags[i]) & ~CFLAG_MARKED)); } // We'll be mean and leave non_brownie_draws as-is. props["num_marked"] = static_cast(0); } static void _unmark_and_shuffle_deck(item_def& deck) { if (is_deck(deck)) { _unmark_deck(deck); _shuffle_deck(deck); } } static bool _shuffle_all_decks_on_level() { bool success = false; for (int i = 0; i < MAX_ITEMS; ++i) { item_def& item(mitm[i]); if (item.is_valid() && is_deck(item)) { #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Shuffling: %s on level %d, branch %d", item.name(DESC_PLAIN).c_str(), static_cast(you.your_level), static_cast(you.where_are_you)); #endif _unmark_and_shuffle_deck(item); success = true; } } return success; } static bool _shuffle_inventory_decks() { bool success = false; for (int i = 0; i < ENDOFPACK; ++i) { item_def& item(you.inv[i]); if (item.is_valid() && is_deck(item)) { #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Shuffling in inventory: %s", item.name(DESC_PLAIN).c_str()); #endif _unmark_and_shuffle_deck(item); success = true; } } return success; } void nemelex_shuffle_decks() { bool success = false; if (apply_to_all_dungeons(_shuffle_all_decks_on_level)) success = true; if (_shuffle_inventory_decks()) success = true; if (success) god_speaks(GOD_NEMELEX_XOBEH, "You hear Nemelex Xobeh chuckle."); }