1 /**
2  * @file
3  * @brief Player kill tracking
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "kills.h"
9 
10 #include <algorithm>
11 
12 #include "clua.h"
13 #include "describe.h"
14 #include "english.h"
15 #include "files.h"
16 #include "ghost.h"
17 #include "libutil.h"
18 #include "l-libs.h"
19 #include "mon-death.h"
20 #include "mon-info.h"
21 #include "monster.h"
22 #include "options.h"
23 #include "stringutil.h"
24 #include "tags.h"
25 #include "tag-version.h"
26 #include "travel.h"
27 #include "unwind.h"
28 #include "viewchar.h"
29 
30 #define KILLS_MAJOR_VERSION 4
31 #define KILLS_MINOR_VERSION 1
32 
33 static void kill_lua_filltable(vector<kill_exp> &v);
34 
35 ///////////////////////////////////////////////////////////////////////////
36 // KillMaster
37 //
38 
39 static const char *kill_category_names[] =
40 {
41     "you",
42     "collateral kills",
43     "others",
44 };
45 
category_name(kill_category kc) const46 const char *KillMaster::category_name(kill_category kc) const
47 {
48     if (kc >= KC_YOU && kc < KC_NCATEGORIES)
49         return kill_category_names[kc];
50     return nullptr;
51 }
52 
empty() const53 bool KillMaster::empty() const
54 {
55     for (int i = 0; i < KC_NCATEGORIES; ++i)
56         if (!categorized_kills[i].empty())
57             return false;
58     return true;
59 }
60 
save(writer & outf) const61 void KillMaster::save(writer& outf) const
62 {
63     const auto version = save_version(KILLS_MAJOR_VERSION, KILLS_MINOR_VERSION);
64     write_save_version(outf, version);
65 
66     for (int i = 0; i < KC_NCATEGORIES; ++i)
67         categorized_kills[i].save(outf);
68 }
69 
load(reader & inf)70 void KillMaster::load(reader& inf)
71 {
72     const auto version = get_save_version(inf);
73     const auto major = version.major, minor = version.minor;
74 
75     if (major != KILLS_MAJOR_VERSION
76         || (minor != KILLS_MINOR_VERSION && minor > 0))
77     {
78         return;
79     }
80 
81     for (int i = 0; i < KC_NCATEGORIES; ++i)
82     {
83         categorized_kills[i].load(inf);
84         if (!minor)
85             break;
86     }
87 }
88 
record_kill(const monster * mon,int killer,bool ispet)89 void KillMaster::record_kill(const monster* mon, int killer, bool ispet)
90 {
91     const kill_category kc =
92         YOU_KILL(killer) ? KC_YOU :
93         ispet            ? KC_FRIENDLY :
94                            KC_OTHER;
95     categorized_kills[kc].record_kill(mon);
96 }
97 
total_kills() const98 int KillMaster::total_kills() const
99 {
100     int grandtotal = 0;
101     for (int i = KC_YOU; i < KC_NCATEGORIES; ++i)
102     {
103         if (categorized_kills[i].empty())
104             continue;
105 
106         vector<kill_exp> kills;
107         int count = categorized_kills[i].get_kills(kills);
108         grandtotal += count;
109     }
110     return grandtotal;
111 }
112 
kill_info() const113 string KillMaster::kill_info() const
114 {
115     if (empty())
116         return "";
117 
118     string killtext;
119 
120     bool needseparator = false;
121     int categories = 0;
122     int grandtotal = 0;
123 
124     Kills catkills[KC_NCATEGORIES];
125     for (int i = 0; i < KC_NCATEGORIES; ++i)
126     {
127         int targ = Options.kill_map[i];
128         catkills[targ].merge(categorized_kills[i]);
129     }
130 
131     for (int i = KC_YOU; i < KC_NCATEGORIES; ++i)
132     {
133         if (catkills[i].empty())
134             continue;
135 
136         categories++;
137         vector<kill_exp> kills;
138         int count = catkills[i].get_kills(kills);
139         grandtotal += count;
140 
141         add_kill_info(killtext,
142                        kills,
143                        count,
144                        i == KC_YOU ? nullptr
145                                    : category_name((kill_category) i),
146                        needseparator);
147         needseparator = true;
148     }
149 
150     string grandt;
151     if (categories > 1)
152     {
153         char buf[200];
154         snprintf(buf, sizeof buf,
155                 "Grand Total: %d creatures vanquished",
156                 grandtotal);
157         grandt = buf;
158     }
159 
160     bool custom = false;
161     unwind_var<int> lthrottle(clua.throttle_unit_lines, 500000);
162     // Call the kill dump Lua function with null a, to tell it we're done.
163     if (!clua.callfn("c_kill_list", "ss>b", nullptr, grandt.c_str(), &custom)
164         || !custom)
165     {
166         // We can sum up ourselves, if Lua doesn't want to.
167         if (categories > 1)
168         {
169             // Give ourselves a newline first
170             killtext += "\n";
171             killtext += grandt + "\n";
172         }
173     }
174 
175     return killtext;
176 }
177 
add_kill_info(string & killtext,vector<kill_exp> & kills,int count,const char * category,bool separator) const178 void KillMaster::add_kill_info(string &killtext,
179                                vector<kill_exp> &kills,
180                                int count,
181                                const char *category,
182                                bool separator) const
183 {
184     // Set a pointer to killtext as a Lua global
185     lua_pushlightuserdata(clua.state(), &killtext);
186     clua.setregistry("cr_skill");
187 
188     // Populate a Lua table with kill_exp structs, in the default order,
189     // and leave the table on the top of the Lua stack.
190     kill_lua_filltable(kills);
191 
192     if (category)
193         lua_pushstring(clua, category);
194     else
195         lua_pushnil(clua);
196 
197     lua_pushboolean(clua, separator);
198 
199     unwind_var<int> lthrottle(clua.throttle_unit_lines, 500000);
200     if (!clua.callfn("c_kill_list", 3, 1)
201         || !lua_isboolean(clua, -1) || !lua_toboolean(clua, -1))
202     {
203         if (!clua.error.empty())
204         {
205             killtext += "Lua error:\n";
206             killtext += clua.error + "\n\n";
207         }
208         if (separator)
209             killtext += "\n";
210 
211         killtext += "Vanquished Creatures";
212         if (category)
213             killtext += string(" (") + category + ")";
214 
215         killtext += "\n";
216 
217         for (const kill_exp &kill : kills)
218             killtext += "  " + kill.desc + "\n";
219 
220         killtext += make_stringf("%d creature%s vanquished.\n",
221                                  count, count == 1 ? "" : "s");
222     }
223     lua_pop(clua, 1);
224 }
225 
num_kills(const monster * mon,kill_category cat) const226 int KillMaster::num_kills(const monster* mon, kill_category cat) const
227 {
228     return categorized_kills[cat].num_kills(mon);
229 }
230 
num_kills(const monster_info & mon,kill_category cat) const231 int KillMaster::num_kills(const monster_info& mon, kill_category cat) const
232 {
233     return categorized_kills[cat].num_kills(mon);
234 }
235 
num_kills(const monster * mon) const236 int KillMaster::num_kills(const monster* mon) const
237 {
238     int total = 0;
239     for (int cat = 0; cat < KC_NCATEGORIES; cat++)
240         total += categorized_kills[cat].num_kills(mon);
241 
242     return total;
243 }
244 
num_kills(const monster_info & mon) const245 int KillMaster::num_kills(const monster_info& mon) const
246 {
247     int total = 0;
248     for (int cat = 0; cat < KC_NCATEGORIES; cat++)
249         total += categorized_kills[cat].num_kills(mon);
250 
251     return total;
252 }
253 
254 ///////////////////////////////////////////////////////////////////////////
255 
empty() const256 bool Kills::empty() const
257 {
258     return kills.empty() && ghosts.empty();
259 }
260 
merge(const Kills & k)261 void Kills::merge(const Kills &k)
262 {
263     ghosts.insert(ghosts.end(), k.ghosts.begin(), k.ghosts.end());
264 
265     // Regular kills are messier to merge.
266     for (const auto &entry : k.kills)
267     {
268         const kill_monster_desc &kmd = entry.first;
269         kill_def &ki = kills[kmd];
270         const kill_def &ko = entry.second;
271         bool uniq = mons_is_unique(kmd.monnum);
272         ki.merge(ko, uniq);
273     }
274 }
275 
record_kill(const monster * mon)276 void Kills::record_kill(const monster* mon)
277 {
278     // Handle player ghosts separately, but don't handle summoned
279     // ghosts at all. {due}
280     if (mon->type == MONS_PLAYER_GHOST && !mon->is_summoned())
281     {
282         record_ghost_kill(mon);
283         return ;
284     }
285 
286     // Normal monsters
287     // Create a descriptor
288     kill_monster_desc descriptor = mon;
289 
290     kill_def &k = kills[descriptor];
291     if (k.kills)
292         k.add_kill(mon, level_id::current());
293     else
294         k = kill_def(mon);
295 }
296 
get_kills(vector<kill_exp> & all_kills) const297 int Kills::get_kills(vector<kill_exp> &all_kills) const
298 {
299     int count = 0;
300     for (const auto &entry : kills)
301     {
302         const kill_monster_desc &md = entry.first;
303         const kill_def &k = entry.second;
304         all_kills.emplace_back(k, md);
305         count += k.kills;
306     }
307 
308     for (const kill_ghost &gh : ghosts)
309         all_kills.emplace_back(gh);
310     count += ghosts.size();
311 
312     sort(all_kills.begin(), all_kills.end());
313     return count;
314 }
315 
save(writer & outf) const316 void Kills::save(writer& outf) const
317 {
318     // How many kill records do we have?
319     marshallInt(outf, kills.size());
320 
321     for (const auto &entry : kills)
322     {
323         entry.first.save(outf);
324         entry.second.save(outf);
325     }
326 
327     // How many ghosts do we have?
328     marshallShort(outf, ghosts.size());
329     for (const auto &ghost : ghosts)
330         ghost.save(outf);
331 }
332 
load(reader & inf)333 void Kills::load(reader& inf)
334 {
335     // How many kill records?
336     int kill_count = unmarshallInt(inf);
337     kills.clear();
338     for (int i = 0; i < kill_count; ++i)
339     {
340         kill_monster_desc md;
341         md.load(inf);
342         kills[md].load(inf);
343     }
344 
345     short ghost_count = unmarshallShort(inf);
346     ghosts.clear();
347     for (short i = 0; i < ghost_count; ++i)
348     {
349         kill_ghost kg;
350         kg.load(inf);
351         ghosts.push_back(kg);
352     }
353 }
354 
record_ghost_kill(const monster * mon)355 void Kills::record_ghost_kill(const monster* mon)
356 {
357     // We should never get to this point, but just in case... {due}
358     if (mon->is_summoned())
359         return;
360     ghosts.emplace_back(mon);
361 }
362 
num_kills(const monster * mon) const363 int Kills::num_kills(const monster* mon) const
364 {
365     kill_monster_desc desc(mon);
366     return num_kills(desc);
367 }
368 
num_kills(const monster_info & mon) const369 int Kills::num_kills(const monster_info& mon) const
370 {
371     kill_monster_desc desc(mon);
372     return num_kills(desc);
373 }
374 
num_kills(kill_monster_desc desc) const375 int Kills::num_kills(kill_monster_desc desc) const
376 {
377     auto iter = kills.find(desc);
378     int total = (iter == kills.end() ? 0 : iter->second.kills);
379 
380     if (desc.modifier == kill_monster_desc::M_SHAPESHIFTER)
381     {
382         desc.modifier = kill_monster_desc::M_NORMAL;
383         iter = kills.find(desc);
384         total += (iter == kills.end() ? 0 : iter->second.kills);
385     }
386 
387     return total;
388 }
389 
kill_def(const monster * mon)390 kill_def::kill_def(const monster* mon) : kills(0), exp(0)
391 {
392     exp = exper_value(*mon);
393     add_kill(mon, level_id::current());
394 }
395 
396 // For a non-unique monster, prefixes a suitable article if we have only one
397 // kill, else prefixes a kill count and pluralises the monster name.
n_names(const string & name,int n)398 static string n_names(const string &name, int n)
399 {
400     if (n > 1)
401     {
402         char buf[20];
403         snprintf(buf, sizeof buf, "%d ", n);
404         return buf + pluralise_monster(name);
405     }
406     else
407         return article_a(name, false);
408 }
409 
410 // Returns a string describing the number of times a unique has been killed.
411 // Currently required only for Boris.
412 //
kill_times(int kills)413 static string kill_times(int kills)
414 {
415     char buf[50];
416     switch (kills)
417     {
418     case 1:
419         strcpy(buf, " (once)");
420         break;
421     case 2:
422         strcpy(buf, " (twice)");
423         break;
424     case 3:
425         strcpy(buf, " (thrice)");
426         break;
427     default:
428         snprintf(buf, sizeof buf, " (%d times)", kills);
429         break;
430     }
431     return string(buf);
432 }
433 
merge(const kill_def & k,bool uniq)434 void kill_def::merge(const kill_def &k, bool uniq)
435 {
436     if (!kills)
437         *this = k;
438     else
439     {
440         kills += k.kills;
441         for (level_id lvl : k.places)
442             add_place(lvl, uniq);
443     }
444 }
445 
add_kill(const monster * mon,level_id place)446 void kill_def::add_kill(const monster* mon, level_id place)
447 {
448     kills++;
449     // They're only unique if they aren't summoned.
450     add_place(place, mons_is_unique(mon->type) && !mon->is_summoned());
451 }
452 
add_place(level_id place,bool force)453 void kill_def::add_place(level_id place, bool force)
454 {
455     if (find(begin(places), end(places), place) != end(places))
456         return; // Already there
457 
458     if (force || places.size() < PLACE_LIMIT)
459         places.push_back(place);
460 }
461 
base_name(const kill_monster_desc & md) const462 string kill_def::base_name(const kill_monster_desc &md) const
463 {
464     string name;
465     if (md.monnum == MONS_PANDEMONIUM_LORD)
466         name = "pandemonium lord";
467     else
468         name = mons_type_name(md.monnum, DESC_PLAIN);
469 
470     switch (md.modifier)
471     {
472     case kill_monster_desc::M_ZOMBIE:
473         name += " zombie";
474         break;
475     case kill_monster_desc::M_SKELETON:
476         name += " skeleton";
477         break;
478     case kill_monster_desc::M_SIMULACRUM:
479         name += " simulacrum";
480         break;
481     case kill_monster_desc::M_SPECTRE:
482         name = "spectral " + name;
483         break;
484     default:
485         // Silence compiler warning about not handling M_NORMAL and
486         // M_SHAPESHIFTER
487         break;
488     }
489 
490     return name;
491 }
492 
info(const kill_monster_desc & md) const493 string kill_def::info(const kill_monster_desc &md) const
494 {
495     string name = base_name(md);
496 
497     if (!mons_is_unique(md.monnum))
498     {
499         // Pluralise as needed.
500         name = n_names(name, kills);
501 
502         // We brand shapeshifters with the (shapeshifter) qualifier.
503         // This has to be done after doing pluralise(), else we get very
504         // odd plurals :)
505         if (md.modifier == kill_monster_desc::M_SHAPESHIFTER
506             && md.monnum != MONS_SHAPESHIFTER
507             && md.monnum != MONS_GLOWING_SHAPESHIFTER)
508         {
509             name += " (shapeshifter)";
510         }
511     }
512     else if (kills > 1)
513     {
514         // Aha! A resurrected unique
515         name += kill_times(kills);
516     }
517 
518     // What places we killed this type of monster
519     return append_places(md, name);
520 }
521 
append_places(const kill_monster_desc & md,const string & name) const522 string kill_def::append_places(const kill_monster_desc &md,
523                                const string &name) const
524 {
525     if (Options.dump_kill_places == KDO_NO_PLACES) return name;
526 
527     size_t nplaces = places.size();
528     if (nplaces == 1 || mons_is_unique(md.monnum)
529             || Options.dump_kill_places == KDO_ALL_PLACES)
530     {
531         string augmented = name;
532         augmented += " (";
533         for (auto iter = places.begin(); iter != places.end(); ++iter)
534         {
535             if (iter != places.begin())
536                 augmented += " ";
537             augmented += iter->describe();
538         }
539         augmented += ")";
540         return augmented;
541     }
542     return name;
543 }
544 
save(writer & outf) const545 void kill_def::save(writer& outf) const
546 {
547     marshallShort(outf, kills);
548     marshallShort(outf, exp);
549 
550     marshallShort(outf, places.size());
551     for (auto lvl : places)
552         lvl.save(outf);
553 }
554 
load(reader & inf)555 void kill_def::load(reader& inf)
556 {
557     kills = (unsigned short) unmarshallShort(inf);
558     exp   = unmarshallShort(inf);
559 
560     places.clear();
561     short place_count = unmarshallShort(inf);
562     for (short i = 0; i < place_count; ++i)
563     {
564 #if TAG_MAJOR_VERSION == 34
565         if (inf.getMinorVersion() < TAG_MINOR_PLACE_UNPACK)
566         {
567             places.push_back(level_id::from_packed_place(
568                                 (unsigned short) unmarshallShort(inf)));
569         }
570         else
571         {
572 #endif
573         level_id tmp;
574         tmp.load(inf);
575         places.push_back(tmp);
576 #if TAG_MAJOR_VERSION == 34
577         }
578 #endif
579     }
580 }
581 
kill_ghost(const monster * mon)582 kill_ghost::kill_ghost(const monster* mon)
583 {
584     exp = exper_value(*mon);
585     place = level_id::current();
586     ghost_name = mon->ghost->name;
587 
588     // Check whether this is really a ghost, since we also have to handle
589     // the Pandemonic demons.
590     if (mon->type == MONS_PLAYER_GHOST && !mon->is_summoned())
591     {
592         monster_info mi(mon);
593         ghost_name = "The ghost of " + get_ghost_description(mi, true);
594     }
595 }
596 
info() const597 string kill_ghost::info() const
598 {
599     return ghost_name
600            + (Options.dump_kill_places != KDO_NO_PLACES ?
601                 " (" + place.describe() + ")" :
602                 string(""));
603 }
604 
save(writer & outf) const605 void kill_ghost::save(writer& outf) const
606 {
607     marshallString4(outf, ghost_name);
608     marshallShort(outf, (unsigned short) exp);
609     place.save(outf);
610 }
611 
load(reader & inf)612 void kill_ghost::load(reader& inf)
613 {
614     unmarshallString4(inf, ghost_name);
615     exp = unmarshallShort(inf);
616 #if TAG_MAJOR_VERSION == 34
617     if (inf.getMinorVersion() < TAG_MINOR_PLACE_UNPACK)
618         place = level_id::from_packed_place((unsigned short) unmarshallShort(inf));
619     else
620 #endif
621     place.load(inf);
622 }
623 
kill_monster_desc(const monster * mon)624 kill_monster_desc::kill_monster_desc(const monster* mon)
625 {
626     monnum = mon->type;
627     modifier = M_NORMAL;
628     switch (mon->type)
629     {
630         case MONS_ZOMBIE:
631 #if TAG_MAJOR_VERSION == 34
632         case MONS_ZOMBIE_LARGE: case MONS_ZOMBIE_SMALL:
633 #endif
634             modifier = M_ZOMBIE;
635             break;
636         case MONS_SKELETON:
637 #if TAG_MAJOR_VERSION == 34
638         case MONS_SKELETON_LARGE: case MONS_SKELETON_SMALL:
639 #endif
640             modifier = M_SKELETON;
641             break;
642         case MONS_SIMULACRUM:
643 #if TAG_MAJOR_VERSION == 34
644         case MONS_SIMULACRUM_LARGE: case MONS_SIMULACRUM_SMALL:
645 #endif
646             modifier = M_SIMULACRUM;
647             break;
648         case MONS_SPECTRAL_THING:
649             modifier = M_SPECTRE;
650             break;
651         default: break;
652     }
653     if (modifier != M_NORMAL)
654         monnum = mons_species(mon->base_monster);
655 
656     if (mon->is_shapeshifter())
657         modifier = M_SHAPESHIFTER;
658 }
659 
660 // TODO: de-duplicate with the monster * version?
kill_monster_desc(const monster_info & mon)661 kill_monster_desc::kill_monster_desc(const monster_info& mon)
662 {
663     monnum = mon.type;
664     modifier = M_NORMAL;
665     switch (mon.type)
666     {
667         case MONS_ZOMBIE:
668 #if TAG_MAJOR_VERSION == 34
669         case MONS_ZOMBIE_LARGE: case MONS_ZOMBIE_SMALL:
670 #endif
671             modifier = M_ZOMBIE;
672             break;
673         case MONS_SKELETON:
674 #if TAG_MAJOR_VERSION == 34
675         case MONS_SKELETON_LARGE: case MONS_SKELETON_SMALL:
676 #endif
677             modifier = M_SKELETON;
678             break;
679         case MONS_SIMULACRUM:
680 #if TAG_MAJOR_VERSION == 34
681         case MONS_SIMULACRUM_LARGE: case MONS_SIMULACRUM_SMALL:
682 #endif
683             modifier = M_SIMULACRUM;
684             break;
685         case MONS_SPECTRAL_THING:
686             modifier = M_SPECTRE;
687             break;
688         default: break;
689     }
690     if (modifier != M_NORMAL)
691         monnum = mon.base_type;
692 
693     if (mon.is(MB_SHAPESHIFTER))
694         modifier = M_SHAPESHIFTER;
695 }
696 
save(writer & outf) const697 void kill_monster_desc::save(writer& outf) const
698 {
699     marshallShort(outf, (short) monnum);
700     marshallShort(outf, (short) modifier);
701 }
702 
load(reader & inf)703 void kill_monster_desc::load(reader& inf)
704 {
705     monnum = static_cast<monster_type>(unmarshallShort(inf));
706     modifier = (name_modifier) unmarshallShort(inf);
707 }
708 
709 ///////////////////////////////////////////////////////////////////////////
710 // Kill Lua interface
711 //
712 
713 #define KILLEXP_ACCESS(name, type, field) \
714     static int kill_lualc_##name(lua_State *ls) \
715     { \
716         if (!lua_islightuserdata(ls, 1)) \
717         { \
718             luaL_argerror(ls, 1, "Unexpected argument type"); \
719             return 0; \
720         } \
721           \
722         kill_exp *ke = static_cast<kill_exp*>(lua_touserdata(ls, 1)); \
723         if (ke) \
724         { \
725             lua_push##type(ls, ke->field); \
726             return 1; \
727         } \
728         return 0; \
729     }
730 
KILLEXP_ACCESS(nkills,number,nkills)731 KILLEXP_ACCESS(nkills, number, nkills)
732 KILLEXP_ACCESS(exp, number, exp)
733 KILLEXP_ACCESS(base_name, string, base_name.c_str())
734 KILLEXP_ACCESS(desc, string, desc.c_str())
735 KILLEXP_ACCESS(monnum, number, monnum)
736 KILLEXP_ACCESS(isghost, boolean, monnum == MONS_PLAYER_GHOST)
737 KILLEXP_ACCESS(ispandemon, boolean, monnum == MONS_PANDEMONIUM_LORD)
738 
739 static int kill_lualc_modifier(lua_State *ls)
740 {
741     if (!lua_islightuserdata(ls, 1))
742     {
743         luaL_argerror(ls, 1, "Unexpected argument type");
744         return 0;
745     }
746 
747     kill_exp *ke = static_cast<kill_exp*>(lua_touserdata(ls, 1));
748     if (ke)
749     {
750         const char *modifier;
751         switch (ke->modifier)
752         {
753         case kill_monster_desc::M_ZOMBIE:
754             modifier = "zombie";
755             break;
756         case kill_monster_desc::M_SKELETON:
757             modifier = "skeleton";
758             break;
759         case kill_monster_desc::M_SIMULACRUM:
760             modifier = "simulacrum";
761             break;
762         case kill_monster_desc::M_SPECTRE:
763             modifier = "spectre";
764             break;
765         case kill_monster_desc::M_SHAPESHIFTER:
766             modifier = "shapeshifter";
767             break;
768         default:
769             modifier = "";
770             break;
771         }
772         lua_pushstring(ls, modifier);
773         return 1;
774     }
775     return 0;
776 }
777 
kill_lualc_isunique(lua_State * ls)778 static int kill_lualc_isunique(lua_State *ls)
779 {
780     if (!lua_islightuserdata(ls, 1))
781     {
782         luaL_argerror(ls, 1, "Unexpected argument type");
783         return 0;
784     }
785 
786     kill_exp *ke = static_cast<kill_exp*>(lua_touserdata(ls, 1));
787     if (ke)
788     {
789         lua_pushboolean(ls, mons_is_unique(ke->monnum));
790         return 1;
791     }
792     return 0;
793 }
794 
kill_lualc_holiness(lua_State * ls)795 static int kill_lualc_holiness(lua_State *ls)
796 {
797     if (!lua_islightuserdata(ls, 1))
798     {
799         luaL_argerror(ls, 1, "Unexpected argument type");
800         return 0;
801     }
802 
803     kill_exp *ke = static_cast<kill_exp*>(lua_touserdata(ls, 1));
804     if (ke)
805     {
806         const char *verdict = "strange";
807         mon_holy_type holi = mons_class_holiness(ke->monnum);
808         if (holi & MH_HOLY)
809             verdict = "holy";
810         else if (holi & MH_NATURAL)
811             verdict = "natural";
812         else if (holi & MH_UNDEAD)
813             verdict = "undead";
814         else if (holi & MH_DEMONIC)
815             verdict = "demonic";
816         else if (holi & MH_NONLIVING)
817             verdict = "nonliving";
818         else if (holi & MH_PLANT)
819             verdict = "plant";
820         if (ke->modifier != kill_monster_desc::M_NORMAL
821             && ke->modifier != kill_monster_desc::M_SHAPESHIFTER)
822         {
823             verdict = "undead";
824         }
825         lua_pushstring(ls, verdict);
826         return 1;
827     }
828     return 0;
829 }
830 
kill_lualc_symbol(lua_State * ls)831 static int kill_lualc_symbol(lua_State *ls)
832 {
833     if (!lua_islightuserdata(ls, 1))
834     {
835         luaL_argerror(ls, 1, "Unexpected argument type");
836         return 0;
837     }
838 
839     kill_exp *ke = static_cast<kill_exp*>(lua_touserdata(ls, 1));
840     if (ke)
841     {
842         char32_t ch = mons_char(ke->monnum);
843 
844         if (ke->monnum == MONS_PROGRAM_BUG)
845             ch = ' ';
846 
847         switch (ke->modifier)
848         {
849         case kill_monster_desc::M_ZOMBIE:
850         case kill_monster_desc::M_SKELETON:
851             ch = mons_char(mons_zombie_size(ke->monnum) == Z_SMALL ?
852                            MONS_ZOMBIE_SMALL : MONS_ZOMBIE_LARGE);
853             break;
854         case kill_monster_desc::M_SIMULACRUM:
855             ch = mons_char(mons_zombie_size(ke->monnum) == Z_SMALL ?
856                            MONS_SIMULACRUM_SMALL : MONS_SIMULACRUM_LARGE);
857             break;
858         case kill_monster_desc::M_SPECTRE:
859             ch = mons_char(MONS_SPECTRAL_THING);
860             break;
861         default:
862             break;
863         }
864 
865         lua_pushstring(ls, stringize_glyph(ch).c_str());
866         return 1;
867     }
868     return 0;
869 }
870 
kill_lualc_rawwrite(lua_State * ls)871 static int kill_lualc_rawwrite(lua_State *ls)
872 {
873     const char *s = luaL_checkstring(ls, 1);
874     lua_pushstring(ls, "cr_skill");
875     lua_gettable(ls, LUA_REGISTRYINDEX);
876     if (!lua_islightuserdata(ls, -1))
877     {
878         lua_settop(ls, -2);
879         fprintf(stderr, "Can't find kill string?\n");
880         return 0;
881     }
882 
883     string *skill = static_cast<string *>(lua_touserdata(ls, -1));
884     // Pop the userdata off the stack.
885     lua_settop(ls, -2);
886 
887     *skill += s;
888     *skill += "\n";
889 
890     return 0;
891 }
892 
kill_lualc_write(lua_State * ls)893 static int kill_lualc_write(lua_State *ls)
894 {
895     if (!lua_islightuserdata(ls, 1))
896     {
897         luaL_argerror(ls, 1, "Unexpected argument type");
898         return 0;
899     }
900 
901     kill_exp *ke = static_cast<kill_exp*>(lua_touserdata(ls, 1));
902     if (ke)
903     {
904         lua_pushstring(ls, "cr_skill");
905         lua_gettable(ls, LUA_REGISTRYINDEX);
906         if (!lua_islightuserdata(ls, -1))
907         {
908             lua_settop(ls,-2);
909             fprintf(stderr, "Can't find kill string?\n");
910             return 0;
911         }
912 
913         string *skill = static_cast<string *>(lua_touserdata(ls, -1));
914         // Pop the userdata off the stack.
915         lua_settop(ls, -2);
916 
917         // Write kill description and a newline.
918         *skill += ke->desc + "\n";
919     }
920     return 0;
921 }
922 
kill_lualc_summary(lua_State * ls)923 static int kill_lualc_summary(lua_State *ls)
924 {
925     if (!lua_istable(ls, 1))
926     {
927         luaL_argerror(ls, 1, "Unexpected argument type, wanted table");
928         return 0;
929     }
930 
931     unsigned int count = 0;
932     for (int i = 1; ; ++i)
933     {
934         lua_rawgeti(ls, 1, i);
935         if (lua_isnil(ls, -1))
936         {
937             lua_settop(ls, -2);
938             break;
939         }
940 
941         if (!lua_islightuserdata(ls, -1))
942         {
943             luaL_argerror(ls, 1, "Unexpected argument type");
944             return 0;
945         }
946 
947         kill_exp *ke = static_cast<kill_exp*>(lua_touserdata(ls, -1));
948         lua_settop(ls, -2);
949         if (ke)
950             count += ke->nkills;
951     }
952     char buf[120];
953     *buf = 0;
954     if (count)
955     {
956         snprintf(buf, sizeof buf, "%u creature%s vanquished.",
957                 count, count > 1? "s" : "");
958     }
959     lua_pushstring(ls, buf);
960     return 1;
961 }
962 
963 static const struct luaL_reg kill_lib[] =
964 {
965     { "nkills",     kill_lualc_nkills },
966     { "exp"   ,     kill_lualc_exp },
967     { "base_name",  kill_lualc_base_name },
968     { "desc",       kill_lualc_desc },
969     { "monnum",     kill_lualc_monnum },
970     { "modifier",   kill_lualc_modifier },
971     { "holiness",   kill_lualc_holiness },
972     { "symbol",     kill_lualc_symbol },
973     { "isghost",    kill_lualc_isghost },
974     { "ispandemon", kill_lualc_ispandemon },
975     { "isunique",   kill_lualc_isunique },
976     { "rawwrite",   kill_lualc_rawwrite },
977     { "write",      kill_lualc_write },
978     { "summary",    kill_lualc_summary },
979     { nullptr, nullptr }
980 };
981 
cluaopen_kills(lua_State * ls)982 void cluaopen_kills(lua_State *ls)
983 {
984     luaL_openlib(ls, "kills", kill_lib, 0);
985 }
986 
kill_lua_filltable(vector<kill_exp> & v)987 static void kill_lua_filltable(vector<kill_exp> &v)
988 {
989     lua_State *ls = clua.state();
990     lua_newtable(ls);
991     for (int i = 0, count = v.size(); i < count; ++i)
992     {
993         lua_pushlightuserdata(ls, &v[i]);
994         lua_rawseti(ls, -2, i + 1);
995     }
996 }
997