summaryrefslogtreecommitdiffstats
path: root/trunk/source/Kills.cc
diff options
context:
space:
mode:
authordshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573>2006-08-02 12:54:15 +0000
committerdshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573>2006-08-02 12:54:15 +0000
commitd5e5340c3926d1cf97f6cba151ffaecb20bfb35f (patch)
treed1faf7d5b27df8f3c523a8dd33357804118e62b1 /trunk/source/Kills.cc
parent7b2204d69f21d7075e4666ee032d7a129081bc4b (diff)
downloadcrawl-ref-d5e5340c3926d1cf97f6cba151ffaecb20bfb35f.tar.gz
crawl-ref-d5e5340c3926d1cf97f6cba151ffaecb20bfb35f.zip
Integrated travel patch as of 20060727
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@7 c06c8d41-db1a-0410-9941-cceddc491573
Diffstat (limited to 'trunk/source/Kills.cc')
-rw-r--r--trunk/source/Kills.cc1098
1 files changed, 1098 insertions, 0 deletions
diff --git a/trunk/source/Kills.cc b/trunk/source/Kills.cc
new file mode 100644
index 0000000000..4a1eaa0ebf
--- /dev/null
+++ b/trunk/source/Kills.cc
@@ -0,0 +1,1098 @@
+/*
+ * File: Kills.cc
+ * Summary: Player kill tracking
+ * Written by: Darshan Shaligram
+ */
+#include "AppHdr.h"
+#include "chardump.h"
+#include "describe.h"
+#include "mon-util.h"
+#include "files.h"
+#include "itemname.h"
+#include "travel.h"
+#include "tags.h"
+#include "Kills.h"
+#include "clua.h"
+#include <algorithm>
+
+#define KILLS_MAJOR_VERSION 4
+#define KILLS_MINOR_VERSION 1
+
+#ifdef CLUA_BINDINGS
+static void kill_lua_filltable(std::vector<kill_exp> &v);
+#endif
+
+
+unsigned short get_packed_place( unsigned char branch, int subdepth,
+ char level_type )
+{
+ unsigned short place = (unsigned short)
+ ( (branch << 8) | subdepth );
+ if (level_type == LEVEL_ABYSS || level_type == LEVEL_PANDEMONIUM
+ || level_type == LEVEL_LABYRINTH)
+ place = (unsigned short) ( (level_type << 8) | 0xFF );
+ return place;
+}
+
+unsigned short get_packed_place()
+{
+ return get_packed_place( you.where_are_you,
+ subdungeon_depth(you.where_are_you, you.your_level),
+ you.level_type );
+}
+
+///////////////////////////////////////////////////////////////////////////
+// KillMaster
+//
+
+const char *kill_category_names[] =
+{
+ "you",
+ "collateral kills",
+ "others",
+};
+
+const char *KillMaster::category_name(KillCategory kc) const
+{
+ if (kc >= KC_YOU && kc < KC_NCATEGORIES)
+ return (kill_category_names[kc]);
+ return (NULL);
+}
+
+bool KillMaster::empty() const
+{
+ for (int i = 0; i < KC_NCATEGORIES; ++i)
+ if (!categorized_kills[i].empty())
+ return (false);
+ return (true);
+}
+
+void KillMaster::save(FILE *file) const
+{
+ // Write the version of the kills file
+ writeByte(file, KILLS_MAJOR_VERSION);
+ writeByte(file, KILLS_MINOR_VERSION);
+
+ for (int i = 0; i < KC_NCATEGORIES; ++i)
+ categorized_kills[i].save(file);
+}
+
+void KillMaster::load(FILE *file)
+{
+ unsigned char major = readByte(file),
+ minor = readByte(file);
+ if (major != KILLS_MAJOR_VERSION ||
+ (minor != KILLS_MINOR_VERSION && minor > 0))
+ return ;
+
+ for (int i = 0; i < KC_NCATEGORIES; ++i)
+ {
+ categorized_kills[i].load(file);
+ if (!minor)
+ break;
+ }
+}
+
+void KillMaster::record_kill(const monsters *mon, int killer, bool ispet)
+{
+ KillCategory kc =
+ (killer == KILL_YOU || killer == KILL_YOU_MISSILE)? KC_YOU :
+ (ispet)? KC_FRIENDLY :
+ KC_OTHER;
+ categorized_kills[kc].record_kill(mon);
+}
+
+std::string KillMaster::kill_info() const
+{
+ if (empty())
+ return ("");
+
+ std::string killtext;
+
+ bool needseparator = false;
+ int categories = 0;
+ long grandtotal = 0L;
+
+ Kills catkills[KC_NCATEGORIES];
+ for (int i = 0; i < KC_NCATEGORIES; ++i)
+ {
+ int targ = Options.kill_map[i];
+ catkills[targ].merge( categorized_kills[i] );
+ }
+
+ for (int i = KC_YOU; i < KC_NCATEGORIES; ++i)
+ {
+ if (catkills[i].empty())
+ continue;
+
+ categories++;
+ std::vector<kill_exp> kills;
+ long count = catkills[i].get_kills(kills);
+ grandtotal += count;
+
+ add_kill_info( killtext,
+ kills,
+ count,
+ i == KC_YOU? NULL :
+ category_name((KillCategory) i),
+ needseparator );
+ needseparator = true;
+ }
+
+ std::string grandt;
+ if (categories > 1)
+ {
+ char buf[200];
+ snprintf(buf, sizeof buf,
+ "Grand Total: %ld creatures vanquished",
+ grandtotal);
+ grandt = buf;
+ }
+
+#ifdef CLUA_BINDINGS
+ // Call the kill dump Lua function with null a, to tell it we're done.
+ if (!clua.callfn("c_kill_list", "ss", NULL, grandt.c_str()))
+#endif
+ {
+ // We can sum up ourselves, if Lua doesn't want to.
+ // FIXME: I'm not happy with the looks/wording of the grand total
+ // count.
+ if (categories > 1)
+ {
+ // Give ourselves a newline first
+ killtext += EOL;
+ killtext += grandt + EOL;
+ }
+ }
+
+ return killtext;
+}
+
+void KillMaster::add_kill_info(std::string &killtext,
+ std::vector<kill_exp> &kills,
+ long count,
+ const char *category,
+ bool separator) const
+{
+#ifdef CLUA_BINDINGS
+ // Set a pointer to killtext as a Lua global
+ lua_pushlightuserdata(clua.state(), &killtext);
+ clua.setregistry("cr_skill");
+
+ // Populate a Lua table with kill_exp structs, in the default order,
+ // and leave the table on the top of the Lua stack.
+ kill_lua_filltable(kills);
+
+ if (category)
+ lua_pushstring(clua, category);
+ else
+ lua_pushnil(clua);
+
+ lua_pushboolean(clua, separator);
+
+ if (!clua.callfn("c_kill_list", 3, 0))
+#endif
+ {
+#ifdef CLUA_BINDINGS
+ if (clua.error.length())
+ {
+ killtext += "Lua error:\n";
+ killtext += clua.error + "\n\n";
+ }
+#endif
+ if (separator)
+ killtext += EOL;
+
+ killtext += "Vanquished Creatures";
+ if (category)
+ killtext += std::string(" (") + category + ")";
+
+ killtext += EOL;
+
+ for (int i = 0, sz = kills.size(); i < sz; ++i)
+ {
+ killtext += " " + kills[i].desc;
+ killtext += EOL;
+ }
+ {
+ char numbuf[100];
+ snprintf(numbuf, sizeof numbuf,
+ "%ld creature%s vanquished." EOL, count,
+ count > 1? "s" : "");
+ killtext += numbuf;
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+bool Kills::empty() const
+{
+ return kills.empty() && ghosts.empty();
+}
+
+void Kills::merge(const Kills &k)
+{
+ ghosts.insert( ghosts.end(), k.ghosts.begin(), k.ghosts.end() );
+
+ // Regular kills are messier to merge.
+ for (kill_map::const_iterator i = k.kills.begin();
+ i != k.kills.end(); ++i)
+ {
+ const kill_monster_desc &kmd = i->first;
+ kill_def &k = kills[kmd];
+ const kill_def &ko = i->second;
+ bool uniq = mons_is_unique(kmd.monnum);
+ k.merge(ko, uniq);
+ }
+}
+
+void Kills::record_kill(const struct monsters *mon)
+{
+ // Handle player ghosts separately.
+ if (mon->type == MONS_PLAYER_GHOST || mon->type == MONS_PANDEMONIUM_DEMON)
+ {
+ record_ghost_kill(mon);
+ return ;
+ }
+
+ // Normal monsters
+ // Create a descriptor
+ kill_monster_desc descriptor = mon;
+
+ kill_def &k = kills[descriptor];
+ if (k.kills)
+ k.add_kill(mon, get_packed_place());
+ else
+ k = kill_def(mon);
+}
+
+long Kills::get_kills(std::vector<kill_exp> &all_kills) const
+{
+ long count = 0;
+ kill_map::const_iterator iter = kills.begin();
+ for (; iter != kills.end(); ++iter)
+ {
+ const kill_monster_desc &md = iter->first;
+ const kill_def &k = iter->second;
+ all_kills.push_back( kill_exp(k, md) );
+ count += k.kills;
+ }
+
+ ghost_vec::const_iterator gi = ghosts.begin();
+ for (; gi != ghosts.end(); ++gi)
+ {
+ all_kills.push_back( kill_exp(*gi) );
+ }
+ count += ghosts.size();
+
+ std::sort(all_kills.begin(), all_kills.end());
+ return (count);
+}
+
+// Takes a packed 'place' and returns a compact stringified place name.
+// XXX: This is done in several other places; a unified function to
+// describe places would be nice.
+std::string short_place_name(unsigned short place)
+{
+ unsigned char branch = (unsigned char) ((place >> 8) & 0xFF);
+ int lev = place & 0xFF;
+
+ const char *s;
+ bool level_num = false;
+ if (lev == 0xFF)
+ {
+ switch (branch)
+ {
+ case LEVEL_ABYSS:
+ s = "Abyss";
+ break;
+ case LEVEL_PANDEMONIUM:
+ s = "Pan";
+ break;
+ case LEVEL_LABYRINTH:
+ s = "Lab";
+ break;
+ default:
+ s = "Buggy Badlands";
+ break;
+ }
+ }
+ else
+ {
+ switch (branch)
+ {
+ case BRANCH_VESTIBULE_OF_HELL:
+ s = "Hell";
+ break;
+ case BRANCH_HALL_OF_BLADES:
+ s = "Blade";
+ break;
+ case BRANCH_ECUMENICAL_TEMPLE:
+ s = "Temple";
+ break;
+ default:
+ level_num = true;
+ s = (branch == BRANCH_DIS) ? "Dis:" :
+ (branch == BRANCH_GEHENNA) ? "Geh:" :
+ (branch == BRANCH_COCYTUS) ? "Coc:" :
+ (branch == BRANCH_TARTARUS) ? "Tar:" :
+ (branch == BRANCH_ORCISH_MINES) ? "Orc:" :
+ (branch == BRANCH_HIVE) ? "Hive:" :
+ (branch == BRANCH_LAIR) ? "Lair:" :
+ (branch == BRANCH_SLIME_PITS) ? "Slime:" :
+ (branch == BRANCH_VAULTS) ? "Vault:" :
+ (branch == BRANCH_CRYPT) ? "Crypt:" :
+ (branch == BRANCH_HALL_OF_ZOT) ? "Zot:" :
+ (branch == BRANCH_SNAKE_PIT) ? "Snake:" :
+ (branch == BRANCH_ELVEN_HALLS) ? "Elf:" :
+ (branch == BRANCH_TOMB) ? "Tomb:" :
+ (branch == BRANCH_SWAMP) ? "Swamp:" : "D:";
+ break;
+ }
+ }
+
+ std::string pl = s;
+ if (level_num)
+ {
+ char buf[20];
+ snprintf(buf, sizeof buf, "%d", lev);
+ pl += buf;
+ }
+ return pl;
+}
+
+void Kills::save(FILE *file) const
+{
+ // How many kill records do we have?
+ writeLong(file, kills.size());
+
+ kill_map::const_iterator iter = kills.begin();
+ for ( ; iter != kills.end(); ++iter)
+ {
+ iter->first.save(file);
+ iter->second.save(file);
+ }
+
+ // How many ghosts do we have?
+ writeShort(file, ghosts.size());
+ for (ghost_vec::const_iterator iter = ghosts.begin();
+ iter != ghosts.end(); ++iter)
+ {
+ iter->save(file);
+ }
+}
+
+void Kills::load(FILE *file)
+{
+ // How many kill records?
+ long kill_count = readLong(file);
+ kills.clear();
+ for (long i = 0; i < kill_count; ++i)
+ {
+ kill_monster_desc md;
+ md.load(file);
+ kills[md].load(file);
+ }
+
+ short ghost_count = readShort(file);
+ ghosts.clear();
+ for (short i = 0; i < ghost_count; ++i)
+ {
+ kill_ghost kg;
+ kg.load(file);
+ ghosts.push_back(kg);
+ }
+}
+
+void Kills::record_ghost_kill(const struct monsters *mon)
+{
+ kill_ghost ghost(mon);
+ ghosts.push_back(ghost);
+}
+
+kill_def::kill_def(const struct monsters *mon) : kills(0), exp(0)
+{
+ exp = exper_value( (struct monsters *) mon);
+ add_kill(mon, get_packed_place());
+}
+
+static bool ends_with(const std::string &s, const char *suffix)
+{
+ std::string other = suffix;
+ if (s.length() < other.length()) return false;
+ return (s.substr(s.length() - other.length()) == other);
+}
+
+static bool ends_with(const std::string &s, const char *suffixes[])
+{
+ if (!suffixes) return false;
+ for ( ; *suffixes; suffixes++)
+ {
+ if (ends_with(s, *suffixes))
+ return true;
+ }
+ return false;
+}
+
+// For monster names ending with these suffixes, we pluralize directly without
+// attempting to use the "of" rule. For instance:
+//
+// moth of wrath => moths of wrath but
+// moth of wrath zombie => moth of wrath zombies.
+//
+// This is not necessary right now, since there are currently no monsters that
+// require this special treatment (no monster with 'of' in its name is eligible
+// for zombies or skeletons).
+static const char *modifier_suffixes[] =
+{
+ "zombie", "skeleton", "simulacrum", NULL,
+};
+
+// Pluralizes a monster name. This'll need to be updated for correctness
+// whenever new monsters are added.
+static std::string pluralize(const std::string &name,
+ const char *no_of[] = NULL)
+{
+ std::string::size_type pos;
+
+ // Pluralize first word of names like 'eye of draining', but only if the
+ // whole name is not suffixed by a modifier, such as 'zombie' or 'skeleton'
+ if ( (pos = name.find(" of ")) != std::string::npos
+ && !ends_with(name, no_of) )
+ {
+ return pluralize(name.substr(0, pos)) + name.substr(pos);
+ }
+ else if (ends_with(name, "us"))
+ // Fungus, ufetubus, for instance.
+ return name.substr(0, name.length() - 2) + "i";
+ else if (ends_with(name, "larva") || ends_with(name, "amoeba"))
+ // Giant amoebae sounds a little weird, to tell the truth.
+ return name + "e";
+ else if (ends_with(name, "ex"))
+ // Vortex; vortexes is legal, but the classic plural is cooler.
+ return name.substr(0, name.length() - 2) + "ices";
+ else if (ends_with(name, "cyclops"))
+ return name.substr(0, name.length() - 1) + "es";
+ else if (ends_with(name, "y"))
+ return name.substr(0, name.length() - 1) + "ies";
+ else if (ends_with(name, "lf"))
+ // Elf, wolf. Dwarfs can stay dwarfs, if there were dwarfs.
+ return name.substr(0, name.length() - 1) + "ves";
+ else if (ends_with(name, "mage"))
+ // mage -> magi
+ return name.substr(0, name.length() - 1) + "i";
+ else if ( ends_with(name, "sheep") || ends_with(name, "manes")
+ || ends_with(name, "fish") )
+ // Maybe we should generalise 'manes' to ends_with("es")?
+ return name;
+ else if (ends_with(name, "ch") || ends_with(name, "sh")
+ || ends_with(name, "x"))
+ // To handle cockroaches, fish and sphinxes. Fish will be netted by
+ // the previous check anyway.
+ return name + "es";
+ else if (ends_with(name, "um"))
+ // simulacrum -> simulacra
+ return name.substr(0, name.length() - 2) + "a";
+ else if (ends_with(name, "efreet"))
+ // efreet -> efreeti. Not sure this is correct.
+ return name + "i";
+
+ return name + "s";
+}
+
+// Naively prefix A/an to a monster name. At the moment, we don't have monster
+// names that demand more sophistication (maybe ynoxinul - don't know how
+// that's pronounced).
+static std::string article_a(const std::string &name)
+{
+ if (!name.length()) return name;
+ switch (name[0])
+ {
+ case 'a': case 'e': case 'i': case 'o': case 'u':
+ case 'A': case 'E': case 'I': case 'O': case 'U':
+ return "An " + name;
+ default:
+ return "A " + name;
+ }
+}
+
+// For a non-unique monster, prefixes a suitable article if we have only one
+// kill, else prefixes a kill count and pluralizes the monster name.
+static std::string n_names(const std::string &name, int n)
+{
+ if (n > 1)
+ {
+ char buf[20];
+ snprintf(buf, sizeof buf, "%d ", n);
+ return buf + pluralize(name, modifier_suffixes);
+ }
+ else
+ return article_a(name);
+}
+
+// Returns a string describing the number of times a unique has been killed.
+// Currently required only for Boris.
+//
+static std::string kill_times(int kills)
+{
+ char buf[50];
+ switch (kills)
+ {
+ case 1:
+ strcpy(buf, " (once)");
+ break;
+ case 2:
+ strcpy(buf, " (twice)");
+ break;
+ case 3:
+ strcpy(buf, " (thrice)");
+ break;
+ default:
+ snprintf(buf, sizeof buf, " (%d times)", kills);
+ break;
+ }
+ return std::string(buf);
+}
+
+void kill_def::merge(const kill_def &k, bool uniq)
+{
+ if (!kills)
+ {
+ *this = k;
+ }
+ else
+ {
+ kills += k.kills;
+ for (int i = 0, size = k.places.size(); i < size; ++i)
+ add_place(k.places[i], uniq);
+ }
+}
+
+void kill_def::add_kill(const struct monsters *mon, unsigned short place)
+{
+ kills++;
+ add_place(place, mons_is_unique(mon->type));
+}
+
+void kill_def::add_place(unsigned short place, bool force)
+{
+ for (unsigned i = 0; i < places.size(); ++i)
+ if (places[i] == place) return;
+
+ if (force || places.size() < PLACE_LIMIT)
+ places.push_back(place);
+}
+
+std::string kill_def::base_name(const kill_monster_desc &md) const
+{
+ char monnamebuf[ITEMNAME_SIZE]; // Le sigh.
+ moname(md.monnum, true, DESC_PLAIN, monnamebuf);
+
+ std::string name = monnamebuf;
+ switch (md.modifier)
+ {
+ case kill_monster_desc::M_ZOMBIE:
+ name += " zombie";
+ break;
+ case kill_monster_desc::M_SKELETON:
+ name += " skeleton";
+ break;
+ case kill_monster_desc::M_SIMULACRUM:
+ name += " simulacrum";
+ break;
+ case kill_monster_desc::M_SPECTRE:
+ name = "spectral " + name;
+ break;
+ default:
+ // Silence compiler warning about not handling M_NORMAL and
+ // M_SHAPESHIFTER
+ break;
+ }
+
+ switch (md.monnum)
+ {
+ case MONS_ABOMINATION_LARGE:
+ name = "large " + name;
+ break;
+ case MONS_ABOMINATION_SMALL:
+ // Do nothing
+ break;
+ case MONS_RAKSHASA_FAKE:
+ name = "illusory " + name;
+ break;
+ }
+ return name;
+}
+
+std::string kill_def::info(const kill_monster_desc &md) const
+{
+ std::string name = base_name(md);
+
+ if (!mons_is_unique(md.monnum))
+ {
+ // Pluralize as needed
+ name = n_names(name, kills);
+
+ // We brand shapeshifters with the (shapeshifter) qualifier. This
+ // has to be done after doing pluralize(), else we get very odd plurals
+ // :)
+ if (md.modifier == kill_monster_desc::M_SHAPESHIFTER &&
+ md.monnum != MONS_SHAPESHIFTER &&
+ md.monnum != MONS_GLOWING_SHAPESHIFTER)
+ name += " (shapeshifter)";
+ }
+ else if (kills > 1)
+ {
+ // Aha! A resurrected unique
+ name += kill_times(kills);
+ }
+
+ // What places we killed this type of monster
+ return append_places(md, name);
+}
+
+std::string kill_def::append_places(const kill_monster_desc &md,
+ const std::string &name) const
+{
+ if (Options.dump_kill_places == KDO_NO_PLACES) return name;
+
+ int nplaces = places.size();
+ if ( nplaces == 1 || mons_is_unique(md.monnum)
+ || Options.dump_kill_places == KDO_ALL_PLACES )
+ {
+ std::string augmented = name;
+ augmented += " (";
+ for (std::vector<unsigned short>::const_iterator iter = places.begin();
+ iter != places.end(); ++iter)
+ {
+ if (iter != places.begin())
+ augmented += " ";
+ augmented += short_place_name(*iter);
+ }
+ augmented += ")";
+ return augmented;
+ }
+ return name;
+}
+
+void kill_def::save(FILE *file) const
+{
+ writeShort(file, kills);
+ writeShort(file, exp);
+
+ writeShort(file, places.size());
+ for (std::vector<unsigned short>::const_iterator iter = places.begin();
+ iter != places.end(); ++iter)
+ {
+ writeShort(file, *iter);
+ }
+}
+
+void kill_def::load(FILE *file)
+{
+ kills = (unsigned short) readShort(file);
+ exp = readShort(file);
+
+ places.clear();
+ short place_count = readShort(file);
+ for (short i = 0; i < place_count; ++i)
+ {
+ places.push_back((unsigned short) readShort(file));
+ }
+}
+
+kill_ghost::kill_ghost(const struct monsters *mon)
+{
+ exp = exper_value( (struct monsters *) mon);
+ place = get_packed_place();
+ ghost_name = ghost.name;
+
+ // Check whether this is really a ghost, since we also have to handle
+ // the Pandemonic demons.
+ if (mon->type == MONS_PLAYER_GHOST)
+ ghost_name = "The ghost of " + ghost_description(true);
+}
+
+std::string kill_ghost::info() const
+{
+ return ghost_name +
+ (Options.dump_kill_places != KDO_NO_PLACES?
+ " (" + short_place_name(place) + ")" : std::string(""));
+}
+
+void kill_ghost::save(FILE *file) const
+{
+ writeString(file, ghost_name);
+ writeShort(file, (unsigned short) exp);
+ writeShort(file, place);
+}
+
+void kill_ghost::load(FILE *file)
+{
+ ghost_name = readString(file);
+ exp = readShort(file);
+ place = (unsigned short) readShort(file);
+}
+
+kill_monster_desc::kill_monster_desc(const monsters *mon)
+{
+
+ // TODO: We need to understand how shapeshifters are handled.
+ monnum = mon->type;
+ modifier = M_NORMAL;
+ switch (mon->type)
+ {
+ case MONS_ZOMBIE_LARGE: case MONS_ZOMBIE_SMALL:
+ modifier = M_ZOMBIE;
+ break;
+ case MONS_SKELETON_LARGE: case MONS_SKELETON_SMALL:
+ modifier = M_SKELETON;
+ break;
+ case MONS_SIMULACRUM_LARGE: case MONS_SIMULACRUM_SMALL:
+ modifier = M_SIMULACRUM;
+ break;
+ case MONS_SPECTRAL_THING:
+ modifier = M_SPECTRE;
+ break;
+ }
+ if (modifier != M_NORMAL) monnum = mon->number;
+
+ if (mons_has_ench((struct monsters *) mon, ENCH_SHAPESHIFTER) ||
+ mons_has_ench((struct monsters *) mon, ENCH_GLOWING_SHAPESHIFTER))
+ modifier = M_SHAPESHIFTER;
+
+ // XXX: Ugly hack - merge all mimics into one mimic record.
+ if (monnum >= MONS_GOLD_MIMIC && monnum <= MONS_POTION_MIMIC)
+ monnum = MONS_WEAPON_MIMIC;
+}
+
+void kill_monster_desc::save(FILE *file) const
+{
+ writeShort(file, (short) monnum);
+ writeShort(file, (short) modifier);
+}
+
+void kill_monster_desc::load(FILE *file)
+{
+ monnum = (int) readShort(file);
+ modifier = (name_modifier) readShort(file);
+}
+
+#ifdef CLUA_BINDINGS
+///////////////////////////////////////////////////////////////////////////
+// Kill Lua interface
+//
+
+#define KILLEXP_ACCESS(name, type, field) \
+ static int kill_lualc_##name(lua_State *ls) { \
+ if (!lua_islightuserdata(ls, 1)) { \
+ luaL_argerror(ls, 1, "Unexpected argument type"); \
+ return 0; \
+ } \
+ \
+ kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) ); \
+ if (ke) { \
+ lua_push##type(ls, ke->field); \
+ return 1; \
+ } \
+ return 0; \
+ }
+
+KILLEXP_ACCESS(nkills, number, nkills)
+KILLEXP_ACCESS(exp, number, exp)
+KILLEXP_ACCESS(base_name, string, base_name.c_str())
+KILLEXP_ACCESS(desc, string, desc.c_str())
+KILLEXP_ACCESS(monnum, number, monnum)
+KILLEXP_ACCESS(isghost, boolean,
+ monnum == -1 &&
+ ke->desc.find("The ghost of") != std::string::npos)
+KILLEXP_ACCESS(ispandemon, boolean,
+ monnum == -1 &&
+ ke->desc.find("The ghost of") == std::string::npos)
+KILLEXP_ACCESS(isunique, boolean,
+ monnum != -1 && mons_is_unique(ke->monnum))
+
+
+static int kill_lualc_modifier(lua_State *ls)
+{
+ if (!lua_islightuserdata(ls, 1))
+ {
+ luaL_argerror(ls, 1, "Unexpected argument type");
+ return 0;
+ }
+
+ kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) );
+ if (ke)
+ {
+ const char *modifier;
+ switch (ke->modifier)
+ {
+ case kill_monster_desc::M_ZOMBIE:
+ modifier = "zombie";
+ break;
+ case kill_monster_desc::M_SKELETON:
+ modifier = "skeleton";
+ break;
+ case kill_monster_desc::M_SIMULACRUM:
+ modifier = "simulacrum";
+ break;
+ case kill_monster_desc::M_SPECTRE:
+ modifier = "spectre";
+ break;
+ case kill_monster_desc::M_SHAPESHIFTER:
+ modifier = "shapeshifter";
+ break;
+ default:
+ modifier = "";
+ break;
+ }
+ lua_pushstring(ls, modifier);
+ return 1;
+ }
+ return 0;
+}
+
+static int kill_lualc_places(lua_State *ls)
+{
+ if (!lua_islightuserdata(ls, 1))
+ {
+ luaL_argerror(ls, 1, "Unexpected argument type");
+ return 0;
+ }
+
+ kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) );
+ if (ke)
+ {
+ lua_newtable(ls);
+ for (int i = 0, count = ke->places.size(); i < count; ++i)
+ {
+ lua_pushnumber(ls, ke->places[i]);
+ lua_rawseti(ls, -2, i + 1);
+ }
+ return 1;
+ }
+ return 0;
+}
+
+static int kill_lualc_place_name(lua_State *ls)
+{
+ int num = luaL_checkint(ls, 1);
+ std::string plname = short_place_name(num);
+ lua_pushstring(ls, plname.c_str());
+ return 1;
+}
+
+static bool is_ghost(const kill_exp *ke)
+{
+ return ke->monnum == -1
+ && ke->desc.find("The ghost of") != std::string::npos;
+}
+
+static int kill_lualc_holiness(lua_State *ls)
+{
+ if (!lua_islightuserdata(ls, 1))
+ {
+ luaL_argerror(ls, 1, "Unexpected argument type");
+ return 0;
+ }
+
+ kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) );
+ if (ke)
+ {
+ const char *verdict = "strange";
+ if (ke->monnum == -1)
+ {
+ verdict = is_ghost(ke)? "undead" : "demonic";
+ }
+ else
+ {
+ switch (mons_holiness(ke->monnum))
+ {
+ case MH_HOLY: verdict = "holy"; break;
+ case MH_NATURAL: verdict = "natural"; break;
+ case MH_UNDEAD: verdict = "undead"; break;
+ case MH_DEMONIC: verdict = "demonic"; break;
+ case MH_NONLIVING: verdict = "nonliving"; break;
+ case MH_PLANT: verdict = "plant"; break;
+ }
+ if (ke->modifier != kill_monster_desc::M_NORMAL
+ && ke->modifier != kill_monster_desc::M_SHAPESHIFTER)
+ verdict = "undead";
+ }
+ lua_pushstring(ls, verdict);
+ return 1;
+ }
+ return 0;
+}
+
+static int kill_lualc_symbol(lua_State *ls)
+{
+ if (!lua_islightuserdata(ls, 1))
+ {
+ luaL_argerror(ls, 1, "Unexpected argument type");
+ return 0;
+ }
+
+ kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) );
+ if (ke)
+ {
+ unsigned char ch = ke->monnum != -1?
+ mons_char(ke->monnum) :
+ is_ghost(ke)? 'p' : '&';
+
+ if (ke->monnum == MONS_PROGRAM_BUG)
+ ch = ' ';
+
+ switch (ke->modifier)
+ {
+ case kill_monster_desc::M_ZOMBIE:
+ case kill_monster_desc::M_SKELETON:
+ case kill_monster_desc::M_SIMULACRUM:
+ ch = mons_zombie_size(ke->monnum) == Z_SMALL? 'z' : 'Z';
+ break;
+ case kill_monster_desc::M_SPECTRE:
+ ch = 'W';
+ break;
+ default:
+ break;
+ }
+
+ char s[2];
+ s[0] = (char) ch;
+ s[1] = 0;
+ lua_pushstring(ls, s);
+ return 1;
+ }
+ return 0;
+}
+
+static int kill_lualc_rawwrite(lua_State *ls)
+{
+ const char *s = luaL_checkstring(ls, 1);
+ lua_pushstring(ls, "cr_skill");
+ lua_gettable(ls, LUA_REGISTRYINDEX);
+ if (!lua_islightuserdata(ls, -1))
+ {
+ lua_settop(ls, -2);
+ fprintf(stderr, "Can't find kill string?\n");
+ return 0;
+ }
+
+ std::string *skill = static_cast<std::string *>( lua_touserdata(ls, -1) );
+ // Pop the userdata off the stack.
+ lua_settop(ls, -2);
+
+ *skill += s;
+ *skill += "\n";
+
+ return 0;
+}
+
+static int kill_lualc_write(lua_State *ls)
+{
+ if (!lua_islightuserdata(ls, 1))
+ {
+ luaL_argerror(ls, 1, "Unexpected argument type");
+ return 0;
+ }
+
+ kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) );
+ if (ke)
+ {
+ lua_pushstring(ls, "cr_skill");
+ lua_gettable(ls, LUA_REGISTRYINDEX);
+ if (!lua_islightuserdata(ls, -1))
+ {
+ lua_settop(ls,-2);
+ fprintf(stderr, "Can't find kill string?\n");
+ return 0;
+ }
+
+ std::string *skill = static_cast<std::string *>(
+ lua_touserdata(ls, -1) );
+ // Pop the userdata off the stack.
+ lua_settop(ls, -2);
+
+ // Write kill description and a newline.
+ *skill += ke->desc + "\n";
+ }
+ return 0;
+}
+
+static int kill_lualc_summary(lua_State *ls)
+{
+ if (!lua_istable(ls, 1))
+ {
+ luaL_argerror(ls, 1, "Unexpected argument type, wanted table");
+ return 0;
+ }
+
+ unsigned long count = 0;
+ for (int i = 1; ; ++i)
+ {
+ lua_rawgeti(ls, 1, i);
+ if (lua_isnil(ls, -1))
+ {
+ lua_settop(ls, -2);
+ break;
+ }
+
+ if (!lua_islightuserdata(ls, -1))
+ {
+ luaL_argerror(ls, 1, "Unexpected argument type");
+ return 0;
+ }
+
+ kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, -1) );
+ lua_settop(ls, -2);
+ if (ke)
+ count += ke->nkills;
+ }
+ char buf[120];
+ *buf = 0;
+ if (count)
+ snprintf(buf, sizeof buf, "%lu creature%s vanquished.",
+ count, count > 1? "s" : "");
+ lua_pushstring(ls, buf);
+ return 1;
+}
+
+static const struct luaL_reg kill_lib[] =
+{
+ { "nkills", kill_lualc_nkills },
+ { "exp" , kill_lualc_exp },
+ { "base_name", kill_lualc_base_name },
+ { "desc", kill_lualc_desc },
+ { "monnum", kill_lualc_monnum },
+ { "modifier", kill_lualc_modifier },
+ { "places", kill_lualc_places },
+ { "place_name", kill_lualc_place_name },
+ { "holiness", kill_lualc_holiness },
+ { "symbol", kill_lualc_symbol },
+ { "isghost", kill_lualc_isghost },
+ { "ispandemon", kill_lualc_ispandemon },
+ { "isunique", kill_lualc_isunique },
+ { "rawwrite", kill_lualc_rawwrite },
+ { "write", kill_lualc_write },
+ { "summary", kill_lualc_summary },
+ { NULL, NULL }
+};
+
+void lua_open_kills(lua_State *ls)
+{
+ luaL_openlib(ls, "kills", kill_lib, 0);
+}
+
+static void kill_lua_filltable(std::vector<kill_exp> &v)
+{
+ lua_State *ls = clua.state();
+ lua_newtable(ls);
+ for (int i = 0, count = v.size(); i < count; ++i)
+ {
+ lua_pushlightuserdata(ls, &v[i]);
+ lua_rawseti(ls, -2, i + 1);
+ }
+}
+
+#endif