diff options
-rw-r--r-- | crawl-ref/source/abl-show.cc | 19 | ||||
-rw-r--r-- | crawl-ref/source/debug.cc | 17 | ||||
-rw-r--r-- | crawl-ref/source/decks.cc | 1037 | ||||
-rw-r--r-- | crawl-ref/source/decks.h | 88 | ||||
-rw-r--r-- | crawl-ref/source/enum.h | 59 | ||||
-rw-r--r-- | crawl-ref/source/externs.h | 3 | ||||
-rw-r--r-- | crawl-ref/source/hash.cc | 1260 | ||||
-rw-r--r-- | crawl-ref/source/hash.h | 308 | ||||
-rw-r--r-- | crawl-ref/source/itemname.cc | 36 | ||||
-rw-r--r-- | crawl-ref/source/makefile.obj | 1 | ||||
-rw-r--r-- | crawl-ref/source/makeitem.cc | 17 | ||||
-rw-r--r-- | crawl-ref/source/religion.cc | 11 | ||||
-rw-r--r-- | crawl-ref/source/tags.cc | 58 | ||||
-rw-r--r-- | crawl-ref/source/tags.h | 2 |
14 files changed, 2526 insertions, 390 deletions
diff --git a/crawl-ref/source/abl-show.cc b/crawl-ref/source/abl-show.cc index 2bee608be0..be68ecf8fd 100644 --- a/crawl-ref/source/abl-show.cc +++ b/crawl-ref/source/abl-show.cc @@ -146,7 +146,7 @@ ability_type god_abilities[MAX_NUM_GODS][MAX_GOD_ABILITIES] = ABIL_TROG_BROTHERS_IN_ARMS, ABIL_NON_ABILITY }, // Nemelex { ABIL_NEMELEX_PEEK_DECK, ABIL_NEMELEX_DRAW_CARD, - ABIL_NEMELEX_TRIPLE_DRAW, ABIL_NON_ABILITY, + ABIL_NEMELEX_TRIPLE_DRAW, ABIL_NEMELEX_MARK_DECK, ABIL_NEMELEX_STACK_DECK }, // Elyvilon { ABIL_ELYVILON_LESSER_HEALING, ABIL_ELYVILON_PURIFICATION, @@ -299,6 +299,7 @@ static const ability_def Ability_List[] = { ABIL_NEMELEX_PEEK_DECK, "Deck Peek", 3, 0, 0, 1, ABFLAG_INSTANT }, { ABIL_NEMELEX_DRAW_CARD, "Draw Card", 2, 0, 0, 0, ABFLAG_NONE }, { ABIL_NEMELEX_TRIPLE_DRAW, "Triple Draw", 2, 0, 100, 2, ABFLAG_NONE }, + { ABIL_NEMELEX_MARK_DECK, "Mark Deck", 4, 0, 125, 5, ABFLAG_NONE }, { ABIL_NEMELEX_STACK_DECK, "Stack Deck", 5, 0, 150, 6, ABFLAG_NONE }, // Beogh @@ -728,9 +729,10 @@ static talent get_talent(ability_type ability, bool check_confused) failure = 80 - (you.piety / 25) - (4 * you.skills[SK_EVOCATIONS]); break; - case ABIL_NEMELEX_PEEK_DECK: + case ABIL_NEMELEX_MARK_DECK: invoc = true; - failure = 40 - (you.piety / 20) - (5 * you.skills[SK_EVOCATIONS]); + failure = 70 - (you.piety * 2 / 45) + - (9 * you.skills[SK_EVOCATIONS] / 2); break; case ABIL_NEMELEX_TRIPLE_DRAW: @@ -738,6 +740,11 @@ static talent get_talent(ability_type ability, bool check_confused) failure = 60 - (you.piety / 20) - (5 * you.skills[SK_EVOCATIONS]); break; + case ABIL_NEMELEX_PEEK_DECK: + invoc = true; + failure = 40 - (you.piety / 20) - (5 * you.skills[SK_EVOCATIONS]); + break; + case ABIL_NEMELEX_DRAW_CARD: invoc = true; perfect = true; // Tactically important to allow perfection @@ -1679,6 +1686,12 @@ static bool do_ability(const ability_def& abil) exercise(SK_EVOCATIONS, 2 + random2(2)); break; + case ABIL_NEMELEX_MARK_DECK: + if ( !deck_mark() ) + return false; + exercise(SK_EVOCATIONS, 4 + random2(4)); + break; + case ABIL_NEMELEX_STACK_DECK: if ( !deck_stack() ) return false; diff --git a/crawl-ref/source/debug.cc b/crawl-ref/source/debug.cc index c493ed5d81..a0c4ed14d4 100644 --- a/crawl-ref/source/debug.cc +++ b/crawl-ref/source/debug.cc @@ -822,7 +822,7 @@ static void deck_from_specs(const char* _specs, item_def &item) NUM_MISCELLANY }; - item.colour = BLACK; + item.special = DECK_RARITY_COMMON; item.sub_type = NUM_MISCELLANY; if (type_str != "") @@ -920,9 +920,20 @@ static void deck_from_specs(const char* _specs, item_def &item) int base = static_cast<int>(DECK_RARITY_COMMON); deck_rarity_type rarity = static_cast<deck_rarity_type>(base + rarity_val); - item.colour = deck_rarity_to_color(rarity); + item.special = rarity; - item.plus = 4 + random2(10); + int num = debug_prompt_for_int("How many cards? ", false); + + if (num <= 0) + { + canned_msg( MSG_OK ); + item.base_type = OBJ_UNASSIGNED; + return; + } + + item.plus = num; + + init_deck(item); } static void rune_or_deck_from_specs(const char* specs, item_def &item) diff --git a/crawl-ref/source/decks.cc b/crawl-ref/source/decks.cc index 6f3ebb4537..0c759cea73 100644 --- a/crawl-ref/source/decks.cc +++ b/crawl-ref/source/decks.cc @@ -47,6 +47,19 @@ #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 differetn 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)) @@ -118,11 +131,85 @@ 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."); +} + +static unsigned long cards_in_deck(const item_def &deck) +{ + ASSERT(is_deck(deck)); + + const CrawlHashTable &props = deck.props; + ASSERT(props.exists("cards")); + + return (unsigned long) props["cards"].get_table().size(); +} + +static void shuffle_deck(item_def &deck) +{ + ASSERT(is_deck(deck)); + + CrawlHashTable &props = deck.props; + ASSERT(props.exists("cards")); + + CrawlHashTable &cards = props["cards"]; + ASSERT(cards.size() > 1); + + CrawlHashTable &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. + // Also, the CrawlHashTable iterator doesn't provide the interface + // required for using it with std::shuffle(). + std::vector<long> pos; + for (unsigned long i = 0, size = cards.size(); i < size; i++) + pos.push_back(random2(size)); + + for (unsigned long i = 0, size = pos.size(); i < size; i++) + { + std::swap(cards[i], cards[pos[i]]); + std::swap(flags[i], flags[pos[i]]); + } +} + +static card_type get_card_and_flags(const item_def& deck, int idx, + unsigned char& _flags) +{ + const CrawlHashTable &props = deck.props; + const CrawlHashTable &cards = props["cards"].get_table(); + const CrawlHashTable &flags = props["card_flags"].get_table(); + + if (idx == -1) + idx = (int) cards.size() - 1; + + _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; + CrawlHashTable &cards = props["cards"]; + CrawlHashTable &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"; @@ -171,11 +258,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, - bool &was_oddity) +static std::vector<card_type>* random_sub_deck(unsigned char deck_type) { std::vector<card_type> *pdeck = NULL; - switch ( item.sub_type ) + switch ( deck_type ) { case MISC_DECK_OF_ESCAPE: pdeck = (coinflip() ? &deck_of_transport : &deck_of_emergency); @@ -210,34 +296,114 @@ 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) +{ + 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; was_oddity = true; } card_type chosen = (*pdeck)[random2(pdeck->size())]; - // 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())]; - // Paranoia - if (chosen < CARD_BLANK || chosen > NUM_CARDS) + if (chosen < CARD_BLANK1 || chosen >= NUM_CARDS) chosen = NUM_CARDS; return chosen; } -static card_type choose_one_card(const item_def& item, bool message) +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; + } + + 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 + && you.skills[SK_EVOCATIONS] > random2(30)) + { + card = (*pdeck)[random2(pdeck->size())]; + } + + // 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; + CrawlHashTable &cards = props["cards"].get_table(); + CrawlHashTable &flags = props["card_flags"].get_table(); + + 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.erase(idx); + flags.erase(idx); + + 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) { - bool garbage; + CrawlHashTable &props = deck.props; + CrawlHashTable &cards = props["cards"].get_table(); + CrawlHashTable &flags = props["card_flags"].get_table(); + + int idx = cards.size(); - return choose_one_card(item, message, garbage); + cards[idx] = (char) card; + flags[idx] = (char) _flags; } static bool wielding_deck() @@ -249,128 +415,110 @@ static bool wielding_deck() static bool check_buggy_deck(item_def& deck) { - bool stacked_wrong = false; - bool buggy_cards = false; + if (!is_deck(deck)) + { + crawl_state.zero_turns_taken(); + + mpr("This isn't a deck at all!"); + return true; + } + + CrawlHashTable &props = deck.props; - if (deck.special != 0) + if (!props.exists("cards") || cards_in_deck(deck) == 0) { - long special = deck.special; - long fixed = 0; - long holes = 0; + crawl_state.zero_turns_taken(); - for (int i = 0; i < 4 && special != 0; i++) + std::string msg = ""; + + if (!props.exists("cards")) + msg += "Seems this deck never had any cards in the first place!"; + else { - const short next_card = special & 0xFF; + msg += "Strange, this deck is already empty."; - special >>= 8; + int cards_left = 0; + if (deck.plus2 >= 0) + cards_left = deck.plus - deck.plus2; + else + cards_left = -deck.plus; - if (next_card == 0 || next_card > NUM_CARDS) + if (cards_left != 0) { - if (next_card == 0) - { -#ifdef WIZARD - if (you.wizard && !stacked_wrong && !buggy_cards) - { - mpr("Stacked deck has a hole in it (1)."); - if (yesno("Preserve state for debugging?")) - { - crawl_state.zero_turns_taken(); - return true; - } - } -#endif - mpr("Stacked deck has a hole in it (1), fixing..."); - stacked_wrong = true; - } - else if (next_card > NUM_CARDS) - { -#ifdef WIZARD - if (you.wizard && !buggy_cards && !stacked_wrong) - { - mprf("Buggy card (%d) in deck.", next_card - 1); - if (yesno("Preserve state for debugging?")) - { - crawl_state.zero_turns_taken(); - return true; - } - } -#endif - mprf("Buggy card (%d) in deck, discarding it.", - next_card - 1); - buggy_cards = true; - } - holes++; - continue; + msg += " But there should be been "; + msg += cards_left; + msg += " cards left."; } - - fixed |= next_card << (8 * (i - holes)); } - if (stacked_wrong || buggy_cards) - { - mprf("Original deck.special was 0x%x, is now 0x%x", - deck.special, fixed); + mpr(msg.c_str()); + mpr("A swarm of software bugs snatches the deck from you and " + "whisk it away."); - deck.special = fixed; - } + 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 (deck.plus2 == 0 && deck.special != 0) + bool problems = false; + + CrawlHashTable &cards = props["cards"].get_table(); + CrawlHashTable &flags = props["card_flags"].get_table(); + + problems = cards.fixup_indexed_array("the card stack"); + problems = flags.fixup_indexed_array("the flag stack"); + + unsigned long num_cards = cards.size(); + unsigned long num_flags = flags.size(); + + unsigned int num_buggy = 0; + unsigned int num_marked = 0; + unsigned int counted_cards = 0; + + for (unsigned long i = 0; i < num_cards; i++) { -#ifdef WIZARD - if (you.wizard && !stacked_wrong && !buggy_cards) + unsigned char card = (unsigned char) cards[i].get_byte(); + unsigned char _flags = (unsigned char) flags[i].get_byte(); + if (card >= NUM_CARDS) { - mpr("Stacked deck has a hole in it (2)."); - if (yesno("Preserve state for debugging?")) - { - crawl_state.zero_turns_taken(); - return true; - } + cards.erase(i); + flags.erase(i); + num_buggy++; + } + else + { + counted_cards++; + if (_flags & CFLAG_MARKED) + num_marked++; } -#endif - mpr("Stacked deck has a hole in it (2), fixing..."); - - const short next_card = (deck.special & 0xFF); - deck.special >>= 8; - deck.plus2 = next_card; - stacked_wrong = true; } - if (deck.plus2 > NUM_CARDS) + if (num_buggy > 0) { -#ifdef WIZARD - if (you.wizard && !buggy_cards && !stacked_wrong) - { - mprf("Buggy card (%d) in deck.", deck.plus2 - 1); - if (yesno("Preserve state for debugging?")) - { - crawl_state.zero_turns_taken(); - return true; - } - } -#endif - mprf("Buggy card (%d) in deck, discarding it.", deck.plus2 - 1); + mprf("%d buggy cards found in the deck, discarding them.", + num_buggy); + cards.compact_indicies(0, num_cards - 1); + flags.compact_indicies(0, num_flags - 1); - buggy_cards = true; - deck.plus2 = 0; + deck.plus2 += num_buggy; + + num_cards = cards.size(); + num_flags = cards.size(); + + problems = true; } - if (deck.plus <= 0) + if (num_cards == 0) { crawl_state.zero_turns_taken(); - mpr("Strange, that deck is already empty."); - if (deck.plus2 != 0) - mpr("And it was stacked, too. Weird."); - -#ifdef WIZARD - if (you.wizard) - if (yesno("Preserve deck for debugging?")) - return true; -#endif - + mpr("Oops, all of the cards seem to be gone."); mpr("A swarm of software bugs snatches the deck from you and " - "carries it away."); + "whisk it away."); if ( deck.link == you.equip[EQ_WEAPON] ) unwield_item(); @@ -381,22 +529,105 @@ static bool check_buggy_deck(item_def& deck) return true; } - if (stacked_wrong || buggy_cards) + if ((long) num_cards > deck.plus) { - you.wield_change = true; - print_stats(); + if (deck.plus == 0) + mpr("Deck was created with zero cards???"); + else if (deck.plus < 0) + mpr("Deck was created with *negative* cards?!"); + else + mpr("Deck has more cards than it was created with?"); + + deck.plus = num_cards; + problems = true; + } + + if (num_cards > num_flags) + { +#ifdef WIZARD + mprf("%d more cards than flags.", num_cards - num_flags); +#else + mpr("More cards than flags."); +#endif + for (unsigned int i = num_flags + 1; i <= num_cards; i++) + flags[i] = (char) 0; + + problems = true; + } + else if (num_flags > num_cards) + { +#ifdef WIZARD + mprf("%d more flags than cards.", num_flags - num_cards); +#else + mpr("More flags than cards."); +#endif + for (unsigned int i = num_flags; i > num_cards; i--) + flags.erase(i); + + problems = true; + } - if(!yesno("Deck had problems; use it anyways?")) + if (props["num_marked"].get_byte() > (char) counted_cards) + { + mpr("More cards marked than in the deck?"); + props["num_marked"] = (char) num_marked; + problems = true; + } + else if (props["num_marked"].get_byte() != (char) num_marked) + { +#ifdef WIZARD + mprf("Oops, counted %d marked cards, but num_marked is %d.", + (int) num_marked, (int) props["num_marked"].get_byte()); +#else + mpr("Oops, book-keeping on marked cards is wrong."); +#endif + props["num_marked"] = (char) num_marked; + problems = true; + } + + if (deck.plus2 >= 0) + { + if (deck.plus != (deck.plus2 + (long) num_cards)) + { +#ifdef WIZARD + mprf("Have you used %d cards, or %d? Oops.", + deck.plus2, deck.plus - num_cards); +#else + mpr("Oops, book-keeping on used cards is wrong."); +#endif + deck.plus2 = deck.plus - num_cards; + problems = true; + } + } + else + { + if (-deck.plus2 != (long) num_cards) { - crawl_state.zero_turns_taken(); - return true; +#ifdef WIZARD + mprf("There are %d cards left, not %d. Oops.", + num_cards, -deck.plus2); +#else + mpr("Oops, book-keeping on cards left is wrong."); +#endif + 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; } - // Select a deck from inventory and draw a card from it. bool choose_deck_and_draw() { @@ -421,6 +652,18 @@ bool choose_deck_and_draw() 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() @@ -431,33 +674,177 @@ bool deck_peek() crawl_state.zero_turns_taken(); return false; } - item_def& item(you.inv[you.equip[EQ_WEAPON]]); + item_def& deck(you.inv[you.equip[EQ_WEAPON]]); - if (check_buggy_deck(item)) + if (check_buggy_deck(deck)) return false; - if ( item.plus2 != 0 ) + 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); + CrawlHashTable &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 teh 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 whate 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; } @@ -473,27 +860,47 @@ bool deck_stack() crawl_state.zero_turns_taken(); return false; } - item_def& item(you.inv[you.equip[EQ_WEAPON]]); - - if (check_buggy_deck(item)) + item_def& deck(you.inv[you.equip[EQ_WEAPON]]); + if (check_buggy_deck(deck)) return false; - if ( item.plus2 != 0 ) + 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 ) - mpr("There's only one card left!"); + 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); - item.special = 0; + 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(); while ( draws.size() > 1 ) { @@ -519,14 +926,18 @@ bool deck_stack() canned_msg(MSG_HUH); } } - item.special <<= 8; - item.special += draws[selected] + 1; + push_top_card(deck, draws[selected], flags[selected]); draws.erase(draws.begin() + selected); - mpr("Next card?", MSGCH_PROMPT); + flags.erase(flags.begin() + selected); } - item.plus2 = draws[0] + 1; - item.plus = num_to_stack; // no more deck after the stack + mesclr(); + deck.plus2 = -num_to_stack; + push_top_card(deck, draws[0], flags[0]); + props["num_marked"] = (char) num_to_stack; you.wield_change = true; + + check_buggy_deck(deck); + return true; } @@ -541,36 +952,47 @@ bool deck_triple_draw() } const int slot = you.equip[EQ_WEAPON]; - item_def& item(you.inv[slot]); + item_def& deck(you.inv[slot]); - if (check_buggy_deck(item)) + if (check_buggy_deck(deck)) return false; - if ( item.plus2 != 0 ) + 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) @@ -582,12 +1004,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] ) @@ -595,20 +1019,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 elswehre + 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 @@ -622,110 +1093,72 @@ void evoke_deck( item_def& deck ) return; int brownie_points = 0; - mpr("You draw a card..."); bool allow_id = in_inventory(deck) && !item_ident(deck, ISFLAG_KNOW_TYPE); - // If the deck wasn't marked, draw a fair card. - if ( deck.plus2 == 0 ) - { - // Could a Xom worshipper ever get a stacked deck in the first - // place? - int amusement = 64; - bool was_oddity = false; - card_type card = choose_one_card(deck, true, was_oddity); - - if (!item_type_known(deck)) - amusement *= 2; - // Expecting one type of card but got another, real funny. - else if (was_oddity) - amusement = 255; - - if (player_in_a_dangerous_place()) - amusement *= 2; - - switch (card) - { - case CARD_XOM: - // Handled elswehre - amusement = 0; - break; - - case CARD_BLANK: - // Boring - amusement = 0; - break; + unsigned char flags = 0; + card_type card = draw_top_card(deck, true, flags); + int amusement = xom_check_card(deck, card, flags); + bool deck_gone = false; + deck_rarity_type rarity = deck_rarity(deck); + CrawlHashTable &props = deck.props; + bool no_brownie = (props["non_brownie_draws"].get_byte() > 0); - case CARD_DAMNATION: - // Nothing happened, boring. - if (you.level_type != LEVEL_DUNGEON) - amusement = 0; - break; + // Do these before the deck item_def object is gone. + if (flags & CFLAG_MARKED) + props["num_marked"]--; + if (no_brownie) + props["non_brownie_draws"]--; - case CARD_MINEFIELD: - case CARD_FAMINE: - case CARD_CURSE: - // Always hilarious. - amusement = 255; + deck.plus2++; - default: - break; - } + // 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. + if ( cards_in_deck(deck) == 0 ) + { + mpr("The deck of cards disappears in a puff of smoke."); + dec_inv_item_quantity( deck.link, 1 ); + deck_gone = true; + // Finishing the deck will earn a point, even if it + // was marked or stacked. + brownie_points++; + } - card_effect(card, deck_rarity(deck) ); + card_effect(card, rarity, flags, false); + 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 - 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; } 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) @@ -1580,7 +2013,8 @@ static int card_power(deck_rarity_type rarity) return result; } -void card_effect(card_type which_card, deck_rarity_type rarity) +void card_effect(card_type which_card, deck_rarity_type rarity, + unsigned char flags, bool tell_card) { const int power = card_power(rarity); #ifdef DEBUG_DIAGNOSTICS @@ -1589,8 +2023,9 @@ void 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()) { @@ -1607,7 +2042,8 @@ void card_effect(card_type which_card, deck_rarity_type rarity) 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; @@ -1689,7 +2125,8 @@ void card_effect(card_type which_card, deck_rarity_type rarity) break; } - if (you.religion == GOD_XOM && which_card == CARD_BLANK) + if (you.religion == GOD_XOM + && (which_card == CARD_BLANK1 || which_card == CARD_BLANK2)) { god_speaks(GOD_XOM, "\"How boring, lets spice things up a little.\""); @@ -1699,6 +2136,34 @@ void card_effect(card_type which_card, deck_rarity_type rarity) return; } +bool top_card_is_known(const item_def &deck) +{ + if (!is_deck(deck)) + return false; + + ASSERT(cards_in_deck(deck) > 0); + + 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; + + ASSERT(cards_in_deck(deck) > 0); + + 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 @@ -1706,19 +2171,20 @@ 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_table().get_type() != HV_BYTE); +} + deck_rarity_type deck_rarity(const item_def &item) { ASSERT( is_deck(item) ); - switch (item.colour) - { - 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; - } + + return static_cast<deck_rarity_type>(item.special); } unsigned char deck_rarity_to_color(deck_rarity_type rarity) @@ -1727,7 +2193,7 @@ unsigned char deck_rarity_to_color(deck_rarity_type rarity) { case DECK_RARITY_COMMON: { - const unsigned char colours[] = {BLACK, BLUE, GREEN, CYAN, RED}; + const unsigned char colours[] = {LIGHTBLUE, GREEN, CYAN, RED}; return RANDOM_ELEMENT(colours); } @@ -1743,3 +2209,42 @@ unsigned char deck_rarity_to_color(deck_rarity_type rarity) 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(HFLAG_CONST_TYPE); + + props["cards"].new_table(HV_BYTE); + props["card_flags"].new_table(HV_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) == (unsigned long) 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); +} diff --git a/crawl-ref/source/decks.h b/crawl-ref/source/decks.h index 47c6d646ac..df5b3942e5 100644 --- a/crawl-ref/source/decks.h +++ b/crawl-ref/source/decks.h @@ -17,6 +17,19 @@ #include "externs.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 differetn kinds of cards and 8 bits of flags. + enum deck_rarity_type { DECK_RARITY_COMMON, @@ -34,17 +47,90 @@ enum deck_type DECK_OF_WONDERS }; +enum card_flags_type +{ + CFLAG_ODDITY = (1 << 0), + CFLAG_SEEN = (1 << 1), + CFLAG_MARKED = (1 << 2) +}; + +enum card_type +{ + CARD_BLANK1 = 0, // non-retried + CARD_BLANK2, // retried and failed + CARD_PORTAL, // "the mover" + CARD_WARP, // "the jumper" + CARD_SWAP, // "swap" + CARD_VELOCITY, // "the runner" + + CARD_TOMB, // "the wall" + CARD_BANSHEE, // "the scream" + CARD_DAMNATION, // banishment + CARD_SOLITUDE, // dispersal + CARD_WARPWRIGHT, // create teleport trap + + CARD_VITRIOL, // acid damage + CARD_FLAME, // fire damage + CARD_FROST, // cold damage + CARD_VENOM, // poison damage + CARD_HAMMER, // pure damage + + CARD_ELIXIR, // healing + CARD_BATTLELUST, // melee boosts + CARD_METAMORPHOSIS, // transformation + CARD_HELM, // defense + CARD_BLADE, // weapon boosts + CARD_SHADOW, // assassin skills + + CARD_SUMMON_ANIMAL, + CARD_SUMMON_DEMON, + CARD_SUMMON_WEAPON, + CARD_SUMMON_ANY, + + CARD_POTION, + CARD_FOCUS, + CARD_SHUFFLE, + + CARD_EXPERIENCE, + CARD_WILD_MAGIC, + CARD_HELIX, // remove one *bad* mutation + + CARD_MAP, // magic mapping + CARD_DOWSING, // detect SD/traps/items/monsters + CARD_SPADE, // dig + CARD_TROWEL, // create feature/vault + CARD_MINEFIELD, // plant traps + + CARD_GENIE, // acquirement OR rotting/deterioration + CARD_BARGAIN, // shopping discount + CARD_WRATH, // Godly wrath + CARD_WRAITH, // drain XP + CARD_XOM, + CARD_FEAST, + CARD_FAMINE, + CARD_CURSE, // Curse your items + + NUM_CARDS +}; + const char* card_name(card_type card); void evoke_deck(item_def& deck); bool deck_triple_draw(); bool deck_peek(); +bool deck_mark(); bool deck_stack(); bool choose_deck_and_draw(); -void card_effect(card_type which_card, deck_rarity_type rarity); +void card_effect(card_type which_card, deck_rarity_type rarity, + unsigned char card_flags = 0, bool tell_card = true); void draw_from_deck_of_punishment(); +bool top_card_is_known(const item_def &item); +card_type top_card(const item_def &item); + bool is_deck(const item_def &item); +bool bad_deck(const item_def &item); deck_rarity_type deck_rarity(const item_def &item); unsigned char deck_rarity_to_color(deck_rarity_type rarity); +void init_deck(item_def &item); #endif diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h index dbb0e9aff2..21983398ac 100644 --- a/crawl-ref/source/enum.h +++ b/crawl-ref/source/enum.h @@ -121,6 +121,7 @@ enum ability_type ABIL_NEMELEX_PEEK_DECK, ABIL_NEMELEX_DRAW_CARD, ABIL_NEMELEX_TRIPLE_DRAW, + ABIL_NEMELEX_MARK_DECK, ABIL_NEMELEX_STACK_DECK, ABIL_BEOGH_SMITING, ABIL_BEOGH_RECALL_ORCISH_FOLLOWERS, @@ -358,64 +359,6 @@ enum canned_message_type MSG_EMPTY_HANDED }; -enum card_type -{ - CARD_BLANK = 0, - CARD_PORTAL, // "the mover" - CARD_WARP, // "the jumper" - CARD_SWAP, // "swap" - CARD_VELOCITY, // "the runner" - - CARD_TOMB, // "the wall" - CARD_BANSHEE, // "the scream" - CARD_DAMNATION, // banishment - CARD_SOLITUDE, // dispersal - CARD_WARPWRIGHT, // create teleport trap - - CARD_VITRIOL, // acid damage - CARD_FLAME, // fire damage - CARD_FROST, // cold damage - CARD_VENOM, // poison damage - CARD_HAMMER, // pure damage - - CARD_ELIXIR, // healing - CARD_BATTLELUST, // melee boosts - CARD_METAMORPHOSIS, // transformation - CARD_HELM, // defense - CARD_BLADE, // weapon boosts - CARD_SHADOW, // assassin skills - - CARD_SUMMON_ANIMAL, - CARD_SUMMON_DEMON, - CARD_SUMMON_WEAPON, - CARD_SUMMON_ANY, - - CARD_POTION, - CARD_FOCUS, - CARD_SHUFFLE, - - CARD_EXPERIENCE, - CARD_WILD_MAGIC, - CARD_HELIX, // remove one *bad* mutation - - CARD_MAP, // magic mapping - CARD_DOWSING, // detect SD/traps/items/monsters - CARD_SPADE, // dig - CARD_TROWEL, // create feature/vault - CARD_MINEFIELD, // plant traps - - CARD_GENIE, // acquirement OR rotting/deterioration - CARD_BARGAIN, // shopping discount - CARD_WRATH, // Godly wrath - CARD_WRAITH, // drain XP - CARD_XOM, - CARD_FEAST, - CARD_FAMINE, - CARD_CURSE, // Curse your items - - NUM_CARDS -}; - enum char_set_type { CSET_ASCII, // flat 7-bit ASCII diff --git a/crawl-ref/source/externs.h b/crawl-ref/source/externs.h index 5bff06df19..5a6cfca733 100644 --- a/crawl-ref/source/externs.h +++ b/crawl-ref/source/externs.h @@ -30,6 +30,7 @@ #include "defines.h" #include "enum.h" #include "FixAry.h" +#include "hash.h" #include "libutil.h" #include "mpr.h" @@ -354,6 +355,8 @@ struct item_def std::string inscription; + CrawlHashTable props; + public: item_def() : base_type(OBJ_UNASSIGNED), sub_type(0), plus(0), plus2(0), special(0L), colour(0), flags(0L), quantity(0), diff --git a/crawl-ref/source/hash.cc b/crawl-ref/source/hash.cc new file mode 100644 index 0000000000..60a5816f9c --- /dev/null +++ b/crawl-ref/source/hash.cc @@ -0,0 +1,1260 @@ +/* + * File: hash.cc + * Summary: Saveable hash-table capable of storing multiple types + * of data. + * Written by: Matthew Cline + * + * Modified for Crawl Reference by $Author$ on $Date$ + * + * Change History (most recent first): + + * <1> 10/5/07 MPC Created + */ + +#include "AppHdr.h" +#include "hash.h" + +#include "externs.h" +#include "tags.h" + +CrawlHashValue::CrawlHashValue() + : type(HV_NONE), flags(HFLAG_UNSET) +{ + val.ptr = NULL; +} + +CrawlHashValue::CrawlHashValue(const CrawlHashValue &other) +{ + ASSERT(other.type >= HV_NONE && other.type < NUM_HASH_VAL_TYPES); + + type = other.type; + flags = other.flags; + + if (flags & HFLAG_UNSET) + { + val = other.val; + return; + } + + switch (type) + { + case HV_NONE: + case HV_BOOL: + case HV_BYTE: + case HV_SHORT: + case HV_LONG: + case HV_FLOAT: + val = other.val; + break; + + case HV_STR: + { + std::string* str; + str = new std::string(*static_cast<std::string*>(other.val.ptr)); + val.ptr = static_cast<void*>(str); + break; + } + + case HV_COORD: + { + coord_def* coord; + coord = new coord_def(*static_cast<coord_def*>(other.val.ptr)); + val.ptr = static_cast<void*>(coord); + break; + } + + case HV_HASH: + { + CrawlHashTable* hash; + CrawlHashTable* tmp = static_cast<CrawlHashTable*>(other.val.ptr); + hash = new CrawlHashTable(*tmp); + val.ptr = static_cast<void*>(hash); + break; + } + + case HV_ITEM: + { + item_def* item; + item = new item_def(*static_cast<item_def*>(other.val.ptr)); + val.ptr = static_cast<void*>(item); + break; + } + + case NUM_HASH_VAL_TYPES: + ASSERT(false); + } +} + +CrawlHashValue::CrawlHashValue(const unsigned char _flags, + const hash_val_type _type) + : type(_type), flags(_flags) +{ + ASSERT(type >= HV_NONE && type < NUM_HASH_VAL_TYPES); + ASSERT(!(flags & HFLAG_UNSET)); + + flags |= HFLAG_UNSET; + val.ptr = NULL; +} + +CrawlHashValue::~CrawlHashValue() +{ + unset(true); +} + +void CrawlHashValue::unset(bool force) +{ + if (flags & HFLAG_UNSET) + return; + + if (force) + flags &= ~HFLAG_NO_ERASE; + + ASSERT(!(flags & HFLAG_NO_ERASE)); + + switch (type) + { + case HV_BOOL: + val.boolean = false; + break; + + case HV_BYTE: + val.byte = 0; + break; + + case HV_SHORT: + val._short = 0; + break; + + case HV_LONG: + val._long = 0; + break; + + case HV_FLOAT: + val._float = 0.0; + break; + + case HV_STR: + { + std::string* str = static_cast<std::string*>(val.ptr); + delete str; + val.ptr = NULL; + break; + } + + case HV_COORD: + { + coord_def* coord = static_cast<coord_def*>(val.ptr); + delete coord; + val.ptr = NULL; + break; + } + + case HV_HASH: + { + CrawlHashTable* hash = static_cast<CrawlHashTable*>(val.ptr); + delete hash; + val.ptr = NULL; + break; + } + + case HV_ITEM: + { + item_def* item = static_cast<item_def*>(val.ptr); + delete item; + val.ptr = NULL; + break; + } + + case HV_NONE: + ASSERT(false); + + case NUM_HASH_VAL_TYPES: + ASSERT(false); + } + + flags |= HFLAG_UNSET; +} + +// Only needed to do some assertion checking. +CrawlHashValue &CrawlHashValue::operator = (const CrawlHashValue &other) +{ + ASSERT(other.type >= HV_NONE && other.type < NUM_HASH_VAL_TYPES); + ASSERT(other.type != HV_NONE || type == HV_NONE); + + // NOTE: We don't bother checking HFLAG_CONST_VAL, since the + // asignment operator is used when swapping two elements. + + if (!(flags & HFLAG_UNSET)) + { + if (flags & HFLAG_CONST_TYPE) + ASSERT(type == HV_NONE || type == other.type); + } + + type = other.type; + flags = other.flags; + val = other.val; + + return (*this); +} + +/////////////////////////////////// +// Meta-data accessors and changers +unsigned char CrawlHashValue::get_flags() const +{ + return flags; +} + +unsigned char CrawlHashValue::set_flags(unsigned char _flags) +{ + flags |= _flags; + return flags; +} + +unsigned char CrawlHashValue::unset_flags(unsigned char _flags) +{ + flags &= ~_flags; + return flags; +} + +hash_val_type CrawlHashValue::get_type() const +{ + return type; +} + +////////////////////////////// +// Read/write from/to savefile +void CrawlHashValue::write(tagHeader &th) const +{ + ASSERT(!(flags & HFLAG_UNSET)); + + marshallByte(th, (char) type); + marshallByte(th, (char) flags); + + switch (type) + { + case HV_BOOL: + marshallBoolean(th, val.boolean); + break; + + case HV_BYTE: + marshallByte(th, val.byte); + break; + + case HV_SHORT: + marshallShort(th, val._short); + break; + + case HV_LONG: + marshallLong(th, val._long); + break; + + case HV_FLOAT: + marshallFloat(th, val._float); + break; + + case HV_STR: + { + std::string* str = static_cast<std::string*>(val.ptr); + marshallString(th, *str); + break; + } + + case HV_COORD: + { + coord_def* coord = static_cast<coord_def*>(val.ptr); + marshallCoord(th, *coord); + break; + } + + case HV_HASH: + { + CrawlHashTable* hash = static_cast<CrawlHashTable*>(val.ptr); + hash->write(th); + break; + } + + case HV_ITEM: + { + item_def* item = static_cast<item_def*>(val.ptr); + marshallItem(th, *item); + break; + } + + case HV_NONE: + ASSERT(false); + + case NUM_HASH_VAL_TYPES: + ASSERT(false); + } +} + +void CrawlHashValue::read(tagHeader &th) +{ + type = static_cast<hash_val_type>(unmarshallByte(th)); + flags = (unsigned char) unmarshallByte(th); + + ASSERT(!(flags & HFLAG_UNSET)); + + switch (type) + { + case HV_BOOL: + val.boolean = unmarshallBoolean(th); + break; + + case HV_BYTE: + val.byte = unmarshallByte(th); + break; + + case HV_SHORT: + val._short = unmarshallShort(th); + break; + + case HV_LONG: + val._long = unmarshallLong(th); + break; + + case HV_FLOAT: + val._float = unmarshallFloat(th); + break; + + case HV_STR: + { + std::string str = unmarshallString(th); + val.ptr = (void*) new std::string(str); + break; + } + + case HV_COORD: + { + coord_def coord; + unmarshallCoord(th, coord); + val.ptr = (void*) new coord_def(coord); + + break; + } + + case HV_HASH: + { + CrawlHashTable* hash = new CrawlHashTable(); + hash->read(th); + val.ptr = (void*) hash; + + break; + } + + case HV_ITEM: + { + item_def item; + unmarshallItem(th, item); + val.ptr = (void*) new item_def(item); + + break; + } + + case HV_NONE: + ASSERT(false); + + case NUM_HASH_VAL_TYPES: + ASSERT(false); + } +} + +//////////////////////////////////////////////////////////////// +// Setup a new table with the given flags and/or type; assert if +// a table already exists. +CrawlHashTable &CrawlHashValue::new_table(unsigned char _flags) +{ + return new_table(HV_NONE, flags); +} + +CrawlHashTable &CrawlHashValue::new_table(hash_val_type _type, + unsigned char _flags) +{ + CrawlHashTable* old_table = static_cast<CrawlHashTable*>(val.ptr); + + ASSERT(flags & HFLAG_UNSET); + ASSERT(type == HV_NONE + || (type == HV_HASH + && old_table->size() == 0 + && old_table->get_type() == HV_NONE + && old_table->get_default_flags() == 0)); + + CrawlHashTable &table = get_table(); + + table.default_flags = _flags; + table.type = _type; + + type = HV_HASH; + flags &= ~HFLAG_UNSET; + + return table; +} + +/////////////////////////////////////////// +// Dynamic type-checking accessor functions +#define GET_VAL(x, _type, field, value) \ + ASSERT((flags & HFLAG_UNSET) || !(flags & HFLAG_CONST_VAL)); \ + if (type != (x)) \ + { \ + if (type == HV_NONE) \ + { \ + type = (x); \ + field = (value); \ + } \ + else \ + { \ + ASSERT(!(flags & HFLAG_CONST_TYPE)); \ + switch(type) \ + { \ + case HV_BOOL: \ + field = (_type) val.boolean; \ + break; \ + case HV_BYTE: \ + field = (_type) val.byte; \ + break; \ + case HV_SHORT: \ + field = (_type) val._short; \ + break; \ + case HV_LONG: \ + field = (_type) val._long; \ + break; \ + case HV_FLOAT: \ + field = (_type) val._float; \ + break; \ + default: \ + ASSERT(false); \ + } \ + type = (x); \ + } \ + } \ + flags &= ~HFLAG_UNSET; \ + return field; + +#define GET_VAL_PTR(x, _type, value) \ + ASSERT((flags & HFLAG_UNSET) || !(flags & HFLAG_CONST_VAL)); \ + if (type != (x)) \ + { \ + if (type == HV_NONE) \ + { \ + type = (x); \ + val.ptr = (value); \ + } \ + else \ + { \ + unset(); \ + val.ptr = (value); \ + type = (x); \ + } \ + } \ + flags &= ~HFLAG_UNSET; \ + return *((_type) val.ptr); + +bool &CrawlHashValue::get_bool() +{ + GET_VAL(HV_BOOL, bool, val.boolean, false); +} + +char &CrawlHashValue::get_byte() +{ + GET_VAL(HV_BYTE, char, val.byte, 0); +} + +short &CrawlHashValue::get_short() +{ + GET_VAL(HV_SHORT, short, val._short, 0); +} + +long &CrawlHashValue::get_long() +{ + GET_VAL(HV_LONG, long, val._long, 0); +} + +float &CrawlHashValue::get_float() +{ + GET_VAL(HV_FLOAT, float, val._float, 0.0); +} + +std::string &CrawlHashValue::get_string() +{ + GET_VAL_PTR(HV_STR, std::string*, new std::string("")); +} + +coord_def &CrawlHashValue::get_coord() +{ + GET_VAL_PTR(HV_COORD, coord_def*, new coord_def()); +} + +CrawlHashTable &CrawlHashValue::get_table() +{ + GET_VAL_PTR(HV_HASH, CrawlHashTable*, new CrawlHashTable()); +} + +item_def &CrawlHashValue::get_item() +{ + GET_VAL_PTR(HV_ITEM, item_def*, new item_def()); +} + +/////////////////////////// +// Const accessor functions +#define GET_CONST_SETUP(x) \ + ASSERT(!(flags & HFLAG_UNSET)); \ + ASSERT(type == (x)); + +bool CrawlHashValue::get_bool() const +{ + GET_CONST_SETUP(HV_BOOL); + return val.boolean; +} + +char CrawlHashValue::get_byte() const +{ + GET_CONST_SETUP(HV_BYTE); + return val.byte; +} + +short CrawlHashValue::get_short() const +{ + GET_CONST_SETUP(HV_SHORT); + return val._short; +} + +long CrawlHashValue::get_long() const +{ + GET_CONST_SETUP(HV_LONG); + return val._long; +} + +float CrawlHashValue::get_float() const +{ + GET_CONST_SETUP(HV_FLOAT); + return val._float; +} + +std::string CrawlHashValue::get_string() const +{ + GET_CONST_SETUP(HV_STR); + return *((std::string*)val.ptr); +} + +coord_def CrawlHashValue::get_coord() const +{ + GET_CONST_SETUP(HV_COORD); + return *((coord_def*)val.ptr); +} + +const CrawlHashTable& CrawlHashValue::get_table() const +{ + GET_CONST_SETUP(HV_HASH); + return *((CrawlHashTable*)val.ptr); +} + +const item_def& CrawlHashValue::get_item() const +{ + GET_CONST_SETUP(HV_ITEM); + return *((item_def*)val.ptr); +} + +///////////////////// +// Typecast operators +&CrawlHashValue::operator bool() +{ + return get_bool(); +} + +&CrawlHashValue::operator char() +{ + return get_byte(); +} + +&CrawlHashValue::operator short() +{ + return get_short(); +} + +&CrawlHashValue::operator float() +{ + return get_float(); +} + +&CrawlHashValue::operator long() +{ + return get_long(); +} + +&CrawlHashValue::operator std::string() +{ + return get_string(); +} + +&CrawlHashValue::operator coord_def() +{ + return get_coord(); +} + +&CrawlHashValue::operator CrawlHashTable() +{ + return get_table(); +} + +&CrawlHashValue::operator item_def() +{ + return get_item(); +} + +/////////////////////////// +// Const typecast operators +CrawlHashValue::operator bool() const +{ + return get_bool(); +} + +#define CONST_INT_CAST() \ + switch(type) \ + { \ + case HV_BYTE: \ + return get_byte(); \ + case HV_SHORT: \ + return get_short(); \ + case HV_LONG: \ + return get_long(); \ + default: \ + ASSERT(false); \ + return 0; \ + } + +CrawlHashValue::operator char() const +{ + CONST_INT_CAST(); +} + +CrawlHashValue::operator short() const +{ + CONST_INT_CAST(); +} + +CrawlHashValue::operator long() const +{ + CONST_INT_CAST(); +} + +CrawlHashValue::operator float() const +{ + return get_float(); +} + +CrawlHashValue::operator std::string() const +{ + return get_string(); +} + +CrawlHashValue::operator coord_def() const +{ + return get_coord(); +} + +/////////////////////// +// Assignment operators +bool &CrawlHashValue::operator = (const bool &_val) +{ + return (get_bool() = _val); +} + +char &CrawlHashValue::operator = (const char &_val) +{ + return (get_byte() = _val); +} + +short &CrawlHashValue::operator = (const short &_val) +{ + return (get_short() = _val); +} + +long &CrawlHashValue::operator = (const long &_val) +{ + return (get_long() = _val); +} + +float &CrawlHashValue::operator = (const float &_val) +{ + return (get_float() = _val); +} + +std::string &CrawlHashValue::operator = (const std::string &_val) +{ + return (get_string() = _val); +} + +const char* CrawlHashValue::operator = (const char* _val) +{ + get_string() = _val; + return get_string().c_str(); +} + +coord_def &CrawlHashValue::operator = (const coord_def &_val) +{ + return (get_coord() = _val); +} + +CrawlHashTable &CrawlHashValue::operator = (const CrawlHashTable &_val) +{ + return (get_table() = _val); +} + +item_def &CrawlHashValue::operator = (const item_def &_val) +{ + return (get_item() = _val); +} + +/////////////////////////////////////////////////// +// Non-assignment operators which affect the lvalue +#define INT_OPERATOR_UNARY(op) \ + switch(type) \ + { \ + case HV_BYTE: \ + { \ + char &temp = get_byte(); \ + temp op; \ + return temp; \ + } \ + \ + case HV_SHORT: \ + { \ + short &temp = get_short(); \ + temp op; \ + return temp; \ + } \ + case HV_LONG: \ + { \ + long &temp = get_long(); \ + temp op; \ + return temp; \ + } \ + \ + default: \ + ASSERT(false); \ + return 0; \ + } + +// Prefix +long CrawlHashValue::operator ++ () +{ + INT_OPERATOR_UNARY(++); +} + +long CrawlHashValue::operator -- () +{ + INT_OPERATOR_UNARY(--); +} + +// Postfix +long CrawlHashValue::operator ++ (int) +{ + INT_OPERATOR_UNARY(++); +} + +long CrawlHashValue::operator -- (int) +{ + INT_OPERATOR_UNARY(--); +} + +std::string &CrawlHashValue::operator += (const std::string &_val) +{ + return (get_string() += _val); +} + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// + +CrawlHashTable::CrawlHashTable() + : type(HV_NONE), default_flags(0) +{ +} + +CrawlHashTable::CrawlHashTable(unsigned char flags) + : type(HV_NONE), default_flags(flags) +{ + ASSERT(!(default_flags & HFLAG_UNSET)); +} + +CrawlHashTable::CrawlHashTable(hash_val_type _type, unsigned char flags) + : type(_type), default_flags(flags) +{ + ASSERT(type >= HV_NONE && type < NUM_HASH_VAL_TYPES); + ASSERT(!(default_flags & HFLAG_UNSET)); +} + +CrawlHashTable::~CrawlHashTable() +{ + assert_validity(); +} + +////////////////////////////// +// Read/write from/to savefile +void CrawlHashTable::write(tagHeader &th) const +{ + assert_validity(); + if (empty()) + { + marshallByte(th, 0); + return; + } + + marshallByte(th, size()); + marshallByte(th, static_cast<char>(type)); + marshallByte(th, (char) default_flags); + + CrawlHashTable::hash_map_type::const_iterator i = hash_map.begin(); + + for (; i != hash_map.end(); i++) + { + marshallString(th, i->first); + i->second.write(th); + } + + assert_validity(); +} + +void CrawlHashTable::read(tagHeader &th) +{ + assert_validity(); + + ASSERT(empty()); + ASSERT(type == HV_NONE); + ASSERT(default_flags == 0); + + unsigned char _size = (unsigned char) unmarshallByte(th); + + if (_size == 0) + return; + + type = static_cast<hash_val_type>(unmarshallByte(th)); + default_flags = (unsigned char) unmarshallByte(th); + + for (unsigned char i = 0; i < _size; i++) + { + std::string key = unmarshallString(th); + CrawlHashValue &val = (*this)[key]; + + val.read(th); + } + + assert_validity(); +} + + +////////////////// +// Misc functions + +unsigned char CrawlHashTable::get_default_flags() const +{ + assert_validity(); + return default_flags; +} + +unsigned char CrawlHashTable::set_default_flags(unsigned char flags) +{ + assert_validity(); + ASSERT(!(flags & HFLAG_UNSET)); + default_flags |= flags; + + return default_flags; +} + +unsigned char CrawlHashTable::unset_default_flags(unsigned char flags) +{ + assert_validity(); + ASSERT(!(flags & HFLAG_UNSET)); + default_flags &= ~flags; + + return default_flags; +} + +hash_val_type CrawlHashTable::get_type() const +{ + assert_validity(); + return type; +} + +bool CrawlHashTable::exists(const std::string key) const +{ + assert_validity(); + hash_map_type::const_iterator i = hash_map.find(key); + + return (i != hash_map.end()); +} + +bool CrawlHashTable::exists(const long index) const +{ + char buf[12]; + sprintf(buf, "%ld", index); + return exists(buf); +} + +void CrawlHashTable::assert_validity() const +{ +#if DEBUG + ASSERT(!(default_flags & HFLAG_UNSET)); + + hash_map_type::const_iterator i = hash_map.begin(); + + unsigned long actual_size = 0; + + for (; i != hash_map.end(); i++) + { + actual_size++; + + const std::string &key = i->first; + const CrawlHashValue &val = i->second; + + ASSERT(key != ""); + std::string trimmed = trimmed_string(key); + ASSERT(key == trimmed); + + ASSERT(val.type != HV_NONE); + ASSERT(!(val.flags & HFLAG_UNSET)); + + switch(val.type) + { + case HV_STR: + case HV_COORD: + case HV_ITEM: + ASSERT(val.val.ptr != NULL); + break; + + case HV_HASH: + { + ASSERT(val.val.ptr != NULL); + + CrawlHashTable* nested; + nested = static_cast<CrawlHashTable*>(val.val.ptr); + + nested->assert_validity(); + break; + } + + default: + break; + } + } + + ASSERT(size() == actual_size); + + ASSERT(size() <= 255); +#endif +} + +int CrawlHashTable::compact_indicies(long min_index, long max_index, + bool compact_down) +{ + ASSERT(min_index <= max_index); + + if (min_index == max_index) + return 0; + + long min_exists, max_exists; + + for (min_exists = min_index; min_exists <= max_index; min_exists++) + if (exists(min_exists)) + break; + + if (min_exists > max_index) + return 0; + + for (max_exists = max_index; max_exists >= min_exists; max_exists--) + if (exists(max_exists)) + break; + + if (max_exists <= min_exists) + return 0; + + bool hole_found = false; + for (long i = min_exists; i < max_exists; i++) + if (!exists(i)) + { + hole_found = true; + break; + } + + if (!hole_found) + return 0; + + long start, stop, dir; + if (compact_down) + { + start = min_exists; + stop = max_exists; + dir = +1; + } + else + { + start = max_exists; + stop = min_exists; + dir = -1; + } + stop += dir; + + long move = 0; + for (long i = start; i != stop; i += dir) + { + if (!exists(i)) + { + move++; + continue; + } + + if (move == 0) + continue; + + char buf1[12], buf2[12]; + sprintf(buf1, "%ld", i - (move * dir)); + sprintf(buf2, "%ld", i); + + CrawlHashValue &val = hash_map[buf2]; + hash_map[buf1] = val; + + // Ensure a new()'d object isn't freed, since the other + // CrawlHashValue now owns it. + val.type = HV_BOOL; + erase(buf2); + } + + return move; +} + +bool CrawlHashTable::fixup_indexed_array(std::string name) +{ + bool problems = false; + long max_index = 0; + std::vector<std::string> bad_keys; + for (hash_map_type::iterator i = begin(); i != end(); i++) + { + int index = strtol(i->first.c_str(), NULL, 10); + + if (index < 0) + { + if (name != "") + mprf("Negative index %ld found in %s.", + index, name.c_str()); + problems = true; + bad_keys.push_back(i->first); + } + else if (index == 0 && i->first != "0") + { + if (name != "") + mprf("Non-numerical index '%s' found in %s.", + i->first.c_str(), name.c_str()); + problems = true; + bad_keys.push_back(i->first); + } + else if (index > max_index) + max_index = index; + } + for (unsigned long i = 0, _size = bad_keys.size(); i < _size; i++) + erase(bad_keys[i]); + int holes = compact_indicies(0, max_index); + if (holes > 0 && name != "") + mprf("%d holes found in %s.", holes, name.c_str()); + + return problems; +} + +//////////// +// Accessors + +CrawlHashValue& CrawlHashTable::get_value(const std::string &key) +{ + assert_validity(); + hash_map_type::iterator i = hash_map.find(key); + + if (i == hash_map.end()) + { + hash_map[key] = CrawlHashValue(default_flags); + CrawlHashValue &val = hash_map[key]; + + if (type != HV_NONE) + { + val.type = type; + val.flags |= HFLAG_CONST_TYPE; + } + + return (val); + } + + return (i->second); +} + +CrawlHashValue& CrawlHashTable::get_value(const long &index) +{ + char buf[12]; + sprintf(buf, "%ld", index); + return get_value(buf); +} + +const CrawlHashValue& CrawlHashTable::get_value(const std::string &key) const +{ + assert_validity(); + hash_map_type::const_iterator i = hash_map.find(key); + + ASSERT(i != hash_map.end()); + ASSERT(i->second.type != HV_NONE); + ASSERT(!(i->second.flags & HFLAG_UNSET)); + + return (i->second); +} + +const CrawlHashValue& CrawlHashTable::get_value(const long &index) const +{ + char buf[12]; + sprintf(buf, "%ld", index); + return get_value(buf); +} + +CrawlHashValue& CrawlHashTable::operator[] (const std::string &key) +{ + return get_value(key); +} + +CrawlHashValue& CrawlHashTable::operator[] (const long &index) +{ + return get_value(index); +} + +const CrawlHashValue& CrawlHashTable::operator[] (const std::string &key) const +{ + return get_value(key); +} + +const CrawlHashValue& CrawlHashTable::operator[] (const long &index) const +{ + return get_value(index); +} + +/////////////////////////// +// std::map style interface +size_t CrawlHashTable::size() const +{ + return hash_map.size(); +} + +bool CrawlHashTable::empty() const +{ + return hash_map.empty(); +} + +void CrawlHashTable::erase(const std::string key) +{ + assert_validity(); + hash_map_type::iterator i = hash_map.find(key); + + if (i != hash_map.end()) + { + CrawlHashValue &val = i->second; + + ASSERT(!(val.flags & HFLAG_NO_ERASE)); + + hash_map.erase(i); + } +} + +void CrawlHashTable::erase(const long index) +{ + char buf[12]; + sprintf(buf, "%ld", index); + erase(buf); +} + +void CrawlHashTable::clear() +{ + assert_validity(); + ASSERT(!(default_flags & HFLAG_NO_ERASE)); + + hash_map_type::iterator i = hash_map.begin(); + for (; i != hash_map.end(); i++) + ASSERT(!(i->second.flags & HFLAG_NO_ERASE)); + + hash_map.clear(); +} + +CrawlHashTable::hash_map_type::iterator CrawlHashTable::begin() +{ + assert_validity(); + return hash_map.begin(); +} + +CrawlHashTable::hash_map_type::iterator CrawlHashTable::end() +{ + assert_validity(); + return hash_map.end(); +} + +CrawlHashTable::hash_map_type::const_iterator CrawlHashTable::begin() const +{ + assert_validity(); + return hash_map.begin(); +} + +CrawlHashTable::hash_map_type::const_iterator CrawlHashTable::end() const +{ + assert_validity(); + return hash_map.end(); +} + +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// + +template <typename T, hash_val_type TYPE> +CrawlTableWrapper<T, TYPE>::CrawlTableWrapper() +{ + table = NULL; +} + +template <typename T, hash_val_type TYPE> +CrawlTableWrapper<T, TYPE>::CrawlTableWrapper(CrawlHashTable& _table) +{ + wrap(_table); +} + +template <typename T, hash_val_type TYPE> +CrawlTableWrapper<T, TYPE>::CrawlTableWrapper(CrawlHashTable* _table) +{ + wrap(_table); +} + +template <typename T, hash_val_type TYPE> +void CrawlTableWrapper<T, TYPE>::wrap(CrawlHashTable& _table) +{ + wrap(&_table); +} + +template <typename T, hash_val_type TYPE> +void CrawlTableWrapper<T, TYPE>::wrap(CrawlHashTable* _table) +{ + ASSERT(_table != NULL); + ASSERT(_table->get_type() == TYPE); + + table = _table; +} + +template <typename T, hash_val_type TYPE> +T& CrawlTableWrapper<T, TYPE>::operator[] (const std::string &key) +{ + return (T&) (*table)[key]; +} + +template <typename T, hash_val_type TYPE> +T& CrawlTableWrapper<T, TYPE>::operator[] (const long &index) +{ + return (T&) (*table)[index]; +} + +template <typename T, hash_val_type TYPE> +const T CrawlTableWrapper<T, TYPE>::operator[] (const std::string &key) const +{ + return (T) (*table)[key]; +} + +template <typename T, hash_val_type TYPE> +const T CrawlTableWrapper<T, TYPE>::operator[] (const long &index) const +{ + return (T) (*table)[index]; +} diff --git a/crawl-ref/source/hash.h b/crawl-ref/source/hash.h new file mode 100644 index 0000000000..7c658c1667 --- /dev/null +++ b/crawl-ref/source/hash.h @@ -0,0 +1,308 @@ +/* + * File: hash.h + * Summary: Saveable hash-table capable of storing multiple types + * of data. + * Written by: Matthew Cline + * + * Modified for Crawl Reference by $Author$ on $Date$ + * + * Change History (most recent first): + * + * <1> 10/5/07 MPC Created + */ + +#ifndef HASH_H +#define HASH_H + +#include <string> +#include <map> + +struct tagHeader; +class CrawlHashTable; +class item_def; +class coord_def; + +// NOTE: Changing the ordering of these enums will break savefile +// compatibility. +enum hash_val_type +{ + HV_NONE = 0, + HV_BOOL, + HV_BYTE, + HV_SHORT, + HV_LONG, + HV_FLOAT, + HV_STR, + HV_COORD, + HV_HASH, + HV_ITEM, + NUM_HASH_VAL_TYPES +}; + +enum hash_flag_type +{ + HFLAG_UNSET = (1 << 0), + HFLAG_CONST_VAL = (1 << 1), + HFLAG_CONST_TYPE = (1 << 2), + HFLAG_NO_ERASE = (1 << 3) +}; + + +// Can't just cast everything into a void pointer, since a float might +// not fit into a pointer on all systems. +typedef union HashUnion HashUnion; +union HashUnion +{ + bool boolean; + char byte; + short _short; + long _long; + float _float; + void* ptr; +}; + + +class CrawlHashValue +{ +public: + CrawlHashValue(); + CrawlHashValue(const CrawlHashValue &other); + + ~CrawlHashValue(); + + // Only needed for doing some assertion checking. + CrawlHashValue &operator = (const CrawlHashValue &other); + +protected: + hash_val_type type; + unsigned char flags; + HashUnion val; + +public: + unsigned char get_flags() const; + unsigned char set_flags(unsigned char flags); + unsigned char unset_flags(unsigned char flags); + hash_val_type get_type() const; + + CrawlHashTable &new_table(unsigned char flags); + CrawlHashTable &new_table(hash_val_type type, unsigned char flags = 0); + + bool &get_bool(); + char &get_byte(); + short &get_short(); + long &get_long(); + float &get_float(); + std::string &get_string(); + coord_def &get_coord(); + CrawlHashTable &get_table(); + item_def &get_item(); + + bool get_bool() const; + char get_byte() const; + short get_short() const; + long get_long() const; + float get_float() const; + std::string get_string() const; + coord_def get_coord() const; + + const CrawlHashTable& get_table() const; + const item_def& get_item() const; + + void set_bool(const bool val); + void set_byte(const char val); + void set_short(const short val); + void set_long(const long val); + void set_float(const float val); + void set_string(const std::string &val); + void set_coord(const coord_def &val); + void set_table(const CrawlHashTable &val); + void set_item(const item_def &val); + +public: + // NOTE: All operators will assert if the hash value is of the + // wrong type for the operation. If the value has no type yet, + // the operation will set it to the appropriate type. If the + // value has no type yet and the operation modifies the existing + // value rather than replacing it (i.e., ++) the value will be set + // to a default before the operation is done. + + // If the hash value is a hash table, the table's values can be + // accessed with the [] operator. + CrawlHashValue &operator [] (const std::string &key); + CrawlHashValue &operator [] (const long &key); + + const CrawlHashValue &operator [] (const std::string &key) const; + const CrawlHashValue &operator [] (const long &key) const; + + // Typecast operators + &operator bool(); + &operator char(); + &operator short(); + &operator long(); + &operator float(); + &operator std::string(); + &operator coord_def(); + &operator CrawlHashTable(); + &operator item_def(); + + operator bool() const; + operator char() const; + operator short() const; + operator long() const; + operator float() const; + operator std::string() const; + operator coord_def() const; + + // Assignment operators + bool &operator = (const bool &val); + char &operator = (const char &val); + short &operator = (const short &val); + long &operator = (const long &val); + float &operator = (const float &val); + std::string &operator = (const std::string &val); + const char* operator = (const char* val); + coord_def &operator = (const coord_def &val); + CrawlHashTable &operator = (const CrawlHashTable &val); + item_def &operator = (const item_def &val); + + // Misc operators + std::string &operator += (const std::string &val); + + // Prefix + long operator ++ (); + long operator -- (); + + // Postfix + long operator ++ (int); + long operator -- (int); + +protected: + CrawlHashValue(const unsigned char flags, + const hash_val_type type = HV_NONE); + + void write(tagHeader &th) const; + void read(tagHeader &th); + + void unset(bool force = false); + + friend class CrawlHashTable; +}; + + +// A hash table can have a maximum of 255 key/value pairs. If you +// want more than that you can use nested hash tables. +// +// By default a hash table's value data types are heterogeneous. To +// make it homogeneous (which causes dynamic type checking) you have +// to give a type to the hash table constructor; once it's been +// created it's type (or lack of type) is immutable. +// +// An empty hash table will take up only 1 byte in the savefile. A +// non-empty hash table will have an overhead of 3 bytes for the hash +// table overall and 2 bytes per key/value pair, over and above the +// number of bytes needed to store the keys and values themselves. +class CrawlHashTable +{ +public: + CrawlHashTable(); + CrawlHashTable(unsigned char flags); + CrawlHashTable(hash_val_type type, unsigned char flags = 0); + + ~CrawlHashTable(); + + typedef std::map<std::string, CrawlHashValue> hash_map_type; + +protected: + hash_val_type type; + unsigned char default_flags; + hash_map_type hash_map; + + friend class CrawlHashValue; + +public: + void write(tagHeader &th) const; + void read(tagHeader &th); + + unsigned char get_default_flags() const; + unsigned char set_default_flags(unsigned char flags); + unsigned char unset_default_flags(unsigned char flags); + hash_val_type get_type() const; + bool exists(const std::string key) const; + bool exists(const long key) const; + void assert_validity() const; + int compact_indicies(long min_index, long max_index, + bool compact_down = true); + bool fixup_indexed_array(std::string name = ""); + + // NOTE: If get_value() or [] is given a key which doesn't exist + // in the table, an unset/empty CrawlHashValue will be created + // with that key and returned. If it is not then given a value + // then the next call to assert_validity() will fail. If the + // hash table has a type (rather than being heterogeneous) + // then trying to assign a different type to the CrawlHashValue + // will assert. + CrawlHashValue& get_value(const std::string &key); + CrawlHashValue& get_value(const long &index); + CrawlHashValue& operator[] (const std::string &key); + CrawlHashValue& operator[] (const long &index); + + // NOTE: If the const versions of get_value() or [] are given a + // key which doesn't exist, they will assert. + const CrawlHashValue& get_value(const std::string &key) const; + const CrawlHashValue& get_value(const long &index) const; + const CrawlHashValue& operator[] (const std::string &key) const; + const CrawlHashValue& operator[] (const long &index) const; + + // std::map style interface + size_t size() const; + bool empty() const; + + void erase(const std::string key); + void erase(const long index); + void clear(); + + hash_map_type::iterator begin(); + hash_map_type::iterator end(); + + hash_map_type::const_iterator begin() const; + hash_map_type::const_iterator end() const; +}; + + +// A wrapper for non-heterogeneous hash tables, so that the values can +// be accessed without using get_foo(). T needs to have both normal +// and const type-cast operators defined by CrawlHashValue for this +// template to work. +template <typename T, hash_val_type TYPE> +class CrawlTableWrapper +{ +public: + CrawlTableWrapper(); + CrawlTableWrapper(CrawlHashTable& table); + CrawlTableWrapper(CrawlHashTable* table); + +protected: + CrawlHashTable* table; + +public: + void wrap(CrawlHashTable& table); + void wrap(CrawlHashTable* table); + + CrawlHashTable* get_table(); + T& operator[] (const std::string &key); + T& operator[] (const long &index); + + const CrawlHashTable* get_table() const; + const T operator[] (const std::string &key) const; + const T operator[] (const long &index) const; +}; + +typedef CrawlTableWrapper<bool, HV_BOOL> CrawlBoolTable; +typedef CrawlTableWrapper<char, HV_BYTE> CrawlByteTable; +typedef CrawlTableWrapper<short, HV_SHORT> CrawlShortTable; +typedef CrawlTableWrapper<long, HV_LONG> CrawlLongTable; +typedef CrawlTableWrapper<float, HV_FLOAT> CrawlFloatTable; +typedef CrawlTableWrapper<std::string, HV_STR> CrawlStringTable; +typedef CrawlTableWrapper<coord_def, HV_COORD> CrawlCoordTable; + +#endif diff --git a/crawl-ref/source/itemname.cc b/crawl-ref/source/itemname.cc index accf5cf071..20cea75f1c 100644 --- a/crawl-ref/source/itemname.cc +++ b/crawl-ref/source/itemname.cc @@ -1402,15 +1402,41 @@ std::string item_def::name_aux( description_level_type desc, buff << "deck of cards"; break; } + else if (bad_deck(*this)) + { + buff << "BUGGY deck of cards"; + break; + } buff << deck_rarity_name(deck_rarity(*this)) << ' '; } buff << misc_type_name(item_typ, know_type); - if ( is_deck(*this) && item_plus2 != 0 ) + if ( is_deck(*this) + && (top_card_is_known(*this) || this->plus2 != 0)) { - // an inscribed deck! - buff << " {" - << card_name(static_cast<card_type>(item_plus2 - 1)) - << "}"; + buff << " {"; + // A marked deck! + if (top_card_is_known(*this)) + buff << card_name(top_card(*this)); + + // How many cards have been drawn, or how many are + // left. + if (this->plus2 != 0) + { + if(top_card_is_known(*this)) + buff << ", "; + + buff << abs(this->plus2) << " card"; + + if (abs(this->plus2) > 1) + buff << "s"; + + if (this->plus2 > 0) + buff << " drawn"; + else + buff << " left"; + } + + buff << "}"; } } break; diff --git a/crawl-ref/source/makefile.obj b/crawl-ref/source/makefile.obj index 8819ab5b27..f871fa43f6 100644 --- a/crawl-ref/source/makefile.obj +++ b/crawl-ref/source/makefile.obj @@ -24,6 +24,7 @@ files.o \ food.o \ format.o \ ghost.o \ +hash.o \ hiscores.o \ initfile.o \ insult.o \ diff --git a/crawl-ref/source/makeitem.cc b/crawl-ref/source/makeitem.cc index d020e4a8cf..c97133ec9e 100644 --- a/crawl-ref/source/makeitem.cc +++ b/crawl-ref/source/makeitem.cc @@ -733,14 +733,7 @@ void item_colour( item_def &item ) case OBJ_MISCELLANY: if ( is_deck(item) ) - { - item.colour = GREEN; - if ( one_chance_in(10) ) - item.colour = LIGHTMAGENTA; // legendary - if ( one_chance_in(5) ) - item.colour = (coinflip() ? MAGENTA : BROWN); break; - } switch (item.sub_type) { @@ -2835,8 +2828,18 @@ int items( int allow_uniques, // not just true-false, } if ( is_deck(mitm[p]) ) + { mitm[p].plus = 4 + random2(10); + mitm[p].special = DECK_RARITY_COMMON; + if ( one_chance_in(10) ) + mitm[p].special = DECK_RARITY_LEGENDARY; + if ( one_chance_in(5) ) + mitm[p].special = DECK_RARITY_RARE; + + init_deck(mitm[p]); + } + if (mitm[p].sub_type == MISC_RUNE_OF_ZOT) mitm[p].plus = item_race; diff --git a/crawl-ref/source/religion.cc b/crawl-ref/source/religion.cc index 961df346ea..3129a0808c 100644 --- a/crawl-ref/source/religion.cc +++ b/crawl-ref/source/religion.cc @@ -241,10 +241,10 @@ const char* god_gain_power_messages[NUM_GODS][MAX_GOD_ABILITIES] = "call in reinforcement", "" }, // Nemelex - { "peek at the first card of a deck", + { "peek at three random cards from a deck", "draw cards from decks in your inventory", "draw cards with careful consideration", - "", + "mark decks", "stack decks" }, // Elyvilon { "call upon Elyvilon for minor healing", @@ -327,10 +327,10 @@ const char* god_lose_power_messages[NUM_GODS][MAX_GOD_ABILITIES] = "call in reinforcement", "" }, // Nemelex - { "peek at the first card of a deck", + { "peek at three random cards from a deck", "draw cards from decks in your inventory", "draw cards with careful consideration", - "", + "mark decks", "stack decks" }, // Elyvilon { "call upon Elyvilon for minor healing", @@ -660,7 +660,8 @@ static void do_god_gift(bool prayed_for) item_def &deck(mitm[thing_created]); - deck.colour = deck_rarity_to_color(rarity); + deck.special = rarity; + deck.colour = deck_rarity_to_color(rarity); move_item_to_grid( &thing_created, you.x_pos, you.y_pos ); origin_acquired(deck, you.religion); diff --git a/crawl-ref/source/tags.cc b/crawl-ref/source/tags.cc index 46505fdbe5..e26a4e9eeb 100644 --- a/crawl-ref/source/tags.cc +++ b/crawl-ref/source/tags.cc @@ -128,8 +128,6 @@ static void marshallGhost(tagHeader &th, const ghost_demon &ghost); static ghost_demon unmarshallGhost( tagHeader &th ); static void marshall_monster(tagHeader &th, const monsters &m); static void unmarshall_monster(tagHeader &th, monsters &m); -static void marshall_item(tagHeader &th, const item_def &item); -static void unmarshall_item(tagHeader &th, item_def &item); template<typename T, typename T_iter, typename T_marshal> static void marshall_iterator(struct tagHeader &th, T_iter beg, T_iter end, @@ -939,19 +937,7 @@ static void tag_construct_you_items(struct tagHeader &th) // how many inventory slots? marshallByte(th, ENDOFPACK); for (i = 0; i < ENDOFPACK; ++i) - { - marshallByte(th,you.inv[i].base_type); - marshallByte(th,you.inv[i].sub_type); - marshallShort(th,you.inv[i].plus); - marshallLong(th,you.inv[i].special); - marshallByte(th,you.inv[i].colour); - marshallLong(th,you.inv[i].flags); - marshallShort(th,you.inv[i].quantity); - marshallShort(th,you.inv[i].plus2); - marshallShort(th, you.inv[i].orig_place); - marshallShort(th, you.inv[i].orig_monnum); - marshallString(th, you.inv[i].inscription.c_str(), 80); - } + marshallItem(th, you.inv[i]); marshallByte(th, you.quiver); @@ -1073,14 +1059,14 @@ static void marshall_follower(tagHeader &th, const follower &f) { marshall_monster(th, f.mons); for (int i = 0; i < NUM_MONSTER_SLOTS; ++i) - marshall_item(th, f.items[i]); + marshallItem(th, f.items[i]); } static void unmarshall_follower(tagHeader &th, follower &f) { unmarshall_monster(th, f.mons); for (int i = 0; i < NUM_MONSTER_SLOTS; ++i) - unmarshall_item(th, f.items[i]); + unmarshallItem(th, f.items[i]); } static void marshall_follower_list(tagHeader &th, const m_transit_list &mlist) @@ -1288,26 +1274,7 @@ static void tag_read_you_items(struct tagHeader &th, char minorVersion) // how many inventory slots? count_c = unmarshallByte(th); for (i = 0; i < count_c; ++i) - { - you.inv[i].base_type = - static_cast<object_class_type>(unmarshallByte(th)); - you.inv[i].sub_type = (unsigned char) unmarshallByte(th); - you.inv[i].plus = unmarshallShort(th); - you.inv[i].special = unmarshallLong(th); - you.inv[i].colour = (unsigned char) unmarshallByte(th); - you.inv[i].flags = (unsigned long) unmarshallLong(th); - you.inv[i].quantity = unmarshallShort(th); - you.inv[i].plus2 = unmarshallShort(th); - you.inv[i].orig_place = unmarshallShort(th); - you.inv[i].orig_monnum = unmarshallShort(th); - you.inv[i].inscription = unmarshallString(th, 80); - - // these never need to be saved for items in the inventory -- bwr - you.inv[i].x = -1; - you.inv[i].y = -1; - you.inv[i].link = i; - you.inv[i].slot = index_to_letter(i); - } + unmarshallItem(th, you.inv[i]); you.quiver = unmarshallByte(th); @@ -1553,7 +1520,7 @@ static void tag_construct_level(struct tagHeader &th) env.markers.write(th); } -static void marshall_item(tagHeader &th, const item_def &item) +void marshallItem(tagHeader &th, const item_def &item) { marshallByte(th, item.base_type); marshallByte(th, item.sub_type); @@ -1568,16 +1535,21 @@ static void marshall_item(tagHeader &th, const item_def &item) marshallLong(th, item.flags); marshallShort(th, item.link); // unused - marshallShort(th, igrd[item.x][item.y]); // unused + if (item.x == -1 && item.y == -1) + marshallShort(th, -1); // unused + else + marshallShort(th, igrd[item.x][item.y]); // unused marshallByte(th, item.slot); marshallShort(th, item.orig_place); marshallShort(th, item.orig_monnum); marshallString(th, item.inscription.c_str(), 80); + + item.props.write(th); } -static void unmarshall_item(tagHeader &th, item_def &item) +void unmarshallItem(tagHeader &th, item_def &item) { item.base_type = static_cast<object_class_type>(unmarshallByte(th)); item.sub_type = (unsigned char) unmarshallByte(th); @@ -1603,6 +1575,8 @@ static void unmarshall_item(tagHeader &th, item_def &item) item.orig_place = unmarshallShort(th); item.orig_monnum = unmarshallShort(th); item.inscription = unmarshallString(th, 80); + + item.props.read(th); } static void tag_construct_level_items(struct tagHeader &th) @@ -1619,7 +1593,7 @@ static void tag_construct_level_items(struct tagHeader &th) // how many items? marshallShort(th, MAX_ITEMS); for (int i = 0; i < MAX_ITEMS; ++i) - marshall_item(th, mitm[i]); + marshallItem(th, mitm[i]); } static void marshall_mon_enchant(tagHeader &th, const mon_enchant &me) @@ -1799,7 +1773,7 @@ static void tag_read_level_items(struct tagHeader &th, char minorVersion) // how many items? const int item_count = unmarshallShort(th); for (int i = 0; i < item_count; ++i) - unmarshall_item(th, mitm[i]); + unmarshallItem(th, mitm[i]); } static void unmarshall_monster(tagHeader &th, monsters &m) diff --git a/crawl-ref/source/tags.h b/crawl-ref/source/tags.h index 40244224e9..df4c3d754b 100644 --- a/crawl-ref/source/tags.h +++ b/crawl-ref/source/tags.h @@ -72,6 +72,7 @@ void marshallBoolean(struct tagHeader &th, bool data); void marshallString(struct tagHeader &th, const std::string &data, int maxSize = 0); void marshallCoord(tagHeader &th, const coord_def &c); +void marshallItem(tagHeader &th, const item_def &item); // last updated 22jan2001 {gdl} /* *********************************************************************** @@ -85,6 +86,7 @@ bool unmarshallBoolean(struct tagHeader &th); int unmarshallCString(struct tagHeader &th, char *data, int maxSize); std::string unmarshallString(tagHeader &th, int maxSize = 1000); void unmarshallCoord(tagHeader &th, coord_def &c); +void unmarshallItem(tagHeader &th, item_def &item); std::string make_date_string( time_t in_date ); time_t parse_date_string( char[20] ); |