associative array with heterogeneous values, capable of holding booleans, bytes, shorts, longs, floats, string, coordinates (coord_def), items (item_def) and nested hash tables. A table can be made to be homogeneous by giving it a value type, which causes dynamic type checking to be performed. The same type checking can be performed for individual member values of a heterogeneous table by setting a flag on that value. A flag can also be set on an individual member value to prevent its value from being changed.
CrawlHashTable is currently only used for the props field of the item_def struct (though it could easily be added elsewhere), and is only being used by decks. The deck structure has been changed so that deck.plus is the original number of cards in the deck, deck.plus2 is either the number of cards used or the number of cards left, and deck.special hold the deck's rarity. The cards themselves are selected at deck creation time and stored in the nested hash table deck.props["cards"]. The Nemelex "Peek Deck" ability has been changed to identify the deck, draw three cards from it, show them to the user, and shuffle them back into the deck (with special cases for decks containing only one or two cards). A fourth Nemelex ability, "Mark Deck", has been added, which picks four cards from the deck, marks them, and then shuffles them back into the deck, creating a deck with a mixture of marked and unmarked cards.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@2370 c06c8d41-db1a-0410-9941-cceddc491573
EGV2HM7SD7UQSWJGLR65NQJTUBAJ7WHLM67FMH4UFP7JRSFKREPAC
2BCJY2VAUXBEDHN7RN5T3DZ4Q2PNXKVZF7RYH5TY6M2CXGJAPZYAC
Q47PZ5JVUSAU7MOHGPJ7XKFUYKPVEPVQHXSIHU6KLRPVOHPQMF5AC
LHPOPBFQJOYUD2ZESOJQGPGB3YLWLDOO6KF35MKYFECN45NBLDEAC
5HBRQ2QZTFBQE2Z2CFADUI2D52LO5Z5CNHXHA7BJP3LGO7FJPUCQC
5BJPWUPLJFS34FUTFJVKA4A52YMIGV6EWDXLNSDCWBJWBGVSQFGQC
J5AAA4LUTW6PLS2DBKGTXPSW2SGOBMICEOA3VDHRSKVLU36EGQMAC
CWHVAAPTQD4OYISQP2YZI7KZ6DZ4HIK63UFJ2XKTPAFCSTPHPJAQC
HZ6SBPPUHJUYFVXA6KOELY455RTBXICRJGVHTUYLHWAPV2DEOMSQC
IVVTHLTTLOP5TSULXJWUSSXHOKYWVU3OWKYVK45A7RIB6V34MYQAC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
7KWDC7XFNMBLSUO2HISIROBINZBX5T67LJEEXTAORXW2YZ7VWFGAC
SDLKLUNFGVKDS55DDJZCBAVIB7NL3RRYPTACAY65SCUQKV6APFSAC
QDWDUURSNLMT6AXNNJ3DEQCWAKCAIHV6MP5F7QGIBGXOG2BI2NPQC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
HSRRNAU5UAYC6B6IQWGJPFROMZBTJICPCH6DJVZDHDTAGOQ6IOYAC
5KJCHLIUFKRPMIVWUAYT6EOF7SW4PTQF6Y5OPEFWXGLE7DUGYLZAC
5ASC3STDYCNLZFEBN6UTMUCGDETHBR2OCBZCF5VIAZ5RRWLOTDYQC
LCCGXSFIDQFIRXHGRJWOELNPQOHHCXCWXS366GOULDFPQVOKAIJAC
X5WLJCJVW55SXZVP7IKP7ADCJIGNKN4PKAXFECVR6TNK7XSMZR7QC
JW2KRJHES33W7UTWZ6NDO4TLMK4EFU4HKZXBWR2UJOMPCCOTR4CQC
2ESKXYN266CEMLSL6DNCKG4REDO34FXL4ODVGMWDJTNJRKMXWCRQC
Z7SW3IKYNXMOHHD77NGXN6RGL32PZBO6AIHLJY74IRO23AIRKWMQC
RR2J4VLJCZSAKY3HNS334KI4YUAPOMSETO2HGCGEEIUCUJAYAGSQC
E42EFZ3RINKLTGOJJZAH2N5QF3P4S5NCO5T52HLXJRMBPP463HTAC
3KAINFIXO7WNWGUGZB43EUNFRS2ZPBLQZDTY456QACMRHYIJ7WDAC
DTO3EUKWHZ5RJNGNCFYXSOVTIPVXPP637F2W7WFGYKJ7JK7VNKNQC
7AMQN7MITMXBNVDAK5VOXTQ4TZIAOD6ZLOFJG7GQMBTY23Y2BKSAC
MQ62OAMLGJVRW2QIL4PAZRAU6PC52ZVGY2FCOBIY6IWGQIHMU5CAC
VNHFP63ZLLZU3A3PLXP4BITX57DUIYDHFOHQYK3BOBHV3S64G26QC
UL7XFKMUX3WIU4O2LZANK4ECJ654UZPDBFGNXUEYZYOLKBYBCG6AC
JVSCP4FTW2G57C6YD5HZOZXTODGZH7TR75JQGFJBEPX3LCZH236QC
2IJDLTWK74ULETLFTIPUIY2MSG6RNONVAFNFE7MADPT6UNYSSBDAC
DH3YTI6VVI727SQXO4CXSDCSBG2UN3UAWLFULBGRLBVH22ACRXIAC
TJRYL3NXPW5IUGEV3YOC7JYWEXCZDBFPLT4AUG4P227WVKVB72ZAC
SVY2PTCLXR3KNPQAWXVXTTGCC5DR334HOAKHYO3VDDRWM2BWMALAC
QKGDOYIYKE6B36ION5O2DRW65DWWPZMYNWJVH7LJJ7FPGGM2MYAQC
JPYDWBRN75GC6UZ26MXJTCXGORTJOWGRDEU4JFPU52LYHGK6UI2QC
JM7UAK777RAVDAVLQLEOBRTGNW2B47S5G55XITJXO243IUNZHVYQC
LP3U7LC6QK6TCMLAYTRGZ2CDZAHPM6VWDT6NPE5ET4WBBZVHBDXQC
WVFKGV3AMOYUZ53MWH2RWRITRRCPKKNPTO7QASA5WVKWAUGDJ2OQC
J7VFKVSE7DLDDWLIVSRKABUZ2AK6I7CXKCY3O4WMKINA46K6QO4AC
KFZYPFHHOWRUZEK2PW26EI73Z6I6DLHW2YEJV2CB5XBWFRRNBFXQC
2UBWR54HKLIWZQXB3ZFOH4R4X6TZWWZZWEU26KRDOS3BHC5J7GPAC
{
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]);
{
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);
unmarshallItem(th, you.inv[i]);
// 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);
}
// 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 << "}";
/*
* 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
/*
* 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];
}
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
};
// 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 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
// 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.
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;
}
// 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())];
return choose_one_card(item, message, garbage);
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)
{
CrawlHashTable &props = deck.props;
CrawlHashTable &cards = props["cards"].get_table();
CrawlHashTable &flags = props["card_flags"].get_table();
int idx = cards.size();
cards[idx] = (char) card;
flags[idx] = (char) _flags;
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.";
if (deck.plus2 == 0 && deck.special != 0)
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 && !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);
deck.plus2 += num_buggy;
num_cards = cards.size();
num_flags = cards.size();
if (stacked_wrong || buggy_cards)
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 (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)
you.wield_change = true;
print_stats();
#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(!yesno("Deck had problems; use it anyways?"))
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;
}
CrawlHashTable &cards = deck.props["cards"];
int num_cards = cards.size();
card_type card1, card2, card3;
unsigned char flags1, flags2, flags3;
card1 = get_card_and_flags(deck, 0, flags1);
retry_blank_card(card1, deck, flags1);
if (num_cards == 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;
item_def& item(you.inv[you.equip[EQ_WEAPON]]);
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"]++;
}
if (check_buggy_deck(item))
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))
msg::stream << "You see " << card_name(chosen) << '.' << std::endl;
item.plus2 = chosen + 1;
you.wield_change = true;
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);
// You lose 1d2 cards when peeking.
if ( item.plus > 1 )
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)
const int num_to_stack = (item.plus < 5 ? item.plus : 5);
const int num_cards = cards_in_deck(deck);
const int num_to_stack = (num_cards < 5 ? num_cards : 5);
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);
std::vector<card_type> draws;
for ( int i = 0; i < num_to_stack; ++i )
draws.push_back(choose_one_card(item, false));
if (i < num_to_stack)
{
draws.push_back(card);
flags.push_back(_flags | CFLAG_SEEN | CFLAG_MARKED);
}
else
; // Rest of deck is discarded.
}
item.special = 0;
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();
// 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)
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;
// 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"]--;
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++;
}
// 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 )
if (!deck_gone && allow_id
&& (you.skills[SK_EVOCATIONS] > 5 + random2(35)))
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)))
{
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);
}
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);