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