/* * File: decks.cc * Summary: Functions with decks of cards. * * Modified for Crawl Reference by $Author$ on $Date$ * * Change History (most recent first): * * <1> -/--/-- LRH Created */ #include "AppHdr.h" #include "decks.h" #include #include #include "externs.h" #include "beam.h" #include "cio.h" #include "dungeon.h" #include "effects.h" #include "files.h" #include "food.h" #include "invent.h" #include "it_use2.h" #include "item_use.h" #include "itemprop.h" #include "items.h" #include "maps.h" #include "message.h" #include "misc.h" #include "monplace.h" #include "monstuff.h" #include "mutation.h" #include "ouch.h" #include "output.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-util.h" #include "state.h" #include "stuff.h" #include "terrain.h" #include "transfor.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} }, 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 static_cast(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"]; CrawlVector &flags = props["card_flags"]; 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"]; CrawlVector &flags = props["card_flags"]; 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_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 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; int i = 0; card_type result = NUM_CARDS; while ( pdeck[i].card != NUM_CARDS ) { const card_with_weights& cww = pdeck[i]; totalweight += cww.weight[rarity]; if ( random2(totalweight) < cww.weight[rarity] ) result = cww.card; ++i; } 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 bool _wielding_deck() { if ( you.equip[EQ_WEAPON] == -1 ) return false; return is_deck(you.inv[you.equip[EQ_WEAPON]]); } static void _remember_drawn_card(item_def& deck, card_type card) { 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 ( drawn.size() >= 2 ) _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, OBJ_MISCELLANY, true, true, true, 0, NULL, OPER_EVOKE ); if ( slot == PROMPT_ABORT ) { canned_msg(MSG_OK); 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"]; 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 && random2(2) < already_seen ) 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 the 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(); } } // 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() { cursor_control con(false); if ( !_wielding_deck() ) { mpr("You aren't wielding a deck!"); crawl_state.zero_turns_taken(); return false; } item_def& deck(you.inv[you.equip[EQ_WEAPON]]); 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; } 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); } else ; // 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 ) { unsigned int selected = draws.size(); 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 Enter to accept."); _redraw_stacked_cards(draws, selected); // Hand-hacked implementation, instead of using Menu. Oh well. while (true) { 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 >= '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); 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 | CFLAG_SEEN | CFLAG_MARKED); } mpr("You draw... (choose one card)"); for ( int i = 0; i < num_to_draw; ++i ) { msg::streams(MSGCH_PROMPT) << (static_cast(i + 'a')) << " - " << card_name(draws[i]) << std::endl; } int selected = -1; while ( true ) { const int keyin = tolower(get_ch()); 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; for ( int i = 0; i < num_to_draw; ++i ) _remember_drawn_card(deck, draws[i]); 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], 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: // 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); // 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 ( random2(3000) < c * you.penance[GOD_NEMELEX_XOBEH] ) { 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); // 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 = 1; 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 ( random2(500) < power ) ++power_level; // deliberate fall-through case DECK_RARITY_RARE: if ( random2(700) < power ) ++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; } const bool was_controlled = player_control_teleport(); if ( controlled && !was_controlled ) you.duration[DUR_CONTROL_TELEPORT] = 6; // 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 other monsters // nearby. monsters *mon_to_swap = choose_random_nearby_monster(0); if ( !mon_to_swap ) { mpr("You spin around."); } else { monsters& mon(*mon_to_swap); const coord_def newpos = mon.pos(); // pick the monster up mgrd(newpos) = NON_MONSTER; mon.x = you.x_pos; mon.y = you.y_pos; // plunk it down mgrd(mon.pos()) = monster_index(mon_to_swap); // move you to its previous location you.moveto(newpos); } } 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; int fx = -1, fy = -1; for ( int dx = -1; dx <= 1; ++dx ) for ( int dy = -1; dy <= 1; ++dy ) { if ( dx == 0 && dy == 0 ) continue; const int rx = you.x_pos + dx; const int ry = you.y_pos + dy; if ( grd[rx][ry] == DNGN_FLOOR && trap_at_xy(rx,ry) == -1 && one_chance_in(++count) ) { fx = rx; fy = ry; } } if ( fx >= 0 ) // found a spot { if ( place_specific_trap(fx, fy, TRAP_TELEPORT) ) { // mark it discovered if enough power if ( get_power_level(power, rarity) >= 1 ) { const int i = trap_at_xy(fx, fy); if (i != -1) // should always happen grd[fx][fy] = trap_category(env.trap[i].type); } } } } static void _flight_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); if ( power_level == 0 ) transform(random2(power/4), coinflip() ? TRAN_SPIDER : TRAN_BAT); if ( power_level >= 1 ) { cast_fly(random2(power/4)); cast_swiftness(random2(power/4)); } if ( power_level == 2 ) { if (is_valid_shaft_level() && grd[you.x_pos][you.y_pos] == DNGN_FLOOR) { if ( place_specific_trap(you.x_pos, you.y_pos, TRAP_SHAFT) ) { const int i = trap_at_xy(you.x_pos, you.y_pos); grd[you.x_pos][you.y_pos] = trap_category(env.trap[i].type); mpr("A shaft materializes beneath you!"); } } } if ( one_chance_in(4 - power_level) ) potion_effect(POT_INVISIBILITY, random2(power)/4); } 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 ( int dx = -radius; dx <= radius; ++dx ) for ( int dy = -radius; dy <= radius; ++dy ) { if ( dx*dx + dy*dy > radius*radius + 1 ) continue; if ( dx == 0 && dy == 0 ) continue; const int rx = you.x_pos + dx; const int ry = you.y_pos + dy; if ( !in_bounds(rx, ry) ) continue; if ( grd[rx][ry] == DNGN_FLOOR && trap_at_xy(rx,ry) == -1 && one_chance_in(4 - power_level) ) { if ( you.level_type == LEVEL_ABYSS ) grd[rx][ry] = coinflip() ? DNGN_DEEP_WATER : DNGN_LAVA; else place_specific_trap(rx, ry, TRAP_RANDOM); } } } static int _drain_monsters(int x, int y, int pow, int garbage) { UNUSED( garbage ); if ( coord_def(x,y) == you.pos() ) drain_exp(); else { const int mnstr = mgrd[x][y]; if (mnstr == NON_MONSTER) return 0; monsters& mon = menv[mnstr]; if (mons_res_negative_energy(&mon) > 0) simple_monster_message(&mon, " is unaffected."); else { simple_monster_message(&mon, " is drained."); if (random2(20) < pow/60) { mon.hit_dice--; mon.experience = 0; } mon.max_hit_points -= 2 + random2(pow/50); mon.hit_points -= 2 + random2(50); if (mon.hit_points >= mon.max_hit_points) mon.hit_points = mon.max_hit_points; if (mon.hit_dice < 1) mon.hit_points = 0; if ( mon.hit_points <= 0 ) monster_die( &mon, KILL_YOU, 0 ); } } 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(); const int power_level = get_power_level(power, rarity); dist target; bolt beam; 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_ICE_BOLT, 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 ) { _mass_drain(power); return true; } else ztype = painzaps[power_level]; break; default: break; } if ( spell_direction( target, 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 ) { you.hp = you.hp_max; you.magic_points = 0; } else if ( power_level >= 2 ) { you.hp = you.hp_max; 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.duration[DUR_SLAYING] = random2(power/6) + 1; mpr("You feel deadly."); } else if ( power_level == 1 ) { you.duration[DUR_BUILDING_RAGE] = 1; mpr("You feel your rage building."); } else if ( power_level == 0 ) potion_effect(POT_MIGHT, random2(power/4)); } // doesn't allow transformation into bat 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 = coinflip() ? TRAN_SPIDER : TRAN_ICE_BEAST; transform(random2(power/4), trans); } static void _helm_card(int power, deck_rarity_type rarity) { const int power_level = get_power_level(power, rarity); bool do_forescry = false; bool do_stoneskin = false; bool do_shield = false; int num_resists = 0; if ( power_level >= 2 ) { if ( coinflip() ) do_forescry = true; if ( coinflip() ) do_stoneskin = true; if ( coinflip() ) do_shield = true; num_resists = random2(4); } if ( power_level >= 1 ) { if ( coinflip() ) do_forescry = true; if ( coinflip() ) do_stoneskin = true; if ( coinflip() ) do_shield = true; } if ( power_level >= 0 ) { if ( coinflip() ) do_forescry = true; else do_stoneskin = true; } if ( do_forescry ) cast_forescry( 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 ( random2(4-i) < num_resists ) { // Add a temporary resist you.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.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 ) { dancing_weapon( random2(power/4), false ); } 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.equip[EQ_WEAPON] == -1 ) { msg::stream << "Your " << your_hand(true) << " twitch." << std::endl; } else { msg::stream << "Your weapon vibrates for a moment." << std::endl; } } } } 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.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) ) pot = POT_MAGIC; 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, 0, kill_types[i], cause.c_str(), true); // The player survived! 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, 0, 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 > 20000) you.exp_available = 20000; level_change(); } static void _remove_bad_mutation() { if (!delete_mutation(RANDOM_BAD_MUTATION, false)) 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 ) { const int curweight = 1 + you.skills[i] * (40-you.skills[i]) * c; totalweight += curweight; if ( random2(totalweight) < curweight ) result = i; } } if ( result == -1 ) { mpr("You feel omnipotent."); // all skills maxed } else { you.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) { radius_iterator ri(center, radius, false); for ( ; ri; ++ri ) { const coord_def p = *ri; if ( p != you.pos() && coinflip() ) { destroy_trap(p); if ( grd(p) == DNGN_FLOOR ) { if ( allow_deep ) grd(p) = coinflip() ? DNGN_SHALLOW_WATER : DNGN_DEEP_WATER; else grd(p) = DNGN_SHALLOW_WATER; } } } } static void _deepen_water(const coord_def& center, int radius) { radius_iterator ri(center, radius, false); for ( ; 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() && random2(8) < 1 + count_neighbours(p.x, p.y, DNGN_DEEP_WATER) ) { grd(p) = DNGN_DEEP_WATER; } if (grd(p) == DNGN_FLOOR && random2(3) < random2(count_neighbours(p.x,p.y,DNGN_DEEP_WATER) + count_neighbours(p.x,p.y,DNGN_SHALLOW_WATER))) { grd(p) = DNGN_SHALLOW_WATER; } } } static void _pond_creature_effect( const coord_def& center, int radius ) { radius_iterator ri(center, radius, false); bool you_affected = false; for ( ; ri; ++ri ) { const coord_def p = *ri; if ( p == you.pos() ) you_affected = true; else if ( mgrd(p) != NON_MONSTER ) mons_check_pool( &menv[mgrd(p)], KILL_YOU ); } if ( you_affected ) move_player_to_grid( you.x_pos, you.y_pos, false, true, true ); } 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); _pond_creature_effect(you.pos(), 4); } 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); _pond_creature_effect(you.pos(), 5); } else { mpr("Water floods your area!"); // Flood all visible squares radius_iterator ri( you.pos(), LOS_RADIUS, false ); for ( ; 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; grd(p) = new_feature; // In the future we might want to place all the water first, // if monsters can scramble out. if ( mgrd(p) != NON_MONSTER ) mons_check_pool( &menv[mgrd(p)], KILL_YOU ); } } move_player_to_grid( you.x_pos, you.y_pos, false, true, true ); } } 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] ) { mpr("You feel telepathic!"); you.duration[DUR_TELEPATHY] = random2(power/4); } } static bool _trowel_card(int power, deck_rarity_type rarity) { // Early exit: don't clobber important features if (is_critical_feature(grd[you.x_pos][you.y_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 int mapidx = random_map_for_tag("trowel_portal", false, false); if ( mapidx == -1 ) { mpr("A buggy portal flickers into view, then vanishes."); } else { { no_messages n; dgn_place_map(mapidx, false, 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(RANDOM_ELEMENT(statues), 0, BEH_HOSTILE, you.x_pos, you.y_pos, MHITYOU, MONS_PROGRAM_BUG) != -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(RANDOM_ELEMENT(golems), 5, BEH_FRIENDLY, you.x_pos, you.y_pos, you.pet_target, MONS_PROGRAM_BUG) != -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 coord_def pos = pick_adjacent_free_square(you.x_pos, you.y_pos); if ( pos.x >= 0 && pos.y >= 0 ) { const dungeon_feature_type statfeat[] = { DNGN_GRANITE_STATUE, DNGN_ORCISH_IDOL }; // We leave the items on the square grd[pos.x][pos.y] = RANDOM_ELEMENT(statfeat); mpr("A statue takes form beside you."); done_stuff = true; } } } else { // generate an altar if ( grd[you.x_pos][you.y_pos] == DNGN_FLOOR ) { // might get GOD_NO_GOD and no altar god_type rgod = static_cast(random2(NUM_GODS)); grd[you.x_pos][you.y_pos] = altar_for_god(rgod); if ( grd[you.x_pos][you.y_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!\""); // use 41 not 40 to tell potion_effect() that this isn't // a real potion potion_effect( coinflip() ? POT_DEGENERATION : POT_DECAY, 41 ); } } // special case for *your* god maybe? static void _godly_wrath() { divine_retribution(static_cast(random2(NUM_GODS - 1) + 1)); } 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 + decay while ( curse_an_item(true, true) && !one_chance_in(1000) ) ; } else if ( power_level == 1 ) { do // curse an average of four items { curse_an_item(false); } while ( !one_chance_in(4) ); } else { curse_an_item(false); // curse 1.5 items 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 ( int i = 0; i < MAX_MONSTERS; ++i ) { monsters* const monster = &menv[i]; if (monster->type == -1 || !mons_near(monster) || mons_friendly(monster) || mons_holiness(monster) != MH_NATURAL || mons_is_unique(monster->type) || mons_immune_magic(monster) || (is_good_god(you.religion) && mons_is_evil(monster))) { 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 ( monster->hit_dice * 35 < random2(power) ) { bool converted = false; if ( one_chance_in(5 - power_level) ) { if (you.religion == GOD_BEOGH && mons_species(monster->type) == MONS_ORC) { beogh_convert_orc(monster, false); } else { monster->attitude = ATT_FRIENDLY; converted = true; } } else { monster->add_ench(ENCH_CHARM); converted = true; } if (converted) simple_monster_message(monster, " is converted."); } } } 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; create_monster( summon_any_demon(dct), std::min(power/50,6), BEH_FRIENDLY, you.x_pos, you.y_pos, you.pet_target, MONS_PROGRAM_BUG ); } 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; int chosen_x = 0, chosen_y = 0; 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 ); monster_type cur_try; do { cur_try = random_monster_at_grid(you.x_pos + dx, you.y_pos + dy); } while ( mons_is_unique(cur_try) ); if ( mon_chosen == NUM_MONSTERS || mons_power(mon_chosen) < mons_power(cur_try) ) { mon_chosen = cur_try; chosen_x = you.x_pos; chosen_y = you.y_pos; } } if ( mon_chosen == NUM_MONSTERS ) // should never happen return; const bool friendly = (power_level > 0 || !one_chance_in(4)); create_monster( mon_chosen, 3, friendly ? BEH_FRIENDLY : BEH_HOSTILE, chosen_x, chosen_y, friendly ? you.pet_target : MHITYOU, MONS_PROGRAM_BUG ); } 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( MONS_DANCING_WEAPON, power_level + 3, friendly ? BEH_FRIENDLY : BEH_HOSTILE, you.x_pos, you.y_pos, friendly ? you.pet_target : MHITYOU, MONS_PROGRAM_BUG, false, false, false, true ); // 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); } menv[mon].flags |= MF_HARD_RESET; } } 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) && !player_see_invis() ); for ( int i = 0; i < power_level * 5 + 2; ++i ) { create_monster(result, std::min(power/50, 6), friendly ? BEH_FRIENDLY : BEH_HOSTILE, you.x_pos, you.y_pos, friendly ? you.pet_target : MHITYOU, MONS_PROGRAM_BUG); } } 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 }; create_monster(skeltypes[power_level], std::min(power/50,6), friendly ? BEH_FRIENDLY : BEH_HOSTILE, you.x_pos, you.y_pos, friendly ? you.pet_target : MHITYOU, MONS_PROGRAM_BUG); } 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; create_monster(ugly, std::min(power/50,6), friendly ? BEH_FRIENDLY : BEH_HOSTILE, you.x_pos, you.y_pos, friendly ? you.pet_target : MHITYOU, MONS_PROGRAM_BUG); } 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); #ifdef DEBUG_DIAGNOSTICS msg::streams(MSGCH_DIAGNOSTICS) << "Card power: " << power << ", rarity: " << static_cast(rarity) << std::endl; #endif if (tell_card) { msg::stream << "You have drawn " << card_name( which_card ) << '.' << std::endl; } 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_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.x_pos, you.y_pos); break; case CARD_VENOM: if ( coinflip() ) 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.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 miscast_effect( SPTYP_RANDOM, random2(power/15) + 5, random2(power), 0, "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 NUM_CARDS: 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 - 100)); } 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); props.set_default_flags(SFLAG_CONST_TYPE); props["cards"].new_vector(SV_BYTE).resize((vec_size)item.plus); props["card_flags"].new_vector(SV_BYTE).resize((vec_size)item.plus); props["drawn_cards"].new_vector(SV_BYTE); 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"]; 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 ( is_valid_item(item) && 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 ( is_valid_item(item) && 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 chuckle."); }