diff options
Diffstat (limited to 'crawl-ref/source/decks.cc')
-rw-r--r-- | crawl-ref/source/decks.cc | 1180 |
1 files changed, 1043 insertions, 137 deletions
diff --git a/crawl-ref/source/decks.cc b/crawl-ref/source/decks.cc index fcb769181d..c47e3f58fd 100644 --- a/crawl-ref/source/decks.cc +++ b/crawl-ref/source/decks.cc @@ -33,6 +33,7 @@ #include "monstuff.h" #include "mutation.h" #include "ouch.h" +#include "output.h" #include "player.h" #include "religion.h" #include "spells1.h" @@ -41,11 +42,26 @@ #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["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. #define VECFROM(x) (x), (x) + ARRAYSIZE(x) #define DEFVEC(Z) static std::vector<card_type> Z(VECFROM(a_##Z)) @@ -83,7 +99,7 @@ static card_type a_deck_of_enchantments[] = { DEFVEC(deck_of_enchantments); static card_type a_deck_of_summoning[] = { - CARD_SUMMON_ANIMAL, CARD_SUMMON_DEMON, CARD_SUMMON_WEAPON + CARD_CRUSADE, CARD_SUMMON_ANIMAL, CARD_SUMMON_DEMON, CARD_SUMMON_WEAPON }; DEFVEC(deck_of_summoning); @@ -119,11 +135,84 @@ DEFVEC(deck_of_punishment); #undef DEFVEC #undef VECFROM +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<unsigned long>(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"]; + ASSERT(cards.size() > 1); + + 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<long> pos; + for (unsigned long i = 0; i < cards.size(); i++) + pos.push_back(random2(cards.size())); + + for (unsigned long 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<int>(cards.size()); + + _flags = (unsigned char) flags[idx].get_byte(); + + return static_cast<card_type>(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 = (int) cards.size() - 1; + + cards[idx] = (char) card; + flags[idx] = (char) _flags; +} + const char* card_name(card_type card) { switch (card) { - case CARD_BLANK: return "blank card"; + case CARD_BLANK1: return "blank card"; + case CARD_BLANK2: return "blank card"; case CARD_PORTAL: return "the Portal"; case CARD_WARP: return "the Warp"; case CARD_SWAP: return "Swap"; @@ -149,6 +238,7 @@ const char* card_name(card_type card) 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"; @@ -174,10 +264,10 @@ const char* card_name(card_type card) return "a very buggy card"; } -static card_type choose_one_card(const item_def& item, bool message) +static const std::vector<card_type>* random_sub_deck(unsigned char deck_type) { - std::vector<card_type> *pdeck = NULL; - switch ( item.sub_type ) + const std::vector<card_type> *pdeck = NULL; + switch ( deck_type ) { case MISC_DECK_OF_ESCAPE: pdeck = (coinflip() ? &deck_of_transport : &deck_of_emergency); @@ -212,29 +302,111 @@ static card_type choose_one_card(const item_def& item, bool message) } ASSERT( pdeck ); - + + return pdeck; +} + +static card_type random_card(unsigned char deck_type, bool &was_oddity) +{ + const std::vector<card_type> *pdeck = random_sub_deck(deck_type); + if ( one_chance_in(100) ) { - if ( message ) - mpr("This card doesn't seem to belong here."); - pdeck = &deck_of_oddities; + pdeck = &deck_of_oddities; + was_oddity = true; } card_type chosen = (*pdeck)[random2(pdeck->size())]; - // Cut the probability of Damnation on common decks... - // too much of a killer otherwise. - if ( chosen == CARD_DAMNATION && - pdeck != &deck_of_punishment && - deck_rarity(item) == DECK_RARITY_COMMON ) - chosen = (*pdeck)[random2(pdeck->size())]; + // Paranoia + if (chosen < CARD_BLANK1 || chosen >= NUM_CARDS) + chosen = NUM_CARDS; + + return chosen; +} + +static card_type random_card(const item_def& item, bool &was_oddity) +{ + return random_card(item.sub_type, was_oddity); +} + +static void retry_blank_card(card_type &card, unsigned char deck_type, + unsigned char flags) +{ + // BLANK1 == hasn't been retried + if (card != CARD_BLANK1) + return; + + if (flags & (CFLAG_MARKED | CFLAG_SEEN)) + { + // Can't retry a card which has been seen or marked. + card = CARD_BLANK2; + return; + } + + const std::vector<card_type> *pdeck = random_sub_deck(deck_type); + + if (flags & CFLAG_ODDITY) + pdeck = &deck_of_oddities; // High Evocations gives you another shot (but not at being punished...) - if (pdeck != &deck_of_punishment && chosen == CARD_BLANK && - you.skills[SK_EVOCATIONS] > random2(30)) - chosen = (*pdeck)[random2(pdeck->size())]; + if (pdeck != &deck_of_punishment + && you.skills[SK_EVOCATIONS] > random2(30)) + { + card = (*pdeck)[random2(pdeck->size())]; + } - return chosen; + // BLANK2 == retried and failed + if (card == CARD_BLANK1) + card = CARD_BLANK2; +} + +static void retry_blank_card(card_type &card, item_def& deck, + unsigned char flags) +{ + retry_blank_card(card, deck.sub_type, flags); +} + +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(); + + retry_blank_card(card, deck, _flags); + + 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() @@ -244,6 +416,234 @@ static bool wielding_deck() return is_deck(you.inv[you.equip[EQ_WEAPON]]); } +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(); + + unsigned long num_cards = cards.size(); + unsigned long num_flags = flags.size(); + + unsigned int num_buggy = 0; + unsigned int num_marked = 0; + + for (unsigned long 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<long>(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<char>(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<char>(num_cards)) + { + strm << "More cards marked than in the deck?" << std::endl; + props["num_marked"] = static_cast<char>(num_marked); + problems = true; + } + else if (props["num_marked"].get_byte() != static_cast<char>(num_marked)) + { +#ifdef WIZARD + + strm << "Oops, counted " << static_cast<int>(num_marked) + << " marked cards, but num_marked is " + << (static_cast<int>(props["num_marked"].get_byte())); +#else + strm << "Oops, book-keeping on marked cards is wrong."; +#endif + strm << std::endl; + + props["num_marked"] = static_cast<char>(num_marked); + problems = true; + } + + if (deck.plus2 >= 0) + { + if (deck.plus != (deck.plus2 + static_cast<long>(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<long>(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?")) + { + 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 ) { @@ -273,12 +673,27 @@ 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_peek_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; + } +} + // Peek at a deck (show what the next card will be.) // Return false if the operation was failed/aborted along the way. bool deck_peek() @@ -286,31 +701,181 @@ bool deck_peek() if ( !wielding_deck() ) { mpr("You aren't wielding a deck!"); + crawl_state.zero_turns_taken(); return false; } - item_def& item(you.inv[you.equip[EQ_WEAPON]]); - if ( item.plus2 != 0 ) + item_def& deck(you.inv[you.equip[EQ_WEAPON]]); + + if (check_buggy_deck(deck)) + return false; + + if (deck.props["num_marked"].get_byte() > 0) { - mpr("You already know what the next card will be."); + mpr("You can't peek into a marked deck."); + crawl_state.zero_turns_taken(); return false; } - const card_type chosen = choose_one_card(item, false); + CrawlVector &cards = deck.props["cards"]; + int num_cards = cards.size(); - msg::stream << "You see " << card_name(chosen) << '.' << std::endl; - item.plus2 = chosen + 1; - you.wield_change = true; + card_type card1, card2, card3; + unsigned char flags1, flags2, flags3; + + card1 = get_card_and_flags(deck, 0, flags1); + retry_blank_card(card1, deck, flags1); - // You lose 1d2 cards when peeking. - if ( item.plus > 1 ) + if (num_cards == 1) { - mpr("Some cards drop out of the deck."); - if ( item.plus > 2 && coinflip() ) - item.plus -= 2; - else - item.plus -= 1; + deck_peek_ident(deck); + + 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); + retry_blank_card(card2, deck, flags2); + + if (num_cards == 2) + { + deck.props["non_brownie_draws"] = (char) 2; + deck.plus2 = -2; + + deck_peek_ident(deck); + + mprf("Only two cards in the deck: %s and %s.", + card_name(card1), card_name(card2)); + + mpr("You shuffle the deck."); + + // If both cards are the same, then you know which card you're + // going to draw both times. + if (card1 == card2) + { + flags1 |= CFLAG_MARKED; + flags2 |= CFLAG_MARKED; + you.wield_change = true; + deck.props["num_marked"] = (char) 2; + } + + // "Shuffle" the two cards (even if they're the same, since + // the flags might differ). + if (coinflip()) + { + std::swap(card1, card2); + std::swap(flags1, flags2); + } + + // After the first of two differing cards is drawn, you know + // what the second card is going to be. + if (card1 != card2) + { + flags1 |= CFLAG_MARKED; + deck.props["num_marked"]++; + } + + set_card_and_flags(deck, 0, card1, flags1 | CFLAG_SEEN); + set_card_and_flags(deck, 1, card2, flags2 | CFLAG_SEEN); + + return true; + } + + deck_peek_ident(deck); + + card3 = get_card_and_flags(deck, 2, flags3); + retry_blank_card(card3, deck, flags3); + + int already_seen = 0; + if (flags1 & CFLAG_SEEN) + already_seen++; + if (flags2 & CFLAG_SEEN) + already_seen++; + if (flags3 & CFLAG_SEEN) + already_seen++; + + if (random2(3) < already_seen) + deck.props["non_brownie_draws"]++; + + mprf("You draw three cards from the deck. They are: %s, %s and %s.", + card_name(card1), card_name(card2), card_name(card3)); + + set_card_and_flags(deck, 0, card1, flags1 | CFLAG_SEEN); + set_card_and_flags(deck, 1, card2, flags2 | CFLAG_SEEN); + set_card_and_flags(deck, 2, card3, flags3 | CFLAG_SEEN); + + mpr("You shuffle the cards back into the deck."); + shuffle_deck(deck); + + return true; +} + +// Mark a deck: look at the next four cards, mark them, and shuffle +// them back into the deck without losing any cards. 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() +{ + 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("Deck is already marked."); + crawl_state.zero_turns_taken(); + return false; + } + + 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<std::string> 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) + return true; + + 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); + you.wield_change = true; + return true; } @@ -326,6 +891,7 @@ static void redraw_stacked_cards(const std::vector<card_type>& draws, } } + // 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. @@ -335,23 +901,52 @@ bool deck_stack() if ( !wielding_deck() ) { mpr("You aren't wielding a deck!"); + crawl_state.zero_turns_taken(); return false; } - item_def& item(you.inv[you.equip[EQ_WEAPON]]); - if ( item.plus2 != 0 ) + 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_to_stack = (item.plus < 5 ? item.plus : 5); - std::vector<card_type> draws; - for ( int i = 0; i < num_to_stack; ++i ) - draws.push_back(choose_one_card(item, false)); + const int num_cards = cards_in_deck(deck); + const int num_to_stack = (num_cards < 5 ? num_cards : 5); - if ( draws.size() == 1 ) + std::vector<card_type> draws; + std::vector<unsigned char> 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 + 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(); @@ -361,6 +956,7 @@ bool deck_stack() "then another digit to swap it."); gotoxy(1,10); cprintf("Press Enter to accept."); + redraw_stacked_cards(draws, selected); // Hand-hacked implementation, instead of using Menu. Oh well. @@ -383,6 +979,7 @@ bool deck_stack() if ( selected < draws.size() ) { std::swap(draws[selected], draws[new_selected]); + std::swap(flags[selected], flags[new_selected]); selected = draws.size(); } else @@ -393,15 +990,16 @@ bool deck_stack() redraw_screen(); } - item.plus2 = draws[0] + 1; - item.special = 0; - for ( unsigned int i = draws.size() - 1; i > 0; --i ) - { - item.special <<= 8; - item.special += draws[i] + 1; - } - item.plus = num_to_stack; // no more deck after the stack + 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<char>(num_to_stack); you.wield_change = true; + + check_buggy_deck(deck); + return true; } @@ -411,35 +1009,51 @@ 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& item(you.inv[slot]); + item_def& deck(you.inv[slot]); - if ( item.plus2 != 0 ) + if (check_buggy_deck(deck)) + return false; + + CrawlHashTable &props = deck.props; + if (props["num_marked"].get_byte() > 0) { mpr("You can't triple draw from a marked deck."); + crawl_state.zero_turns_taken(); return false; } - if (item.plus == 1) + const int num_cards = cards_in_deck(deck); + + if (num_cards == 1) { // only one card to draw, so just draw it - evoke_deck(item); + evoke_deck(deck); return true; } - const int num_to_draw = (item.plus < 3 ? item.plus : 3); - std::vector<card_type> draws; + const int num_to_draw = (num_cards < 3 ? num_cards : 3); + std::vector<card_type> draws; + std::vector<unsigned char> flags; + for ( int i = 0; i < num_to_draw; ++i ) - draws.push_back(choose_one_card(item, false)); + { + 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<char>(i + 'a')) << " - " << card_name(draws[i]) << std::endl; int selected = -1; - while ( 1 ) + while ( true ) { const int keyin = tolower(get_ch()); if (keyin >= 'a' && keyin < 'a' + num_to_draw) @@ -451,12 +1065,14 @@ bool deck_triple_draw() canned_msg(MSG_HUH); } - // Note that card_effect() might cause you to unwield the deck. - card_effect(draws[selected], deck_rarity(item)); + // Note how many cards were removed from the deck. + deck.plus2 += num_to_draw; + you.wield_change = true; - // remove the cards from the deck - item.plus -= num_to_draw; - if (item.plus <= 0) + // 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] ) @@ -464,20 +1080,67 @@ bool deck_triple_draw() dec_inv_item_quantity( slot, 1 ); } - you.wield_change = true; + + // 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() { - item_def deck; - deck.plus = 10; // don't let it puff away - deck.plus2 = 0; - deck.colour = BLACK; // for rarity - deck.base_type = OBJ_MISCELLANY; - deck.sub_type = MISC_DECK_OF_PUNISHMENT; - evoke_deck(deck); + bool oddity; + card_type card = random_card(MISC_DECK_OF_PUNISHMENT, 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_BLANK1: + case CARD_BLANK2: + // Boring + 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; } // In general, if the next cards in a deck are known, they will @@ -487,68 +1150,79 @@ void draw_from_deck_of_punishment() // card could clobber the sign bit in special. void evoke_deck( item_def& deck ) { + if (check_buggy_deck(deck)) + return; + int brownie_points = 0; - mpr("You draw a card..."); bool allow_id = in_inventory(deck) && !item_ident(deck, ISFLAG_KNOW_TYPE); bool fake_draw = false; - // If the deck wasn't marked, draw a fair card. - if ( deck.plus2 == 0 ) + unsigned char flags = 0; + card_type card = draw_top_card(deck, true, flags); + int amusement = xom_check_card(deck, card, flags); + deck_rarity_type rarity = deck_rarity(deck); + CrawlHashTable &props = deck.props; + 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++; + + // 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 ) { - fake_draw = - !card_effect( choose_one_card(deck, true), deck_rarity(deck) ); + 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); - if ( deck.sub_type != MISC_DECK_OF_PUNISHMENT ) + // Nemelex likes gamblers. + if (!no_brownie) { - // Nemelex likes gamblers. brownie_points = 1; if (one_chance_in(3)) brownie_points++; } - } - else - { + // You can't ID off a marked card allow_id = false; - - // draw the marked card - fake_draw = !card_effect(static_cast<card_type>(deck.plus2 - 1), - deck_rarity(deck)); - - // If there are more marked cards, shift them up - if ( deck.special ) - { - const short next_card = (deck.special & 0xFF); - deck.special >>= 8; - deck.plus2 = next_card; - } - else - { - deck.plus2 = 0; - } - you.wield_change = true; - } - deck.plus--; - - if ( deck.plus == 0 ) - { - 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++; } - else if (allow_id && (you.skills[SK_EVOCATIONS] > 5 + random2(35))) + + 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; - you.wield_change = true; } - if (!fake_draw) + 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) @@ -794,9 +1468,7 @@ static void minefield_card(int power, deck_rarity_type rarity) // to target. This is still exploitable by finding popcorn monsters. static bool damaging_card(card_type card, int power, deck_rarity_type rarity) { - extern bool there_are_monsters_nearby(); bool rc = there_are_monsters_nearby(); - const int power_level = get_power_level(power, rarity); dist target; @@ -871,8 +1543,8 @@ static void elixir_card(int power, deck_rarity_type rarity) you.hp = you.hp_max; you.magic_points = you.max_magic_points; } - you.redraw_hit_points = 1; - you.redraw_magic_points = 1; + you.redraw_hit_points = true; + you.redraw_magic_points = true; } static void battle_lust_card(int power, deck_rarity_type rarity) @@ -988,7 +1660,24 @@ static void blade_card(int power, deck_rarity_type rarity) } else { - brand_weapon(SPWPN_VORPAL, random2(power/4)); // maybe other brands? + 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; + } + } } } @@ -1054,9 +1743,45 @@ static void focus_card(int power, deck_rarity_type rarity) (*max_statp[worst_stat])--; (*base_statp[best_stat])++; (*base_statp[worst_stat])--; + + // Did focusing kill the player? + 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 "; + cause += god_name(which_god); + } + else + { + if (which_god == GOD_XOM) + cause = "the capriciousness of Xom"; + else + { + cause = "the 'helpfullness' of "; + cause += 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) @@ -1080,7 +1805,41 @@ static void shuffle_card(int power, deck_rarity_type rarity) 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 "; + cause += god_name(which_god); + } + else + { + if (which_god == GOD_XOM) + cause = "the capriciousness of Xom"; + else + { + cause = "the 'helpfulness' of "; + cause += 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]; @@ -1094,13 +1853,15 @@ static void shuffle_card(int power, deck_rarity_type rarity) you.redraw_intelligence = true; you.redraw_dexterity = true; you.redraw_evasion = true; + + burden_change(); } static void helix_card(int power, deck_rarity_type rarity) { mutation_type bad_mutations[] = { MUT_FAST_METABOLISM, MUT_WEAK, MUT_DOPEY, MUT_CLUMSY, - MUT_TELEPORT, MUT_DEFORMED, MUT_LOST, MUT_DETERIORATION, + MUT_TELEPORT, MUT_DEFORMED, MUT_SCREAM, MUT_DETERIORATION, MUT_BLURRY_VISION, MUT_FRAIL }; @@ -1114,6 +1875,8 @@ static void helix_card(int power, deck_rarity_type rarity) } if ( numfound ) delete_mutation(which_mut); + else + mpr("You feel transcendent for a moment."); } static void dowsing_card(int power, deck_rarity_type rarity) @@ -1210,7 +1973,7 @@ static void trowel_card(int power, deck_rarity_type rarity) } if ( !done_stuff ) - mpr("Nothing appears to happen."); + canned_msg(MSG_NOTHING_HAPPENS); return; } @@ -1243,6 +2006,7 @@ 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 @@ -1264,6 +2028,39 @@ static void curse_card(int power, deck_rarity_type rarity) } } +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)) + 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) ) + { + simple_monster_message(monster, " is converted."); + if ( one_chance_in(5 - power_level) ) + monster->attitude = ATT_FRIENDLY; + else + monster->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); @@ -1331,7 +2128,7 @@ static void summon_dancing_weapon(int power, deck_rarity_type rarity) friendly ? BEH_FRIENDLY : BEH_HOSTILE, you.x_pos, you.y_pos, friendly ? you.pet_target : MHITYOU, - 250 ); + 250, 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 @@ -1358,14 +2155,15 @@ static int card_power(deck_rarity_type rarity) result += you.skills[SK_EVOCATIONS] * 9; if ( rarity == DECK_RARITY_RARE ) - result += random2(result / 2); + result += 150; if ( rarity == DECK_RARITY_LEGENDARY ) - result += random2(result); + result += 300; return result; } -bool card_effect(card_type which_card, deck_rarity_type rarity) +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); @@ -1375,12 +2173,27 @@ bool card_effect(card_type which_card, deck_rarity_type rarity) << std::endl; #endif - msg::stream << "You have drawn " << card_name( which_card ) - << '.' << std::endl; + 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_BLANK: break; + case CARD_BLANK1: break; + case CARD_BLANK2: break; 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; @@ -1403,9 +2216,10 @@ bool card_effect(card_type which_card, deck_rarity_type rarity) 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_TOMB: entomb(power/2); 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; @@ -1440,10 +2254,9 @@ bool card_effect(card_type which_card, deck_rarity_type rarity) case CARD_WILD_MAGIC: // yes, high power is bad here miscast_effect( SPTYP_RANDOM, random2(power/15) + 5, - random2(power), 0 ); + random2(power), 0, "a card of wild magic" ); break; - case CARD_FAMINE: if (you.is_undead == US_UNDEAD) mpr("You feel rather smug."); @@ -1463,12 +2276,43 @@ bool card_effect(card_type which_card, deck_rarity_type rarity) break; } + if (you.religion == GOD_XOM + && (which_card == CARD_BLANK1 || which_card == CARD_BLANK2 || !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 @@ -1476,17 +2320,79 @@ bool is_deck(const item_def &item) item.sub_type <= MISC_DECK_OF_DEFENSE); } +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) ); - switch (item.colour) + + return static_cast<deck_rarity_type>(item.special); +} + +unsigned char deck_rarity_to_color(deck_rarity_type rarity) +{ + switch (rarity) { - case BLACK: case BLUE: case GREEN: case CYAN: case RED: - default: - return DECK_RARITY_COMMON; - case MAGENTA: case BROWN: - return DECK_RARITY_RARE; - case LIGHTMAGENTA: - return DECK_RARITY_LEGENDARY; + 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(item.plus); + props["card_flags"].new_vector(SV_BYTE).resize(item.plus); + + 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); } |