1 /**
2  * @file
3  * @brief Support code for Crawl des files.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "mapdef.h"
9 
10 #include <algorithm>
11 #include <cctype>
12 #include <cstdarg>
13 #include <cstdio>
14 #include <cstdlib>
15 #include <iostream>
16 
17 #include "abyss.h"
18 #include "artefact.h"
19 #include "branch.h"
20 #include "cluautil.h"
21 #include "colour.h"
22 #include "coordit.h"
23 #include "describe.h"
24 #include "dgn-height.h"
25 #include "dungeon.h"
26 #include "end.h"
27 #include "mpr.h"
28 #include "tile-env.h"
29 #include "english.h"
30 #include "files.h"
31 #include "initfile.h"
32 #include "item-prop.h"
33 #include "item-status-flag-type.h"
34 #include "invent.h"
35 #include "libutil.h"
36 #include "mapmark.h"
37 #include "maps.h"
38 #include "mon-cast.h"
39 #include "mon-place.h"
40 #include "mutant-beast.h"
41 #include "place.h"
42 #include "random.h"
43 #include "religion.h"
44 #include "shopping.h"
45 #include "spl-book.h"
46 #include "spl-util.h"
47 #include "stringutil.h"
48 #include "tag-version.h"
49 #include "terrain.h"
50 #include "rltiles/tiledef-dngn.h"
51 #include "rltiles/tiledef-player.h"
52 
53 #ifdef DEBUG_TAG_PROFILING
54 static map<string,int> _tag_profile;
55 
_profile_inc_tag(const string & tag)56 static void _profile_inc_tag(const string &tag)
57 {
58     if (!_tag_profile.count(tag))
59         _tag_profile[tag] = 0;
60     _tag_profile[tag]++;
61 }
62 
tag_profile_out()63 void tag_profile_out()
64 {
65     long total = 0;
66     vector<pair<int, string>> resort;
67     fprintf(stderr, "\nTag hits:\n");
68     for (auto k : _tag_profile)
69     {
70         resort.emplace_back(k.second, k.first);
71         total += k.second;
72     }
73     sort(resort.begin(), resort.end());
74     for (auto p : resort)
75     {
76         long percent = ((long) p.first) * 100 / total;
77         fprintf(stderr, "%8d (%2ld%%): %s\n", p.first, percent, p.second.c_str());
78     }
79     fprintf(stderr, "Total: %ld\n", total);
80 }
81 #endif
82 
83 static const char *map_section_names[] =
84 {
85     "",
86     "north",
87     "south",
88     "east",
89     "west",
90     "northwest",
91     "northeast",
92     "southwest",
93     "southeast",
94     "encompass",
95     "float",
96     "centre",
97 };
98 
99 static string_set Map_Flag_Names;
100 
101 const char *traversable_glyphs =
102     ".+=w@{}()[]<>BC^TUVY$%*|Odefghijk0123456789";
103 
104 // atoi that rejects strings containing non-numeric trailing characters.
105 // returns defval for invalid input.
106 template <typename V>
strict_aton(const char * s,V defval=0)107 static V strict_aton(const char *s, V defval = 0)
108 {
109     char *end;
110     const V res = strtol(s, &end, 10);
111     return (!*s || *end) ? defval : res;
112 }
113 
map_section_name(int msect)114 const char *map_section_name(int msect)
115 {
116     if (msect < 0 || msect >= MAP_NUM_SECTION_TYPES)
117         return "";
118 
119     return map_section_names[msect];
120 }
121 
find_weight(string & s,int defweight=TAG_UNFOUND)122 static int find_weight(string &s, int defweight = TAG_UNFOUND)
123 {
124     int weight = strip_number_tag(s, "weight:");
125     if (weight == TAG_UNFOUND)
126         weight = strip_number_tag(s, "w:");
127     return weight == TAG_UNFOUND ? defweight : weight;
128 }
129 
clear_subvault_stack()130 void clear_subvault_stack()
131 {
132     env.new_subvault_names.clear();
133     env.new_subvault_tags.clear();
134     env.new_used_subvault_names.clear();
135     env.new_used_subvault_tags.clear();
136 }
137 
map_register_flag(const string & flag)138 void map_register_flag(const string &flag)
139 {
140     Map_Flag_Names.insert(flag);
141 }
142 
_map_tag_is_selectable(const string & tag)143 static bool _map_tag_is_selectable(const string &tag)
144 {
145     return !Map_Flag_Names.count(tag)
146            && tag.find("luniq_") != 0
147            && tag.find("uniq_") != 0
148            && tag.find("ruin_") != 0
149            && tag.find("chance_") != 0;
150 }
151 
mapdef_split_key_item(const string & s,string * key,int * separator,string * arg,int key_max_len)152 string mapdef_split_key_item(const string &s, string *key, int *separator,
153                              string *arg, int key_max_len)
154 {
155     string::size_type
156         norm = s.find("=", 1),
157         fixe = s.find(":", 1);
158 
159     const string::size_type sep = norm < fixe? norm : fixe;
160     if (sep == string::npos)
161     {
162         return make_stringf("malformed declaration - must use = or : in '%s'",
163                             s.c_str());
164     }
165 
166     *key = trimmed_string(s.substr(0, sep));
167     string substitute = trimmed_string(s.substr(sep + 1));
168 
169     if (key->empty()
170         || (key_max_len != -1 && (int) key->length() > key_max_len))
171     {
172         return make_stringf(
173             "selector '%s' must be <= %d characters in '%s'",
174             key->c_str(), key_max_len, s.c_str());
175     }
176 
177     if (substitute.empty())
178     {
179         return make_stringf("no substitute defined in '%s'",
180                             s.c_str());
181     }
182 
183     *arg = substitute;
184     *separator = s[sep];
185 
186     return "";
187 }
188 
store_tilename_get_index(const string & tilename)189 int store_tilename_get_index(const string& tilename)
190 {
191     if (tilename.empty())
192         return 0;
193 
194     // Increase index by 1 to distinguish between first entry and none.
195     unsigned int i;
196     for (i = 0; i < tile_env.names.size(); ++i)
197         if (tilename == tile_env.names[i])
198             return i+1;
199 
200 #ifdef DEBUG_TILE_NAMES
201     mprf("adding %s on index %d (%d)", tilename.c_str(), i, i+1);
202 #endif
203     // If not found, add tile name to vector.
204     tile_env.names.push_back(tilename);
205     return i+1;
206 }
207 
208 ///////////////////////////////////////////////
209 // subvault_place
210 //
211 
subvault_place()212 subvault_place::subvault_place()
213     : tl(), br(), subvault()
214 {
215 }
216 
subvault_place(const coord_def & _tl,const coord_def & _br,const map_def & _subvault)217 subvault_place::subvault_place(const coord_def &_tl,
218                                const coord_def &_br,
219                                const map_def &_subvault)
220     : tl(_tl), br(_br), subvault(new map_def(_subvault))
221 {
222 }
223 
subvault_place(const subvault_place & place)224 subvault_place::subvault_place(const subvault_place &place)
225     : tl(place.tl), br(place.br),
226       subvault(place.subvault ? new map_def(*place.subvault) : nullptr)
227 {
228 }
229 
operator =(const subvault_place & place)230 subvault_place &subvault_place::operator = (const subvault_place &place)
231 {
232     tl = place.tl;
233     br = place.br;
234     subvault.reset(place.subvault ? new map_def(*place.subvault)
235                                         : nullptr);
236     return *this;
237 }
238 
set_subvault(const map_def & _subvault)239 void subvault_place::set_subvault(const map_def &_subvault)
240 {
241     subvault.reset(new map_def(_subvault));
242 }
243 
244 ///////////////////////////////////////////////
245 // level_range
246 //
247 
level_range(branch_type br,int s,int d)248 level_range::level_range(branch_type br, int s, int d)
249     : branch(br), shallowest(), deepest(), deny(false)
250 {
251     set(s, d);
252 }
253 
level_range(const raw_range & r)254 level_range::level_range(const raw_range &r)
255     : branch(r.branch), shallowest(r.shallowest), deepest(r.deepest),
256       deny(r.deny)
257 {
258 }
259 
write(writer & outf) const260 void level_range::write(writer& outf) const
261 {
262     marshallShort(outf, branch);
263     marshallShort(outf, shallowest);
264     marshallShort(outf, deepest);
265     marshallBoolean(outf, deny);
266 }
267 
read(reader & inf)268 void level_range::read(reader& inf)
269 {
270     branch     = static_cast<branch_type>(unmarshallShort(inf));
271     shallowest = unmarshallShort(inf);
272     deepest    = unmarshallShort(inf);
273     deny       = unmarshallBoolean(inf);
274 }
275 
str_depth_range() const276 string level_range::str_depth_range() const
277 {
278     if (shallowest == -1)
279         return ":??";
280 
281     if (shallowest == BRANCH_END)
282         return ":$";
283 
284     if (deepest == BRANCH_END)
285         return shallowest == 1? "" : make_stringf("%d-", shallowest);
286 
287     if (shallowest == deepest)
288         return make_stringf(":%d", shallowest);
289 
290     return make_stringf(":%d-%d", shallowest, deepest);
291 }
292 
describe() const293 string level_range::describe() const
294 {
295     return make_stringf("%s%s%s",
296                         deny? "!" : "",
297                         branch == NUM_BRANCHES ? "Any" :
298                         branches[branch].abbrevname,
299                         str_depth_range().c_str());
300 }
301 
operator raw_range() const302 level_range::operator raw_range () const
303 {
304     raw_range r;
305     r.branch     = branch;
306     r.shallowest = shallowest;
307     r.deepest    = deepest;
308     r.deny       = deny;
309     return r;
310 }
311 
set(const string & br,int s,int d)312 void level_range::set(const string &br, int s, int d)
313 {
314     if (br == "any" || br == "Any")
315         branch = NUM_BRANCHES;
316     else if ((branch = branch_by_abbrevname(br)) == NUM_BRANCHES)
317         throw bad_level_id_f("Unknown branch: '%s'", br.c_str());
318 
319     shallowest = s;
320     deepest    = d;
321 
322     if (deepest < shallowest || deepest <= 0)
323     {
324         throw bad_level_id_f("Level-range %s:%d-%d is malformed",
325                              br.c_str(), s, d);
326     }
327 }
328 
parse(string s)329 level_range level_range::parse(string s)
330 {
331     level_range lr;
332     trim_string(s);
333 
334     if (s == "*")
335     {
336         lr.set("any", 0, BRANCH_END);
337         return lr;
338     }
339 
340     if (s[0] == '!')
341     {
342         lr.deny = true;
343         s = trimmed_string(s.substr(1));
344     }
345 
346     string::size_type cpos = s.find(':');
347     if (cpos == string::npos)
348         parse_partial(lr, s);
349     else
350     {
351         string br    = trimmed_string(s.substr(0, cpos));
352         string depth = trimmed_string(s.substr(cpos + 1));
353         parse_depth_range(depth, &lr.shallowest, &lr.deepest);
354 
355         lr.set(br, lr.shallowest, lr.deepest);
356     }
357 
358     return lr;
359 }
360 
parse_partial(level_range & lr,const string & s)361 void level_range::parse_partial(level_range &lr, const string &s)
362 {
363     if (isadigit(s[0]))
364     {
365         lr.branch = NUM_BRANCHES;
366         parse_depth_range(s, &lr.shallowest, &lr.deepest);
367     }
368     else
369         lr.set(s, 1, BRANCH_END);
370 }
371 
parse_depth_range(const string & s,int * l,int * h)372 void level_range::parse_depth_range(const string &s, int *l, int *h)
373 {
374     if (s == "*")
375     {
376         *l = 1;
377         *h = BRANCH_END;
378         return;
379     }
380 
381     if (s == "$")
382     {
383         *l = BRANCH_END;
384         *h = BRANCH_END;
385         return;
386     }
387 
388     string::size_type hy = s.find('-');
389     if (hy == string::npos)
390     {
391         *l = *h = strict_aton<int>(s.c_str());
392         if (!*l)
393             throw bad_level_id("Bad depth: " + s);
394     }
395     else
396     {
397         *l = strict_aton<int>(s.substr(0, hy).c_str());
398 
399         string tail = s.substr(hy + 1);
400         if (tail.empty() || tail == "$")
401             *h = BRANCH_END;
402         else
403             *h = strict_aton<int>(tail.c_str());
404 
405         if (!*l || !*h || *l > *h)
406             throw bad_level_id("Bad depth: " + s);
407     }
408 }
409 
set(int s,int d)410 void level_range::set(int s, int d)
411 {
412     shallowest = s;
413     deepest    = d;
414 
415     if (deepest == -1)
416         deepest = shallowest;
417 
418     if (deepest < shallowest)
419         throw bad_level_id_f("Bad depth range: %d-%d", shallowest, deepest);
420 }
421 
reset()422 void level_range::reset()
423 {
424     deepest = shallowest = -1;
425     branch  = NUM_BRANCHES;
426 }
427 
matches(const level_id & lid) const428 bool level_range::matches(const level_id &lid) const
429 {
430     if (branch == NUM_BRANCHES)
431         return matches(absdungeon_depth(lid.branch, lid.depth));
432     else
433     {
434         return branch == lid.branch
435                && (lid.depth >= shallowest
436                    || shallowest == BRANCH_END && lid.depth == brdepth[branch])
437                && lid.depth <= deepest;
438     }
439 }
440 
matches(int x) const441 bool level_range::matches(int x) const
442 {
443     // [ds] The level ranges used by the game are zero-based, adjust for that.
444     ++x;
445     return x >= shallowest && x <= deepest;
446 }
447 
operator ==(const level_range & lr) const448 bool level_range::operator == (const level_range &lr) const
449 {
450     return deny == lr.deny
451            && shallowest == lr.shallowest
452            && deepest == lr.deepest
453            && branch == lr.branch;
454 }
455 
valid() const456 bool level_range::valid() const
457 {
458     return shallowest > 0 && deepest >= shallowest;
459 }
460 
461 ////////////////////////////////////////////////////////////////////////
462 // map_lines
463 
map_lines()464 map_lines::map_lines()
465     : markers(), lines(), overlay(),
466       map_width(0), solid_north(false), solid_east(false),
467       solid_south(false), solid_west(false), solid_checked(false)
468 {
469 }
470 
map_lines(const map_lines & map)471 map_lines::map_lines(const map_lines &map)
472 {
473     init_from(map);
474 }
475 
write_maplines(writer & outf) const476 void map_lines::write_maplines(writer &outf) const
477 {
478     const int h = height();
479     marshallShort(outf, h);
480     for (int i = 0; i < h; ++i)
481         marshallString(outf, lines[i]);
482 }
483 
read_maplines(reader & inf)484 void map_lines::read_maplines(reader &inf)
485 {
486     clear();
487     const int h = unmarshallShort(inf);
488     ASSERT_RANGE(h, 0, GYM + 1);
489 
490     for (int i = 0; i < h; ++i)
491         add_line(unmarshallString(inf));
492 }
493 
get_iter() const494 rectangle_iterator map_lines::get_iter() const
495 {
496     ASSERT(width() > 0);
497     ASSERT(height() > 0);
498 
499     coord_def tl(0, 0);
500     coord_def br(width() - 1, height() - 1);
501     return rectangle_iterator(tl, br);
502 }
503 
operator ()(const coord_def & c) const504 char map_lines::operator () (const coord_def &c) const
505 {
506     return lines[c.y][c.x];
507 }
508 
operator ()(const coord_def & c)509 char& map_lines::operator () (const coord_def &c)
510 {
511     return lines[c.y][c.x];
512 }
513 
operator ()(int x,int y) const514 char map_lines::operator () (int x, int y) const
515 {
516     return lines[y][x];
517 }
518 
operator ()(int x,int y)519 char& map_lines::operator () (int x, int y)
520 {
521     return lines[y][x];
522 }
523 
in_bounds(const coord_def & c) const524 bool map_lines::in_bounds(const coord_def &c) const
525 {
526     return c.x >= 0 && c.y >= 0 && c.x < width() && c.y < height();
527 }
528 
in_map(const coord_def & c) const529 bool map_lines::in_map(const coord_def &c) const
530 {
531     return in_bounds(c) && lines[c.y][c.x] != ' ';
532 }
533 
operator =(const map_lines & map)534 map_lines &map_lines::operator = (const map_lines &map)
535 {
536     if (this != &map)
537         init_from(map);
538     return *this;
539 }
540 
~map_lines()541 map_lines::~map_lines()
542 {
543     clear_markers();
544 }
545 
init_from(const map_lines & map)546 void map_lines::init_from(const map_lines &map)
547 {
548     // Markers have to be regenerated, they will not be copied.
549     clear_markers();
550     overlay.reset(nullptr);
551     lines            = map.lines;
552     map_width        = map.map_width;
553     solid_north      = map.solid_north;
554     solid_east       = map.solid_east;
555     solid_south      = map.solid_south;
556     solid_west       = map.solid_west;
557     solid_checked    = map.solid_checked;
558 }
559 
clear_markers()560 void map_lines::clear_markers()
561 {
562     deleteAll(markers);
563 }
564 
add_marker(map_marker * marker)565 void map_lines::add_marker(map_marker *marker)
566 {
567     markers.push_back(marker);
568 }
569 
add_feature_marker(const string & s)570 string map_lines::add_feature_marker(const string &s)
571 {
572     string key, arg;
573     int sep = 0;
574     string err = mapdef_split_key_item(s, &key, &sep, &arg, -1);
575     if (!err.empty())
576         return err;
577 
578     map_marker_spec spec(key, arg);
579     spec.apply_transform(*this);
580 
581     return "";
582 }
583 
add_lua_marker(const string & key,const lua_datum & function)584 string map_lines::add_lua_marker(const string &key, const lua_datum &function)
585 {
586     map_marker_spec spec(key, function);
587     spec.apply_transform(*this);
588     return "";
589 }
590 
apply_markers(const coord_def & c)591 void map_lines::apply_markers(const coord_def &c)
592 {
593     for (map_marker *marker : markers)
594     {
595         marker->pos += c;
596         env.markers.add(marker);
597     }
598     // *not* clear_markers() since we've offloaded marker ownership to
599     // the crawl env.
600     markers.clear();
601 }
602 
apply_grid_overlay(const coord_def & c,bool is_layout)603 void map_lines::apply_grid_overlay(const coord_def &c, bool is_layout)
604 {
605     if (!overlay)
606         return;
607 
608     for (int y = height() - 1; y >= 0; --y)
609         for (int x = width() - 1; x >= 0; --x)
610         {
611             coord_def gc(c.x + x, c.y + y);
612             if (is_layout && map_masked(gc, MMT_VAULT))
613                 continue;
614 
615             const int colour = (*overlay)(x, y).colour;
616             if (colour)
617                 dgn_set_grid_colour_at(gc, colour);
618 
619             const terrain_property_t property = (*overlay)(x, y).property;
620             if (property.flags >= FPROP_BLOODY)
621             {
622                  // Over-ride whatever property is already there.
623                 env.pgrid(gc) |= property;
624             }
625 
626             const int fheight = (*overlay)(x, y).height;
627             if (fheight != INVALID_HEIGHT)
628             {
629                 if (!env.heightmap)
630                     dgn_initialise_heightmap();
631                 dgn_height_at(gc) = fheight;
632             }
633 
634             bool has_floor = false, has_rock = false;
635             string name = (*overlay)(x, y).floortile;
636             if (!name.empty() && name != "none")
637             {
638                 tile_env.flv(gc).floor_idx =
639                     store_tilename_get_index(name);
640 
641                 tileidx_t floor;
642                 tile_dngn_index(name.c_str(), &floor);
643                 if (colour)
644                     floor = tile_dngn_coloured(floor, colour);
645                 int offset = random2(tile_dngn_count(floor));
646                 tile_env.flv(gc).floor = floor + offset;
647                 has_floor = true;
648             }
649 
650             name = (*overlay)(x, y).rocktile;
651             if (!name.empty() && name != "none")
652             {
653                 tile_env.flv(gc).wall_idx =
654                     store_tilename_get_index(name);
655 
656                 tileidx_t rock;
657                 tile_dngn_index(name.c_str(), &rock);
658                 if (colour)
659                     rock = tile_dngn_coloured(rock, colour);
660                 int offset = random2(tile_dngn_count(rock));
661                 tile_env.flv(gc).wall = rock + offset;
662                 has_rock = true;
663             }
664 
665             name = (*overlay)(x, y).tile;
666             if (!name.empty() && name != "none")
667             {
668                 tile_env.flv(gc).feat_idx =
669                     store_tilename_get_index(name);
670 
671                 tileidx_t feat;
672                 tile_dngn_index(name.c_str(), &feat);
673 
674                 if (colour)
675                     feat = tile_dngn_coloured(feat, colour);
676 
677                 int offset = 0;
678                 if ((*overlay)(x, y).last_tile)
679                     offset = tile_dngn_count(feat) - 1;
680                 else
681                     offset = random2(tile_dngn_count(feat));
682 
683                 if (!has_floor && env.grid(gc) == DNGN_FLOOR)
684                     tile_env.flv(gc).floor = feat + offset;
685                 else if (!has_rock && env.grid(gc) == DNGN_ROCK_WALL)
686                     tile_env.flv(gc).wall = feat + offset;
687                 else
688                 {
689                     if ((*overlay)(x, y).no_random)
690                         offset = 0;
691                     tile_env.flv(gc).feat = feat + offset;
692                 }
693             }
694         }
695 }
696 
apply_overlays(const coord_def & c,bool is_layout)697 void map_lines::apply_overlays(const coord_def &c, bool is_layout)
698 {
699     apply_markers(c);
700     apply_grid_overlay(c, is_layout);
701 }
702 
get_lines() const703 const vector<string> &map_lines::get_lines() const
704 {
705     return lines;
706 }
707 
get_lines()708 vector<string> &map_lines::get_lines()
709 {
710     return lines;
711 }
712 
add_line(const string & s)713 void map_lines::add_line(const string &s)
714 {
715     lines.push_back(s);
716     if (static_cast<int>(s.length()) > map_width)
717         map_width = s.length();
718 }
719 
clean_shuffle(string s)720 string map_lines::clean_shuffle(string s)
721 {
722     return replace_all_of(s, " \t", "");
723 }
724 
check_block_shuffle(const string & s)725 string map_lines::check_block_shuffle(const string &s)
726 {
727     const vector<string> segs = split_string("/", s);
728     const unsigned seglen = segs[0].length();
729 
730     for (const string &seg : segs)
731     {
732         if (seglen != seg.length())
733             return "block shuffle segment length mismatch";
734     }
735 
736     return "";
737 }
738 
check_shuffle(string & s)739 string map_lines::check_shuffle(string &s)
740 {
741     if (s.find(',') != string::npos)
742         return "use / for block shuffle, or multiple SHUFFLE: lines";
743 
744     s = clean_shuffle(s);
745 
746     if (s.find('/') != string::npos)
747         return check_block_shuffle(s);
748 
749     return "";
750 }
751 
check_clear(string & s)752 string map_lines::check_clear(string &s)
753 {
754     s = clean_shuffle(s);
755 
756     if (!s.length())
757         return "no glyphs specified for clearing";
758 
759     return "";
760 }
761 
parse_glyph_replacements(string s,glyph_replacements_t & gly)762 string map_lines::parse_glyph_replacements(string s, glyph_replacements_t &gly)
763 {
764     s = replace_all_of(s, "\t", " ");
765     for (const string &is : split_string(" ", s))
766     {
767         if (is.length() > 2 && is[1] == ':')
768         {
769             const int glych = is[0];
770             int weight;
771             if (!parse_int(is.substr(2).c_str(), weight) || weight < 1)
772                 return "Invalid weight specifier in \"" + s + "\"";
773             else
774                 gly.emplace_back(glych, weight);
775         }
776         else
777         {
778             for (int c = 0, cs = is.length(); c < cs; ++c)
779                 gly.emplace_back(is[c], 10);
780         }
781     }
782 
783     return "";
784 }
785 
786 template<class T>
_parse_weighted_str(const string & spec,T & list)787 static string _parse_weighted_str(const string &spec, T &list)
788 {
789     for (string val : split_string("/", spec))
790     {
791         lowercase(val);
792 
793         int weight = find_weight(val);
794         if (weight == TAG_UNFOUND)
795         {
796             weight = 10;
797             // :number suffix?
798             string::size_type cpos = val.find(':');
799             if (cpos != string::npos)
800             {
801                 if (!parse_int(val.substr(cpos + 1).c_str(), weight) || weight <= 0)
802                     return "Invalid weight specifier in \"" + spec + "\"";
803                 val.erase(cpos);
804                 trim_string(val);
805             }
806         }
807 
808         if (!list.parse(val, weight))
809         {
810             return make_stringf("bad spec: '%s' in '%s'",
811                                 val.c_str(), spec.c_str());
812         }
813     }
814     return "";
815 }
816 
parse(const string & col,int weight)817 bool map_colour_list::parse(const string &col, int weight)
818 {
819     const int colour = col == "none" ? BLACK : str_to_colour(col, -1, false, true);
820     if (colour == -1)
821         return false;
822 
823     emplace_back(colour, weight);
824     return true;
825 }
826 
add_colour(const string & sub)827 string map_lines::add_colour(const string &sub)
828 {
829     string s = trimmed_string(sub);
830 
831     if (s.empty())
832         return "";
833 
834     int sep = 0;
835     string key;
836     string substitute;
837 
838     string err = mapdef_split_key_item(sub, &key, &sep, &substitute, -1);
839     if (!err.empty())
840         return err;
841 
842     map_colour_list colours;
843     err = _parse_weighted_str<map_colour_list>(substitute, colours);
844     if (!err.empty())
845         return err;
846 
847     colour_spec spec(key, sep == ':', colours);
848     overlay_colours(spec);
849 
850     return "";
851 }
852 
parse(const string & fp,int weight)853 bool map_fprop_list::parse(const string &fp, int weight)
854 {
855     feature_property_type fprop;
856 
857     if (fp == "nothing")
858         fprop = FPROP_NONE;
859     else if (fp.empty())
860         return false;
861     else if (str_to_fprop(fp) == FPROP_NONE)
862         return false;
863     else
864         fprop = str_to_fprop(fp);
865 
866     emplace_back(fprop, weight);
867     return true;
868 }
869 
parse(const string & fp,int weight)870 bool map_featheight_list::parse(const string &fp, int weight)
871 {
872     const int thisheight = strict_aton(fp.c_str(), INVALID_HEIGHT);
873     if (thisheight == INVALID_HEIGHT)
874         return false;
875     emplace_back(thisheight, weight);
876     return true;
877 }
878 
add_fproperty(const string & sub)879 string map_lines::add_fproperty(const string &sub)
880 {
881     string s = trimmed_string(sub);
882 
883     if (s.empty())
884         return "";
885 
886     int sep = 0;
887     string key;
888     string substitute;
889 
890     string err = mapdef_split_key_item(sub, &key, &sep, &substitute, -1);
891     if (!err.empty())
892         return err;
893 
894     map_fprop_list fprops;
895     err = _parse_weighted_str<map_fprop_list>(substitute, fprops);
896     if (!err.empty())
897         return err;
898 
899     fprop_spec spec(key, sep == ':', fprops);
900     overlay_fprops(spec);
901 
902     return "";
903 }
904 
add_fheight(const string & sub)905 string map_lines::add_fheight(const string &sub)
906 {
907     string s = trimmed_string(sub);
908     if (s.empty())
909         return "";
910 
911     int sep = 0;
912     string key;
913     string substitute;
914 
915     string err = mapdef_split_key_item(sub, &key, &sep, &substitute, -1);
916     if (!err.empty())
917         return err;
918 
919     map_featheight_list fheights;
920     err = _parse_weighted_str(substitute, fheights);
921     if (!err.empty())
922         return err;
923 
924     fheight_spec spec(key, sep == ':', fheights);
925     overlay_fheights(spec);
926 
927     return "";
928 }
929 
parse(const string & fp,int weight)930 bool map_string_list::parse(const string &fp, int weight)
931 {
932     emplace_back(fp, weight);
933     return !fp.empty();
934 }
935 
add_subst(const string & sub)936 string map_lines::add_subst(const string &sub)
937 {
938     string s = trimmed_string(sub);
939 
940     if (s.empty())
941         return "";
942 
943     int sep = 0;
944     string key;
945     string substitute;
946 
947     string err = mapdef_split_key_item(sub, &key, &sep, &substitute, -1);
948     if (!err.empty())
949         return err;
950 
951     glyph_replacements_t repl;
952     err = parse_glyph_replacements(substitute, repl);
953     if (!err.empty())
954         return err;
955 
956     subst_spec spec(key, sep == ':', repl);
957     subst(spec);
958 
959     return "";
960 }
961 
parse_nsubst_spec(const string & s,subst_spec & spec)962 string map_lines::parse_nsubst_spec(const string &s, subst_spec &spec)
963 {
964     string key, arg;
965     int sep;
966     string err = mapdef_split_key_item(s, &key, &sep, &arg, -1);
967     if (!err.empty())
968         return err;
969     int count = 0;
970     if (key == "*")
971         count = -1;
972     else
973         parse_int(key.c_str(), count);
974     if (!count)
975         return make_stringf("Illegal spec: %s", s.c_str());
976 
977     glyph_replacements_t repl;
978     err = parse_glyph_replacements(arg, repl);
979     if (!err.empty())
980         return err;
981 
982     spec = subst_spec(count, sep == ':', repl);
983     return "";
984 }
985 
add_nsubst(const string & s)986 string map_lines::add_nsubst(const string &s)
987 {
988     vector<subst_spec> substs;
989 
990     int sep;
991     string key, arg;
992 
993     string err = mapdef_split_key_item(s, &key, &sep, &arg, -1);
994     if (!err.empty())
995         return err;
996 
997     vector<string> segs = split_string("/", arg);
998     for (int i = 0, vsize = segs.size(); i < vsize; ++i)
999     {
1000         string &ns = segs[i];
1001         if (ns.find('=') == string::npos && ns.find(':') == string::npos)
1002         {
1003             if (i < vsize - 1)
1004                 ns = "1=" + ns;
1005             else
1006                 ns = "*=" + ns;
1007         }
1008         subst_spec spec;
1009         err = parse_nsubst_spec(ns, spec);
1010         if (!err.empty())
1011         {
1012             return make_stringf("Bad NSUBST spec: %s (%s)",
1013                                 s.c_str(), err.c_str());
1014         }
1015         substs.push_back(spec);
1016     }
1017 
1018     nsubst_spec spec(key, substs);
1019     nsubst(spec);
1020 
1021     return "";
1022 }
1023 
add_shuffle(const string & raws)1024 string map_lines::add_shuffle(const string &raws)
1025 {
1026     string s = raws;
1027     const string err = check_shuffle(s);
1028 
1029     if (err.empty())
1030         resolve_shuffle(s);
1031 
1032     return err;
1033 }
1034 
add_clear(const string & raws)1035 string map_lines::add_clear(const string &raws)
1036 {
1037     string s = raws;
1038     const string err = check_clear(s);
1039 
1040     if (err.empty())
1041         clear(s);
1042 
1043     return err;
1044 }
1045 
width() const1046 int map_lines::width() const
1047 {
1048     return map_width;
1049 }
1050 
height() const1051 int map_lines::height() const
1052 {
1053     return lines.size();
1054 }
1055 
extend(int min_width,int min_height,char fill)1056 void map_lines::extend(int min_width, int min_height, char fill)
1057 {
1058     min_width = max(1, min_width);
1059     min_height = max(1, min_height);
1060 
1061     bool dirty = false;
1062     int old_width = width();
1063     int old_height = height();
1064 
1065     if (static_cast<int>(lines.size()) < min_height)
1066     {
1067         dirty = true;
1068         while (static_cast<int>(lines.size()) < min_height)
1069             add_line(string(min_width, fill));
1070     }
1071 
1072     if (width() < min_width)
1073     {
1074         dirty = true;
1075         lines[0] += string(min_width - width(), fill);
1076         map_width = max(map_width, min_width);
1077     }
1078 
1079     if (!dirty)
1080         return;
1081 
1082     normalise(fill);
1083 
1084     // Extend overlay matrix as well.
1085     if (overlay)
1086     {
1087         auto new_overlay = make_unique<overlay_matrix>(width(), height());
1088 
1089         for (int y = 0; y < old_height; ++y)
1090             for (int x = 0; x < old_width; ++x)
1091                 (*new_overlay)(x, y) = (*overlay)(x, y);
1092 
1093         overlay = move(new_overlay);
1094     }
1095 }
1096 
size() const1097 coord_def map_lines::size() const
1098 {
1099     return coord_def(width(), height());
1100 }
1101 
glyph(int x,int y) const1102 int map_lines::glyph(int x, int y) const
1103 {
1104     return lines[y][x];
1105 }
1106 
glyph(const coord_def & c) const1107 int map_lines::glyph(const coord_def &c) const
1108 {
1109     return glyph(c.x, c.y);
1110 }
1111 
is_solid(int gly) const1112 bool map_lines::is_solid(int gly) const
1113 {
1114     return gly == 'x' || gly == 'c' || gly == 'b' || gly == 'v' || gly == 't'
1115            || gly == 'X';
1116 }
1117 
check_borders()1118 void map_lines::check_borders()
1119 {
1120     if (solid_checked)
1121         return;
1122 
1123     const int wide = width(), high = height();
1124 
1125     solid_north = solid_south = true;
1126     for (int x = 0; x < wide && (solid_north || solid_south); ++x)
1127     {
1128         if (solid_north && !is_solid(glyph(x, 0)))
1129             solid_north = false;
1130         if (solid_south && !is_solid(glyph(x, high - 1)))
1131             solid_south = false;
1132     }
1133 
1134     solid_east = solid_west = true;
1135     for (int y = 0; y < high && (solid_east || solid_west); ++y)
1136     {
1137         if (solid_west && !is_solid(glyph(0, y)))
1138             solid_west = false;
1139         if (solid_east && !is_solid(glyph(wide - 1, y)))
1140             solid_east = false;
1141     }
1142 
1143     solid_checked = true;
1144 }
1145 
solid_borders(map_section_type border)1146 bool map_lines::solid_borders(map_section_type border)
1147 {
1148     check_borders();
1149     switch (border)
1150     {
1151     case MAP_NORTH: return solid_north;
1152     case MAP_SOUTH: return solid_south;
1153     case MAP_EAST:  return solid_east;
1154     case MAP_WEST:  return solid_west;
1155     case MAP_NORTHEAST: return solid_north && solid_east;
1156     case MAP_NORTHWEST: return solid_north && solid_west;
1157     case MAP_SOUTHEAST: return solid_south && solid_east;
1158     case MAP_SOUTHWEST: return solid_south && solid_west;
1159     default: return false;
1160     }
1161 }
1162 
clear()1163 void map_lines::clear()
1164 {
1165     clear_markers();
1166     lines.clear();
1167     keyspecs.clear();
1168     overlay.reset(nullptr);
1169     map_width = 0;
1170     solid_checked = false;
1171 
1172     // First non-legal character.
1173     next_keyspec_idx = 256;
1174 }
1175 
subst(string & s,subst_spec & spec)1176 void map_lines::subst(string &s, subst_spec &spec)
1177 {
1178     string::size_type pos = 0;
1179     while ((pos = s.find_first_of(spec.key, pos)) != string::npos)
1180         s[pos++] = spec.value();
1181 }
1182 
subst(subst_spec & spec)1183 void map_lines::subst(subst_spec &spec)
1184 {
1185     ASSERT(!spec.key.empty());
1186     for (string &line : lines)
1187         subst(line, spec);
1188 }
1189 
bind_overlay()1190 void map_lines::bind_overlay()
1191 {
1192     if (!overlay)
1193         overlay.reset(new overlay_matrix(width(), height()));
1194 }
1195 
overlay_colours(colour_spec & spec)1196 void map_lines::overlay_colours(colour_spec &spec)
1197 {
1198     bind_overlay();
1199     for (iterator mi(*this, spec.key); mi; ++mi)
1200         (*overlay)(*mi).colour = spec.get_colour();
1201 }
1202 
overlay_fprops(fprop_spec & spec)1203 void map_lines::overlay_fprops(fprop_spec &spec)
1204 {
1205     bind_overlay();
1206     for (iterator mi(*this, spec.key); mi; ++mi)
1207         (*overlay)(*mi).property |= spec.get_property();
1208 }
1209 
overlay_fheights(fheight_spec & spec)1210 void map_lines::overlay_fheights(fheight_spec &spec)
1211 {
1212     bind_overlay();
1213     for (iterator mi(*this, spec.key); mi; ++mi)
1214         (*overlay)(*mi).height = spec.get_height();
1215 }
1216 
fill_mask_matrix(const string & glyphs,const coord_def & tl,const coord_def & br,Matrix<bool> & flags)1217 void map_lines::fill_mask_matrix(const string &glyphs,
1218                                  const coord_def &tl,
1219                                  const coord_def &br,
1220                                  Matrix<bool> &flags)
1221 {
1222     ASSERT(tl.x >= 0);
1223     ASSERT(tl.y >= 0);
1224     ASSERT(br.x < width());
1225     ASSERT(br.y < height());
1226     ASSERT(tl.x <= br.x);
1227     ASSERT(tl.y <= br.y);
1228 
1229     for (int y = tl.y; y <= br.y; ++y)
1230         for (int x = tl.x; x <= br.x; ++x)
1231         {
1232             int ox = x - tl.x;
1233             int oy = y - tl.y;
1234             flags(ox, oy) = (strchr(glyphs.c_str(), (*this)(x, y)) != nullptr);
1235         }
1236 }
1237 
merge_subvault(const coord_def & mtl,const coord_def & mbr,const Matrix<bool> & mask,const map_def & vmap)1238 map_corner_t map_lines::merge_subvault(const coord_def &mtl,
1239                                        const coord_def &mbr,
1240                                        const Matrix<bool> &mask,
1241                                        const map_def &vmap)
1242 {
1243     const map_lines &vlines = vmap.map;
1244 
1245     // If vault is bigger than the mask region (mtl, mbr), then it gets
1246     // randomly centered. (vtl, vbr) stores the vault's region.
1247     coord_def vtl = mtl;
1248     coord_def vbr = mbr;
1249 
1250     int width_diff = (mbr.x - mtl.x + 1) - vlines.width();
1251     int height_diff = (mbr.y - mtl.y + 1) - vlines.height();
1252 
1253     // Adjust vault coords with a random offset.
1254     int ox = random2(width_diff + 1);
1255     int oy = random2(height_diff + 1);
1256     vtl.x += ox;
1257     vtl.y += oy;
1258     vbr.x -= (width_diff - ox);
1259     vbr.y -= (height_diff - oy);
1260 
1261     if (!overlay)
1262         overlay.reset(new overlay_matrix(width(), height()));
1263 
1264     // Clear any markers in the vault's grid
1265     for (size_t i = 0; i < markers.size(); ++i)
1266     {
1267         map_marker *mm = markers[i];
1268         if (mm->pos.x >= vtl.x && mm->pos.x <= vbr.x
1269             && mm->pos.y >= vtl.y && mm->pos.y <= vbr.y)
1270         {
1271             const coord_def maskc = mm->pos - mtl;
1272             if (!mask(maskc.x, maskc.y))
1273                 continue;
1274 
1275             // Erase this marker.
1276             markers[i] = markers[markers.size() - 1];
1277             markers.resize(markers.size() - 1);
1278             i--;
1279         }
1280     }
1281 
1282     // Copy markers and update locations.
1283     for (map_marker *mm : vlines.markers)
1284     {
1285         coord_def mapc = mm->pos + vtl;
1286         coord_def maskc = mapc - mtl;
1287 
1288         if (!mask(maskc.x, maskc.y))
1289             continue;
1290 
1291         map_marker *clone = mm->clone();
1292         clone->pos = mapc;
1293         add_marker(clone);
1294     }
1295 
1296     unsigned mask_tags = 0;
1297 
1298     // TODO: merge the matching of tags to MMTs into a function that is
1299     // called from both here and dungeon.cc::dgn_register_place.
1300     if (vmap.has_tag("no_monster_gen"))
1301         mask_tags |= MMT_NO_MONS;
1302     if (vmap.has_tag("no_item_gen"))
1303         mask_tags |= MMT_NO_ITEM;
1304     if (vmap.has_tag("no_pool_fixup"))
1305         mask_tags |= MMT_NO_POOL;
1306     if (vmap.has_tag("no_wall_fixup"))
1307         mask_tags |= MMT_NO_WALL;
1308     if (vmap.has_tag("no_trap_gen"))
1309         mask_tags |= MMT_NO_TRAP;
1310 
1311     // Cache keyspecs we've already pushed into the extended keyspec space.
1312     // If !ksmap[key], then we haven't seen the 'key' glyph before.
1313     keyspec_map ksmap(0);
1314 
1315     for (int y = mtl.y; y <= mbr.y; ++y)
1316         for (int x = mtl.x; x <= mbr.x; ++x)
1317         {
1318             int mx = x - mtl.x;
1319             int my = y - mtl.y;
1320             if (!mask(mx, my))
1321                 continue;
1322 
1323             // Outside subvault?
1324             if (x < vtl.x || x > vbr.x || y < vtl.y || y > vbr.y)
1325                 continue;
1326 
1327             int vx = x - vtl.x;
1328             int vy = y - vtl.y;
1329             coord_def vc(vx, vy);
1330 
1331             char c = vlines(vc);
1332             if (c == ' ')
1333                 continue;
1334 
1335             // Merge keyspecs.
1336             // Push vault keyspecs into extended keyspecs.
1337             // Push MONS/ITEM into KMONS/KITEM keyspecs.
1338             // Generate KFEAT keyspecs for any normal glyphs.
1339             int idx;
1340             if (ksmap[c])
1341             {
1342                 // Already generated this keyed_pmapspec.
1343                 idx = ksmap[c];
1344             }
1345             else
1346             {
1347                 idx = next_keyspec_idx++;
1348                 ASSERT(idx > 0);
1349 
1350                 if (c != SUBVAULT_GLYPH)
1351                     ksmap[c] = idx;
1352 
1353                 const keyed_mapspec *kspec = vlines.mapspec_at(vc);
1354 
1355                 // If c is a SUBVAULT_GLYPH, it came from a sub-subvault.
1356                 // Sub-subvaults should always have mapspecs at this point.
1357                 ASSERT(c != SUBVAULT_GLYPH || kspec);
1358 
1359                 if (kspec)
1360                 {
1361                     // Copy vault keyspec into the extended keyspecs.
1362                     keyspecs[idx] = *kspec;
1363                     keyspecs[idx].key_glyph = SUBVAULT_GLYPH;
1364                 }
1365                 else if (map_def::valid_monster_array_glyph(c))
1366                 {
1367                     // Translate monster array into keyed_mapspec
1368                     keyed_mapspec &km = keyspecs[idx];
1369                     km.key_glyph = SUBVAULT_GLYPH;
1370 
1371                     km.feat.feats.clear();
1372                     feature_spec spec(-1);
1373                     spec.glyph = '.';
1374                     km.feat.feats.insert(km.feat.feats.begin(), spec);
1375 
1376                     int slot = map_def::monster_array_glyph_to_slot(c);
1377                     km.mons.set_from_slot(vmap.mons, slot);
1378                 }
1379                 else if (map_def::valid_item_array_glyph(c))
1380                 {
1381                     // Translate item array into keyed_mapspec
1382                     keyed_mapspec &km = keyspecs[idx];
1383                     km.key_glyph = SUBVAULT_GLYPH;
1384 
1385                     km.feat.feats.clear();
1386                     feature_spec spec(-1);
1387                     spec.glyph = '.';
1388                     km.feat.feats.insert(km.feat.feats.begin(), spec);
1389 
1390                     int slot = map_def::item_array_glyph_to_slot(c);
1391                     km.item.set_from_slot(vmap.items, slot);
1392                 }
1393                 else
1394                 {
1395                     // Normal glyph. Turn into a feature keyspec.
1396                     // This is valid for non-array items and monsters
1397                     // as well, e.g. '$' and '8'.
1398                     keyed_mapspec &km = keyspecs[idx];
1399                     km.key_glyph = SUBVAULT_GLYPH;
1400                     km.feat.feats.clear();
1401 
1402                     feature_spec spec(-1);
1403                     spec.glyph = c;
1404                     km.feat.feats.insert(km.feat.feats.begin(), spec);
1405                 }
1406 
1407                 // Add overall tags to the keyspec.
1408                 keyspecs[idx].map_mask.flags_set
1409                     |= (mask_tags & ~keyspecs[idx].map_mask.flags_unset);
1410             }
1411 
1412             // Finally, handle merging the cell itself.
1413 
1414             // Glyph becomes SUBVAULT_GLYPH. (The old glyph gets merged into a
1415             // keyspec, above). This is so that the glyphs that are included
1416             // from a subvault are immutable by the parent vault. Otherwise,
1417             // latent transformations (like KMONS or KITEM) from the parent
1418             // vault might confusingly modify a glyph from the subvault.
1419             //
1420             // NOTE: It'd be possible to allow subvaults to be modified by the
1421             // parent vault, but KMONS/KITEM/KFEAT/MONS/ITEM would have to
1422             // apply immediately instead of latently. They would also then
1423             // need to be stored per-coord, rather than per-glyph.
1424             (*this)(x, y) = SUBVAULT_GLYPH;
1425 
1426             // Merge overlays
1427             if (vlines.overlay)
1428                 (*overlay)(x, y) = (*vlines.overlay)(vx, vy);
1429             else
1430             {
1431                 // Erase any existing overlay, as the vault's doesn't exist.
1432                 (*overlay)(x, y) = overlay_def();
1433             }
1434 
1435             // Set keyspec index for this subvault.
1436             (*overlay)(x, y).keyspec_idx = idx;
1437         }
1438 
1439     return map_corner_t(vtl, vbr);
1440 }
1441 
overlay_tiles(tile_spec & spec)1442 void map_lines::overlay_tiles(tile_spec &spec)
1443 {
1444     if (!overlay)
1445         overlay.reset(new overlay_matrix(width(), height()));
1446 
1447     for (int y = 0, ysize = lines.size(); y < ysize; ++y)
1448     {
1449         string::size_type pos = 0;
1450         while ((pos = lines[y].find_first_of(spec.key, pos)) != string::npos)
1451         {
1452             if (spec.floor)
1453                 (*overlay)(pos, y).floortile = spec.get_tile();
1454             else if (spec.feat)
1455                 (*overlay)(pos, y).tile      = spec.get_tile();
1456             else
1457                 (*overlay)(pos, y).rocktile  = spec.get_tile();
1458 
1459             (*overlay)(pos, y).no_random = spec.no_random;
1460             (*overlay)(pos, y).last_tile = spec.last_tile;
1461             ++pos;
1462         }
1463     }
1464 }
1465 
nsubst(nsubst_spec & spec)1466 void map_lines::nsubst(nsubst_spec &spec)
1467 {
1468     vector<coord_def> positions;
1469     for (int y = 0, ysize = lines.size(); y < ysize; ++y)
1470     {
1471         string::size_type pos = 0;
1472         while ((pos = lines[y].find_first_of(spec.key, pos)) != string::npos)
1473             positions.emplace_back(pos++, y);
1474     }
1475     shuffle_array(positions);
1476 
1477     int pcount = 0;
1478     const int psize = positions.size();
1479     for (int i = 0, vsize = spec.specs.size();
1480          i < vsize && pcount < psize; ++i)
1481     {
1482         const int nsubsts = spec.specs[i].count;
1483         pcount += apply_nsubst(positions, pcount, nsubsts, spec.specs[i]);
1484     }
1485 }
1486 
apply_nsubst(vector<coord_def> & pos,int start,int nsub,subst_spec & spec)1487 int map_lines::apply_nsubst(vector<coord_def> &pos, int start, int nsub,
1488                             subst_spec &spec)
1489 {
1490     if (nsub == -1)
1491         nsub = pos.size();
1492     const int end = min(start + nsub, (int) pos.size());
1493     int substituted = 0;
1494     for (int i = start; i < end; ++i)
1495     {
1496         const int val = spec.value();
1497         const coord_def &c = pos[i];
1498         lines[c.y][c.x] = val;
1499         ++substituted;
1500     }
1501     return substituted;
1502 }
1503 
block_shuffle(const string & s)1504 string map_lines::block_shuffle(const string &s)
1505 {
1506     vector<string> segs = split_string("/", s);
1507     shuffle_array(segs);
1508     return comma_separated_line(segs.begin(), segs.end(), "/", "/");
1509 }
1510 
shuffle(string s)1511 string map_lines::shuffle(string s)
1512 {
1513     string result;
1514 
1515     if (s.find('/') != string::npos)
1516         return block_shuffle(s);
1517 
1518     // Inefficient brute-force shuffle.
1519     while (!s.empty())
1520     {
1521         const int c = random2(s.length());
1522         result += s[c];
1523         s.erase(c, 1);
1524     }
1525 
1526     return result;
1527 }
1528 
resolve_shuffle(const string & shufflage)1529 void map_lines::resolve_shuffle(const string &shufflage)
1530 {
1531     string toshuffle = shufflage;
1532     string shuffled = shuffle(toshuffle);
1533 
1534     if (toshuffle.empty() || shuffled.empty())
1535         return;
1536 
1537     for (string &s : lines)
1538     {
1539         for (char &c : s)
1540         {
1541             string::size_type pos = toshuffle.find(c);
1542             if (pos != string::npos)
1543                 c = shuffled[pos];
1544         }
1545     }
1546 }
1547 
clear(const string & clearchars)1548 void map_lines::clear(const string &clearchars)
1549 {
1550     for (string &s : lines)
1551     {
1552         for (char &c : s)
1553         {
1554             string::size_type pos = clearchars.find(c);
1555             if (pos != string::npos)
1556                 c = ' ';
1557         }
1558     }
1559 }
1560 
normalise(char fillch)1561 void map_lines::normalise(char fillch)
1562 {
1563     for (string &s : lines)
1564         if (static_cast<int>(s.length()) < map_width)
1565             s += string(map_width - s.length(), fillch);
1566 }
1567 
1568 // Should never be attempted if the map has a defined orientation, or if one
1569 // of the dimensions is greater than the lesser of GXM,GYM.
rotate(bool clockwise)1570 void map_lines::rotate(bool clockwise)
1571 {
1572     vector<string> newlines;
1573 
1574     // normalise() first for convenience.
1575     normalise();
1576 
1577     const int xs = clockwise? 0 : map_width - 1,
1578               xe = clockwise? map_width : -1,
1579               xi = clockwise? 1 : -1;
1580 
1581     const int ys = clockwise? (int) lines.size() - 1 : 0,
1582               ye = clockwise? -1 : (int) lines.size(),
1583               yi = clockwise? -1 : 1;
1584 
1585     for (int i = xs; i != xe; i += xi)
1586     {
1587         string line;
1588 
1589         for (int j = ys; j != ye; j += yi)
1590             line += lines[j][i];
1591 
1592         newlines.push_back(line);
1593     }
1594 
1595     if (overlay)
1596     {
1597         auto new_overlay = make_unique<overlay_matrix>(lines.size(), map_width);
1598         for (int i = xs, y = 0; i != xe; i += xi, ++y)
1599             for (int j = ys, x = 0; j != ye; j += yi, ++x)
1600                 (*new_overlay)(x, y) = (*overlay)(i, j);
1601         overlay = move(new_overlay);
1602     }
1603 
1604     map_width = lines.size();
1605     lines     = newlines;
1606     rotate_markers(clockwise);
1607     solid_checked = false;
1608 }
1609 
translate_marker(void (map_lines::* xform)(map_marker *,int),int par)1610 void map_lines::translate_marker(
1611     void (map_lines::*xform)(map_marker *, int),
1612     int par)
1613 {
1614     for (map_marker *marker : markers)
1615         (this->*xform)(marker, par);
1616 }
1617 
vmirror_marker(map_marker * marker,int)1618 void map_lines::vmirror_marker(map_marker *marker, int)
1619 {
1620     marker->pos.y = height() - 1 - marker->pos.y;
1621 }
1622 
hmirror_marker(map_marker * marker,int)1623 void map_lines::hmirror_marker(map_marker *marker, int)
1624 {
1625     marker->pos.x = width() - 1 - marker->pos.x;
1626 }
1627 
rotate_marker(map_marker * marker,int clockwise)1628 void map_lines::rotate_marker(map_marker *marker, int clockwise)
1629 {
1630     const coord_def c = marker->pos;
1631     if (clockwise)
1632         marker->pos = coord_def(width() - 1 - c.y, c.x);
1633     else
1634         marker->pos = coord_def(c.y, height() - 1 - c.x);
1635 }
1636 
vmirror_markers()1637 void map_lines::vmirror_markers()
1638 {
1639     translate_marker(&map_lines::vmirror_marker);
1640 }
1641 
hmirror_markers()1642 void map_lines::hmirror_markers()
1643 {
1644     translate_marker(&map_lines::hmirror_marker);
1645 }
1646 
rotate_markers(bool clock)1647 void map_lines::rotate_markers(bool clock)
1648 {
1649     translate_marker(&map_lines::rotate_marker, clock);
1650 }
1651 
vmirror()1652 void map_lines::vmirror()
1653 {
1654     const int vsize = lines.size();
1655     const int midpoint = vsize / 2;
1656 
1657     for (int i = 0; i < midpoint; ++i)
1658     {
1659         string temp = lines[i];
1660         lines[i] = lines[vsize - 1 - i];
1661         lines[vsize - 1 - i] = temp;
1662     }
1663 
1664     if (overlay)
1665     {
1666         for (int i = 0; i < midpoint; ++i)
1667             for (int j = 0, wide = width(); j < wide; ++j)
1668                 swap((*overlay)(j, i), (*overlay)(j, vsize - 1 - i));
1669     }
1670 
1671     vmirror_markers();
1672     solid_checked = false;
1673 }
1674 
hmirror()1675 void map_lines::hmirror()
1676 {
1677     const int midpoint = map_width / 2;
1678     for (string &s : lines)
1679         for (int j = 0; j < midpoint; ++j)
1680             swap(s[j], s[map_width - 1 - j]);
1681 
1682     if (overlay)
1683     {
1684         for (int i = 0, vsize = lines.size(); i < vsize; ++i)
1685             for (int j = 0; j < midpoint; ++j)
1686                 swap((*overlay)(j, i), (*overlay)(map_width - 1 - j, i));
1687     }
1688 
1689     hmirror_markers();
1690     solid_checked = false;
1691 }
1692 
mapspec_for_key(int key)1693 keyed_mapspec *map_lines::mapspec_for_key(int key)
1694 {
1695     return map_find(keyspecs, key);
1696 }
1697 
mapspec_for_key(int key) const1698 const keyed_mapspec *map_lines::mapspec_for_key(int key) const
1699 {
1700     return map_find(keyspecs, key);
1701 }
1702 
mapspec_at(const coord_def & c)1703 keyed_mapspec *map_lines::mapspec_at(const coord_def &c)
1704 {
1705     int key = (*this)(c);
1706 
1707     if (key == SUBVAULT_GLYPH)
1708     {
1709         // Any subvault should create the overlay.
1710         ASSERT(overlay);
1711         if (!overlay)
1712             return nullptr;
1713 
1714         key = (*overlay)(c.x, c.y).keyspec_idx;
1715         ASSERT(key);
1716         if (!key)
1717             return nullptr;
1718     }
1719 
1720     return mapspec_for_key(key);
1721 }
1722 
mapspec_at(const coord_def & c) const1723 const keyed_mapspec *map_lines::mapspec_at(const coord_def &c) const
1724 {
1725     int key = (*this)(c);
1726 
1727     if (key == SUBVAULT_GLYPH)
1728     {
1729         // Any subvault should create the overlay and set the keyspec idx.
1730         ASSERT(overlay);
1731         if (!overlay)
1732             return nullptr;
1733 
1734         key = (*overlay)(c.x, c.y).keyspec_idx;
1735         ASSERT(key);
1736         if (!key)
1737             return nullptr;
1738     }
1739 
1740     return mapspec_for_key(key);
1741 }
1742 
add_key_field(const string & s,string (keyed_mapspec::* set_field)(const string & s,bool fixed),void (keyed_mapspec::* copy_field)(const keyed_mapspec & spec))1743 string map_lines::add_key_field(
1744     const string &s,
1745     string (keyed_mapspec::*set_field)(const string &s, bool fixed),
1746     void (keyed_mapspec::*copy_field)(const keyed_mapspec &spec))
1747 {
1748     int separator = 0;
1749     string key, arg;
1750 
1751     string err = mapdef_split_key_item(s, &key, &separator, &arg, -1);
1752     if (!err.empty())
1753         return err;
1754 
1755     keyed_mapspec &kmbase = keyspecs[key[0]];
1756     kmbase.key_glyph = key[0];
1757     err = ((kmbase.*set_field)(arg, separator == ':'));
1758     if (!err.empty())
1759         return err;
1760 
1761     size_t len = key.length();
1762     for (size_t i = 1; i < len; i++)
1763     {
1764         keyed_mapspec &km = keyspecs[key[i]];
1765         km.key_glyph = key[i];
1766         ((km.*copy_field)(kmbase));
1767     }
1768 
1769     return err;
1770 }
1771 
add_key_item(const string & s)1772 string map_lines::add_key_item(const string &s)
1773 {
1774     return add_key_field(s, &keyed_mapspec::set_item,
1775                          &keyed_mapspec::copy_item);
1776 }
1777 
add_key_feat(const string & s)1778 string map_lines::add_key_feat(const string &s)
1779 {
1780     return add_key_field(s, &keyed_mapspec::set_feat,
1781                          &keyed_mapspec::copy_feat);
1782 }
1783 
add_key_mons(const string & s)1784 string map_lines::add_key_mons(const string &s)
1785 {
1786     return add_key_field(s, &keyed_mapspec::set_mons,
1787                          &keyed_mapspec::copy_mons);
1788 }
1789 
add_key_mask(const string & s)1790 string map_lines::add_key_mask(const string &s)
1791 {
1792     return add_key_field(s, &keyed_mapspec::set_mask,
1793                          &keyed_mapspec::copy_mask);
1794 }
1795 
find_glyph(int gly) const1796 vector<coord_def> map_lines::find_glyph(int gly) const
1797 {
1798     vector<coord_def> points;
1799     for (int y = height() - 1; y >= 0; --y)
1800     {
1801         for (int x = width() - 1; x >= 0; --x)
1802         {
1803             const coord_def c(x, y);
1804             if ((*this)(c) == gly)
1805                 points.push_back(c);
1806         }
1807     }
1808     return points;
1809 }
1810 
find_glyph(const string & glyphs) const1811 vector<coord_def> map_lines::find_glyph(const string &glyphs) const
1812 {
1813     vector<coord_def> points;
1814     for (int y = height() - 1; y >= 0; --y)
1815     {
1816         for (int x = width() - 1; x >= 0; --x)
1817         {
1818             const coord_def c(x, y);
1819             if (glyphs.find((*this)(c)) != string::npos)
1820                 points.push_back(c);
1821         }
1822     }
1823     return points;
1824 }
1825 
find_first_glyph(int gly) const1826 coord_def map_lines::find_first_glyph(int gly) const
1827 {
1828     for (int y = 0, h = height(); y < h; ++y)
1829     {
1830         string::size_type pos = lines[y].find(gly);
1831         if (pos != string::npos)
1832             return coord_def(pos, y);
1833     }
1834 
1835     return coord_def(-1, -1);
1836 }
1837 
find_first_glyph(const string & glyphs) const1838 coord_def map_lines::find_first_glyph(const string &glyphs) const
1839 {
1840     for (int y = 0, h = height(); y < h; ++y)
1841     {
1842         string::size_type pos = lines[y].find_first_of(glyphs);
1843         if (pos != string::npos)
1844             return coord_def(pos, y);
1845     }
1846     return coord_def(-1, -1);
1847 }
1848 
find_bounds(int gly,coord_def & tl,coord_def & br) const1849 bool map_lines::find_bounds(int gly, coord_def &tl, coord_def &br) const
1850 {
1851     tl = coord_def(width(), height());
1852     br = coord_def(-1, -1);
1853 
1854     if (width() == 0 || height() == 0)
1855         return false;
1856 
1857     for (rectangle_iterator ri(get_iter()); ri; ++ri)
1858     {
1859         const coord_def mc = *ri;
1860         if ((*this)(mc) != gly)
1861             continue;
1862 
1863         tl.x = min(tl.x, mc.x);
1864         tl.y = min(tl.y, mc.y);
1865         br.x = max(br.x, mc.x);
1866         br.y = max(br.y, mc.y);
1867     }
1868 
1869     return br.x >= 0;
1870 }
1871 
find_bounds(const char * str,coord_def & tl,coord_def & br) const1872 bool map_lines::find_bounds(const char *str, coord_def &tl, coord_def &br) const
1873 {
1874     tl = coord_def(width(), height());
1875     br = coord_def(-1, -1);
1876 
1877     if (width() == 0 || height() == 0)
1878         return false;
1879 
1880     for (rectangle_iterator ri(get_iter()); ri; ++ri)
1881     {
1882         ASSERT(ri);
1883         const coord_def &mc = *ri;
1884         const size_t len = strlen(str);
1885         for (size_t i = 0; i < len; ++i)
1886         {
1887             if ((*this)(mc) == str[i])
1888             {
1889                 tl.x = min(tl.x, mc.x);
1890                 tl.y = min(tl.y, mc.y);
1891                 br.x = max(br.x, mc.x);
1892                 br.y = max(br.y, mc.y);
1893                 break;
1894             }
1895         }
1896     }
1897 
1898     return br.x >= 0;
1899 }
1900 
fill_zone(travel_distance_grid_t & tpd,const coord_def & start,const coord_def & tl,const coord_def & br,int zone,const char * wanted,const char * passable) const1901 bool map_lines::fill_zone(travel_distance_grid_t &tpd, const coord_def &start,
1902                           const coord_def &tl, const coord_def &br, int zone,
1903                           const char *wanted, const char *passable) const
1904 {
1905     // This is the map_lines equivalent of _dgn_fill_zone.
1906     // It's unfortunately extremely similar, but not close enough to combine.
1907 
1908     bool ret = false;
1909     list<coord_def> points[2];
1910     int cur = 0;
1911 
1912     for (points[cur].push_back(start); !points[cur].empty();)
1913     {
1914         for (const auto &c : points[cur])
1915         {
1916             tpd[c.x][c.y] = zone;
1917 
1918             ret |= (wanted && strchr(wanted, (*this)(c)) != nullptr);
1919 
1920             for (int yi = -1; yi <= 1; ++yi)
1921                 for (int xi = -1; xi <= 1; ++xi)
1922                 {
1923                     if (!xi && !yi)
1924                         continue;
1925 
1926                     const coord_def cp(c.x + xi, c.y + yi);
1927                     if (cp.x < tl.x || cp.x > br.x
1928                         || cp.y < tl.y || cp.y > br.y
1929                         || !in_bounds(cp) || tpd[cp.x][cp.y]
1930                         || passable && !strchr(passable, (*this)(cp)))
1931                     {
1932                         continue;
1933                     }
1934 
1935                     tpd[cp.x][cp.y] = zone;
1936                     points[!cur].push_back(cp);
1937                 }
1938         }
1939 
1940         points[cur].clear();
1941         cur = !cur;
1942     }
1943     return ret;
1944 }
1945 
count_feature_in_box(const coord_def & tl,const coord_def & br,const char * feat) const1946 int map_lines::count_feature_in_box(const coord_def &tl, const coord_def &br,
1947                                     const char *feat) const
1948 {
1949     int result = 0;
1950     for (rectangle_iterator ri(tl, br); ri; ++ri)
1951     {
1952         if (strchr(feat, (*this)(*ri)))
1953             result++;
1954     }
1955 
1956     return result;
1957 }
1958 
parse(const string & s,int weight)1959 bool map_tile_list::parse(const string &s, int weight)
1960 {
1961     tileidx_t idx = 0;
1962     if (s != "none" && !tile_dngn_index(s.c_str(), &idx))
1963         return false;
1964 
1965     emplace_back(s, weight);
1966     return true;
1967 }
1968 
add_tile(const string & sub,bool is_floor,bool is_feat)1969 string map_lines::add_tile(const string &sub, bool is_floor, bool is_feat)
1970 {
1971     string s = trimmed_string(sub);
1972 
1973     if (s.empty())
1974         return "";
1975 
1976     bool no_random = strip_tag(s, "no_random");
1977     bool last_tile = strip_tag(s, "last_tile");
1978 
1979     int sep = 0;
1980     string key;
1981     string substitute;
1982 
1983     string err = mapdef_split_key_item(s, &key, &sep, &substitute, -1);
1984     if (!err.empty())
1985         return err;
1986 
1987     map_tile_list list;
1988     err = _parse_weighted_str<map_tile_list>(substitute, list);
1989     if (!err.empty())
1990         return err;
1991 
1992     tile_spec spec(key, sep == ':', no_random, last_tile, is_floor, is_feat, list);
1993     overlay_tiles(spec);
1994 
1995     return "";
1996 }
1997 
add_rocktile(const string & sub)1998 string map_lines::add_rocktile(const string &sub)
1999 {
2000     return add_tile(sub, false, false);
2001 }
2002 
add_floortile(const string & sub)2003 string map_lines::add_floortile(const string &sub)
2004 {
2005     return add_tile(sub, true, false);
2006 }
2007 
add_spec_tile(const string & sub)2008 string map_lines::add_spec_tile(const string &sub)
2009 {
2010     return add_tile(sub, false, true);
2011 }
2012 
2013 //////////////////////////////////////////////////////////////////////////
2014 // tile_spec
2015 
get_tile()2016 string tile_spec::get_tile()
2017 {
2018     if (chose_fixed)
2019         return fixed_tile;
2020 
2021     string chosen = "";
2022     int cweight = 0;
2023     for (const map_weighted_tile &tile : tiles)
2024         if (x_chance_in_y(tile.second, cweight += tile.second))
2025             chosen = tile.first;
2026 
2027     if (fix)
2028     {
2029         chose_fixed = true;
2030         fixed_tile  = chosen;
2031     }
2032     return chosen;
2033 }
2034 
2035 //////////////////////////////////////////////////////////////////////////
2036 // map_lines::iterator
2037 
iterator(map_lines & _maplines,const string & _key)2038 map_lines::iterator::iterator(map_lines &_maplines, const string &_key)
2039     : maplines(_maplines), key(_key), p(0, 0)
2040 {
2041     advance();
2042 }
2043 
advance()2044 void map_lines::iterator::advance()
2045 {
2046     const int height = maplines.height();
2047     while (p.y < height)
2048     {
2049         string::size_type place = p.x;
2050         if (place < maplines.lines[p.y].length())
2051         {
2052             place = maplines.lines[p.y].find_first_of(key, place);
2053             if (place != string::npos)
2054             {
2055                 p.x = place;
2056                 break;
2057             }
2058         }
2059         ++p.y;
2060         p.x = 0;
2061     }
2062 }
2063 
operator bool() const2064 map_lines::iterator::operator bool() const
2065 {
2066     return p.y < maplines.height();
2067 }
2068 
operator *() const2069 coord_def map_lines::iterator::operator *() const
2070 {
2071     return p;
2072 }
2073 
operator ++()2074 coord_def map_lines::iterator::operator ++ ()
2075 {
2076     p.x++;
2077     advance();
2078     return **this;
2079 }
2080 
operator ++(int)2081 coord_def map_lines::iterator::operator ++ (int)
2082 {
2083     coord_def here(**this);
2084     ++*this;
2085     return here;
2086 }
2087 
2088 ///////////////////////////////////////////////
2089 // dlua_set_map
2090 
dlua_set_map(map_def * map)2091 dlua_set_map::dlua_set_map(map_def *map)
2092 {
2093     clua_push_map(dlua, map);
2094     if (!dlua.callfn("dgn_set_map", 1, 1))
2095     {
2096         mprf(MSGCH_ERROR, "dgn_set_map failed for '%s': %s",
2097              map->name.c_str(), dlua.error.c_str());
2098     }
2099     // Save the returned map as a lua_datum
2100     old_map.reset(new lua_datum(dlua));
2101 }
2102 
~dlua_set_map()2103 dlua_set_map::~dlua_set_map()
2104 {
2105     old_map->push();
2106     if (!dlua.callfn("dgn_set_map", 1, 0))
2107         mprf(MSGCH_ERROR, "dgn_set_map failed: %s", dlua.error.c_str());
2108 }
2109 
2110 ///////////////////////////////////////////////
2111 // map_chance
2112 
describe() const2113 string map_chance::describe() const
2114 {
2115     return make_stringf("%d", chance);
2116 }
2117 
roll() const2118 bool map_chance::roll() const
2119 {
2120     return random2(CHANCE_ROLL) < chance;
2121 }
2122 
write(writer & outf) const2123 void map_chance::write(writer &outf) const
2124 {
2125     marshallInt(outf, chance);
2126 }
2127 
read(reader & inf)2128 void map_chance::read(reader &inf)
2129 {
2130 #if TAG_MAJOR_VERSION == 34
2131     if (inf.getMinorVersion() < TAG_MINOR_NO_PRIORITY)
2132         unmarshallInt(inf); // was chance_priority
2133 #endif
2134     chance = unmarshallInt(inf);
2135 }
2136 
2137 ///////////////////////////////////////////////
2138 // depth_ranges
2139 
write(writer & outf) const2140 void depth_ranges::write(writer& outf) const
2141 {
2142     marshallShort(outf, depths.size());
2143     for (const level_range &depth : depths)
2144         depth.write(outf);
2145 }
2146 
read(reader & inf)2147 void depth_ranges::read(reader &inf)
2148 {
2149     depths.clear();
2150     const int nranges = unmarshallShort(inf);
2151     for (int i = 0; i < nranges; ++i)
2152     {
2153         level_range lr;
2154         lr.read(inf);
2155         depths.push_back(lr);
2156     }
2157 }
2158 
parse_depth_ranges(const string & depth_range_string)2159 depth_ranges depth_ranges::parse_depth_ranges(const string &depth_range_string)
2160 {
2161     depth_ranges ranges;
2162     for (const string &frag : split_string(",", depth_range_string))
2163         ranges.depths.push_back(level_range::parse(frag));
2164     return ranges;
2165 }
2166 
is_usable_in(const level_id & lid) const2167 bool depth_ranges::is_usable_in(const level_id &lid) const
2168 {
2169     bool any_matched = false;
2170     for (const level_range &lr : depths)
2171     {
2172         if (lr.matches(lid))
2173         {
2174             if (lr.deny)
2175                 return false;
2176             any_matched = true;
2177         }
2178     }
2179     return any_matched;
2180 }
2181 
add_depths(const depth_ranges & other_depths)2182 void depth_ranges::add_depths(const depth_ranges &other_depths)
2183 {
2184     depths.insert(depths.end(),
2185                   other_depths.depths.begin(),
2186                   other_depths.depths.end());
2187 }
2188 
describe() const2189 string depth_ranges::describe() const
2190 {
2191     return comma_separated_line(depths.begin(), depths.end(), ", ", ", ");
2192 }
2193 
2194 ///////////////////////////////////////////////
2195 // map_def
2196 //
2197 
2198 const int DEFAULT_MAP_WEIGHT = 10;
map_def()2199 map_def::map_def()
2200     : name(), description(), order(INT_MAX), place(), depths(),
2201       orient(), _chance(), _weight(DEFAULT_MAP_WEIGHT),
2202       map(), mons(), items(), random_mons(),
2203       prelude("dlprelude"), mapchunk("dlmapchunk"), main("dlmain"),
2204       validate("dlvalidate"), veto("dlveto"), epilogue("dlepilogue"),
2205       rock_colour(BLACK), floor_colour(BLACK), rock_tile(""),
2206       floor_tile(""), border_fill_type(DNGN_ROCK_WALL),
2207       tags(),
2208       index_only(false), cache_offset(0L), validating_map_flag(false),
2209       cache_minivault(false), cache_overwritable(false), cache_extra(false)
2210 {
2211     init();
2212 }
2213 
init()2214 void map_def::init()
2215 {
2216     orient = MAP_NONE;
2217     name.clear();
2218     description.clear();
2219     order = INT_MAX;
2220     tags.clear();
2221     place.clear();
2222     depths.clear();
2223     prelude.clear();
2224     mapchunk.clear();
2225     main.clear();
2226     validate.clear();
2227     veto.clear();
2228     epilogue.clear();
2229     place_loaded_from.clear();
2230     reinit();
2231 
2232     // Subvault mask set and cleared externally.
2233     // It should *not* be in reinit.
2234     svmask = nullptr;
2235 }
2236 
reinit()2237 void map_def::reinit()
2238 {
2239     description.clear();
2240     order = INT_MAX;
2241     items.clear();
2242     random_mons.clear();
2243 
2244     rock_colour = floor_colour = BLACK;
2245     rock_tile = floor_tile = "";
2246     border_fill_type = DNGN_ROCK_WALL;
2247 
2248     // Chance of using this level. Nonzero chance should be used
2249     // sparingly. When selecting vaults for a place, first those
2250     // vaults with chance > 0 are considered, in the order they were
2251     // loaded (which is arbitrary). If random2(100) < chance, the
2252     // vault is picked, and all other vaults are ignored for that
2253     // random selection. weight is ignored if the vault is chosen
2254     // based on its chance.
2255     _chance.clear();
2256 
2257     // Weight for this map. When selecting a map, if no map with a
2258     // nonzero chance is picked, one of the other eligible vaults is
2259     // picked with a probability of weight / (sum of weights of all
2260     // eligible vaults).
2261     _weight.clear(DEFAULT_MAP_WEIGHT);
2262 
2263     // Clearing the map also zaps map transforms.
2264     map.clear();
2265     mons.clear();
2266     feat_renames.clear();
2267     subvault_places.clear();
2268     update_cached_tags();
2269 }
2270 
reload_epilogue()2271 void map_def::reload_epilogue()
2272 {
2273     if (!epilogue.empty())
2274         return;
2275     // reload the epilogue from the current .des cache; this is because it
2276     // isn't serialized but with pregen orderings could need to be run after
2277     // vaults have been generated, saved, and reloaded. This can be a tricky
2278     // situation in save-compat terms, but is exactly the same as how lua code
2279     // triggered by markers is currently handled.
2280     const map_def *cache_version = find_map_by_name(name);
2281     if (cache_version)
2282     {
2283         map_def tmp = *cache_version;
2284         // TODO: I couldn't reliably get the epilogue to be in vdefs, is there
2285         // a way to cache it and not do a full load here? The original idea
2286         // was to not clear it out in strip(), but this fails under some
2287         // circumstances.
2288         tmp.load();
2289         epilogue = tmp.epilogue;
2290     }
2291     // for save compat reasons, fail silently if the map is no longer around.
2292     // Probably shouldn't do anything really crucial in the epilogue that you
2293     // aren't prepared to deal with save compat for somehow...
2294 }
2295 
map_already_used() const2296 bool map_def::map_already_used() const
2297 {
2298     return get_uniq_map_names().count(name)
2299            || env.level_uniq_maps.find(name) !=
2300                env.level_uniq_maps.end()
2301            || env.new_used_subvault_names.find(name) !=
2302                env.new_used_subvault_names.end()
2303            || has_any_tag(get_uniq_map_tags().begin(),
2304                           get_uniq_map_tags().end())
2305            || has_any_tag(env.level_uniq_map_tags.begin(),
2306                           env.level_uniq_map_tags.end())
2307            || has_any_tag(env.new_used_subvault_tags.begin(),
2308                           env.new_used_subvault_tags.end());
2309 }
2310 
valid_item_array_glyph(int gly)2311 bool map_def::valid_item_array_glyph(int gly)
2312 {
2313     return gly >= 'd' && gly <= 'k';
2314 }
2315 
item_array_glyph_to_slot(int gly)2316 int map_def::item_array_glyph_to_slot(int gly)
2317 {
2318     ASSERT(map_def::valid_item_array_glyph(gly));
2319     return gly - 'd';
2320 }
2321 
valid_monster_glyph(int gly)2322 bool map_def::valid_monster_glyph(int gly)
2323 {
2324     return gly >= '0' && gly <= '9';
2325 }
2326 
valid_monster_array_glyph(int gly)2327 bool map_def::valid_monster_array_glyph(int gly)
2328 {
2329     return gly >= '1' && gly <= '7';
2330 }
2331 
monster_array_glyph_to_slot(int gly)2332 int map_def::monster_array_glyph_to_slot(int gly)
2333 {
2334     ASSERT(map_def::valid_monster_array_glyph(gly));
2335     return gly - '1';
2336 }
2337 
in_map(const coord_def & c) const2338 bool map_def::in_map(const coord_def &c) const
2339 {
2340     return map.in_map(c);
2341 }
2342 
glyph_at(const coord_def & c) const2343 int map_def::glyph_at(const coord_def &c) const
2344 {
2345     return map(c);
2346 }
2347 
name_at(const coord_def & c) const2348 string map_def::name_at(const coord_def &c) const
2349 {
2350     vector<string> names;
2351     names.push_back(name);
2352     for (const subvault_place& subvault : subvault_places)
2353     {
2354         if (c.x >= subvault.tl.x && c.x <= subvault.br.x &&
2355             c.y >= subvault.tl.y && c.y <= subvault.br.y &&
2356             subvault.subvault->in_map(c - subvault.tl))
2357         {
2358             names.push_back(subvault.subvault->name_at(c - subvault.tl));
2359         }
2360     }
2361     return comma_separated_line(names.begin(), names.end(), ", ", ", ");
2362 }
2363 
desc_or_name() const2364 string map_def::desc_or_name() const
2365 {
2366     return description.empty()? name : description;
2367 }
2368 
write_full(writer & outf) const2369 void map_def::write_full(writer& outf) const
2370 {
2371     cache_offset = outf.tell();
2372     write_save_version(outf, save_version::current());
2373     marshallString4(outf, name);
2374     prelude.write(outf);
2375     mapchunk.write(outf);
2376     main.write(outf);
2377     validate.write(outf);
2378     veto.write(outf);
2379     epilogue.write(outf);
2380 }
2381 
read_full(reader & inf)2382 void map_def::read_full(reader& inf)
2383 {
2384     // There's a potential race-condition here:
2385     // - If someone modifies a .des file while there are games in progress,
2386     // - a new Crawl process will overwrite the .dsc.
2387     // - older Crawl processes trying to reading the new .dsc will be hosed.
2388     // We could try to recover from the condition (by locking and
2389     // reloading the index), but it's easier to save the game at this
2390     // point and let the player reload.
2391 
2392     const auto version = get_save_version(inf);
2393     const auto major = version.major, minor = version.minor;
2394 
2395     if (major != TAG_MAJOR_VERSION || minor > TAG_MINOR_VERSION)
2396     {
2397         throw map_load_exception(make_stringf(
2398             "Map was built for a different version of Crawl (%s) "
2399             "(map: %d.%d us: %d.%d)",
2400             name.c_str(), int(major), int(minor),
2401             TAG_MAJOR_VERSION, TAG_MINOR_VERSION));
2402     }
2403 
2404     string fp_name;
2405     unmarshallString4(inf, fp_name);
2406 
2407     if (fp_name != name)
2408     {
2409         throw map_load_exception(make_stringf(
2410             "Map fp_name (%s) != name (%s)!",
2411             fp_name.c_str(), name.c_str()));
2412     }
2413 
2414     prelude.read(inf);
2415     mapchunk.read(inf);
2416     main.read(inf);
2417     validate.read(inf);
2418     veto.read(inf);
2419     epilogue.read(inf);
2420 }
2421 
weight(const level_id & lid) const2422 int map_def::weight(const level_id &lid) const
2423 {
2424     return _weight.depth_value(lid);
2425 }
2426 
chance(const level_id & lid) const2427 map_chance map_def::chance(const level_id &lid) const
2428 {
2429     return _chance.depth_value(lid);
2430 }
2431 
describe() const2432 string map_def::describe() const
2433 {
2434     return make_stringf("Map: %s\n%s%s%s%s%s%s",
2435                         name.c_str(),
2436                         prelude.describe("prelude").c_str(),
2437                         mapchunk.describe("mapchunk").c_str(),
2438                         main.describe("main").c_str(),
2439                         validate.describe("validate").c_str(),
2440                         veto.describe("veto").c_str(),
2441                         epilogue.describe("epilogue").c_str());
2442 }
2443 
strip()2444 void map_def::strip()
2445 {
2446     if (index_only)
2447         return;
2448 
2449     index_only = true;
2450     map.clear();
2451     mons.clear();
2452     items.clear();
2453     random_mons.clear();
2454     prelude.clear();
2455     mapchunk.clear();
2456     main.clear();
2457     validate.clear();
2458     veto.clear();
2459     epilogue.clear();
2460     feat_renames.clear();
2461 }
2462 
load()2463 void map_def::load()
2464 {
2465     if (!index_only)
2466         return;
2467 
2468     const string descache_base = get_descache_path(cache_name, "");
2469     file_lock deslock(descache_base + ".lk", "rb", false);
2470     const string loadfile = descache_base + ".dsc";
2471 
2472     reader inf(loadfile, TAG_MINOR_VERSION);
2473     if (!inf.valid())
2474     {
2475         throw map_load_exception(
2476                 make_stringf("Map inf is invalid: %s", name.c_str()));
2477     }
2478     inf.advance(cache_offset);
2479     read_full(inf);
2480 
2481     index_only = false;
2482 }
2483 
find_glyph(int glyph) const2484 vector<coord_def> map_def::find_glyph(int glyph) const
2485 {
2486     return map.find_glyph(glyph);
2487 }
2488 
find_first_glyph(int glyph) const2489 coord_def map_def::find_first_glyph(int glyph) const
2490 {
2491     return map.find_first_glyph(glyph);
2492 }
2493 
find_first_glyph(const string & s) const2494 coord_def map_def::find_first_glyph(const string &s) const
2495 {
2496     return map.find_first_glyph(s);
2497 }
2498 
write_maplines(writer & outf) const2499 void map_def::write_maplines(writer &outf) const
2500 {
2501     map.write_maplines(outf);
2502 }
2503 
_marshall_map_chance(writer & th,const map_chance & chance)2504 static void _marshall_map_chance(writer &th, const map_chance &chance)
2505 {
2506     chance.write(th);
2507 }
2508 
_unmarshall_map_chance(reader & th)2509 static map_chance _unmarshall_map_chance(reader &th)
2510 {
2511     map_chance chance;
2512     chance.read(th);
2513     return chance;
2514 }
2515 
write_index(writer & outf) const2516 void map_def::write_index(writer& outf) const
2517 {
2518     if (!cache_offset)
2519     {
2520         end(1, false, "Map %s: can't write index - cache offset not set!",
2521             name.c_str());
2522     }
2523     marshallString4(outf, name);
2524     marshallString4(outf, place_loaded_from.filename);
2525     marshallInt(outf, place_loaded_from.lineno);
2526     marshallShort(outf, orient);
2527     // XXX: This is a hack. See the comment in l-dgn.cc.
2528     marshallShort(outf, static_cast<short>(border_fill_type));
2529     _chance.write(outf, _marshall_map_chance);
2530     _weight.write(outf, marshallInt);
2531     marshallInt(outf, cache_offset);
2532     marshallString4(outf, tags_string());
2533     place.write(outf);
2534     depths.write(outf);
2535     prelude.write(outf);
2536 }
2537 
read_maplines(reader & inf)2538 void map_def::read_maplines(reader &inf)
2539 {
2540     map.read_maplines(inf);
2541 }
2542 
read_index(reader & inf)2543 void map_def::read_index(reader& inf)
2544 {
2545     unmarshallString4(inf, name);
2546     unmarshallString4(inf, place_loaded_from.filename);
2547     place_loaded_from.lineno = unmarshallInt(inf);
2548     orient = static_cast<map_section_type>(unmarshallShort(inf));
2549     // XXX: Hack. See the comment in l-dgn.cc.
2550     border_fill_type =
2551         static_cast<dungeon_feature_type>(unmarshallShort(inf));
2552 
2553     _chance = range_chance_t::read(inf, _unmarshall_map_chance);
2554     _weight = range_weight_t::read(inf, unmarshallInt);
2555     cache_offset = unmarshallInt(inf);
2556     string read_tags;
2557     unmarshallString4(inf, read_tags);
2558     set_tags(read_tags);
2559     place.read(inf);
2560     depths.read(inf);
2561     prelude.read(inf);
2562     index_only = true;
2563 }
2564 
set_file(const string & s)2565 void map_def::set_file(const string &s)
2566 {
2567     prelude.set_file(s);
2568     mapchunk.set_file(s);
2569     main.set_file(s);
2570     validate.set_file(s);
2571     veto.set_file(s);
2572     epilogue.set_file(s);
2573     file = get_base_filename(s);
2574     cache_name = get_cache_name(s);
2575 }
2576 
run_lua(bool run_main)2577 string map_def::run_lua(bool run_main)
2578 {
2579     dlua_set_map mset(this);
2580 
2581     int err = prelude.load(dlua);
2582     if (err == E_CHUNK_LOAD_FAILURE)
2583         lua_pushnil(dlua);
2584     else if (err)
2585         return prelude.orig_error();
2586     if (!dlua.callfn("dgn_run_map", 1, 0))
2587         return rewrite_chunk_errors(dlua.error);
2588 
2589     if (run_main)
2590     {
2591         // Run the map chunk to set up the vault's map grid.
2592         err = mapchunk.load(dlua);
2593         if (err == E_CHUNK_LOAD_FAILURE)
2594             lua_pushnil(dlua);
2595         else if (err)
2596             return mapchunk.orig_error();
2597         if (!dlua.callfn("dgn_run_map", 1, 0))
2598             return rewrite_chunk_errors(dlua.error);
2599 
2600         // The vault may be non-rectangular with a ragged-right edge; for
2601         // transforms to work right at this point, we must pad out the right
2602         // edge with spaces, so run normalise:
2603         normalise();
2604 
2605         // Run the main Lua chunk to set up the rest of the vault
2606         run_hook("pre_main");
2607         err = main.load(dlua);
2608         if (err == E_CHUNK_LOAD_FAILURE)
2609             lua_pushnil(dlua);
2610         else if (err)
2611             return main.orig_error();
2612         if (!dlua.callfn("dgn_run_map", 1, 0))
2613             return rewrite_chunk_errors(dlua.error);
2614         run_hook("post_main");
2615     }
2616 
2617     return dlua.error;
2618 }
2619 
copy_hooks_from(const map_def & other_map,const string & hook_name)2620 void map_def::copy_hooks_from(const map_def &other_map, const string &hook_name)
2621 {
2622     const dlua_set_map mset(this);
2623     if (!dlua.callfn("dgn_map_copy_hooks_from", "ss",
2624                      other_map.name.c_str(), hook_name.c_str()))
2625     {
2626         mprf(MSGCH_ERROR, "Lua error copying hook (%s) from '%s' to '%s': %s",
2627              hook_name.c_str(), other_map.name.c_str(),
2628              name.c_str(), dlua.error.c_str());
2629     }
2630 }
2631 
2632 // Runs Lua hooks registered by the map's Lua code, if any. Returns true if
2633 // no errors occurred while running hooks.
run_hook(const string & hook_name,bool die_on_lua_error)2634 bool map_def::run_hook(const string &hook_name, bool die_on_lua_error)
2635 {
2636     const dlua_set_map mset(this);
2637     if (!dlua.callfn("dgn_map_run_hook", "s", hook_name.c_str()))
2638     {
2639         const string error = rewrite_chunk_errors(dlua.error);
2640         // only show the error message if this isn't a hook map-placement
2641         // failure, which should just lead to a silent veto.
2642         if (error.find("Failed to place map") == string::npos)
2643         {
2644             if (die_on_lua_error)
2645             {
2646                 end(1, false, "Lua error running hook '%s' on map '%s': %s",
2647                     hook_name.c_str(), name.c_str(), error.c_str());
2648             }
2649             else
2650             {
2651                 mprf(MSGCH_ERROR, "Lua error running hook '%s' on map '%s': %s",
2652                      hook_name.c_str(), name.c_str(), error.c_str());
2653             }
2654         }
2655         return false;
2656     }
2657     return true;
2658 }
2659 
run_postplace_hook(bool die_on_lua_error)2660 bool map_def::run_postplace_hook(bool die_on_lua_error)
2661 {
2662     return run_hook("post_place", die_on_lua_error);
2663 }
2664 
test_lua_boolchunk(dlua_chunk & chunk,bool defval,bool die_on_lua_error)2665 bool map_def::test_lua_boolchunk(dlua_chunk &chunk, bool defval,
2666                                  bool die_on_lua_error)
2667 {
2668     bool result = defval;
2669     dlua_set_map mset(this);
2670 
2671     int err = chunk.load(dlua);
2672     if (err == E_CHUNK_LOAD_FAILURE)
2673         return result;
2674     else if (err)
2675     {
2676         if (die_on_lua_error)
2677             end(1, false, "Lua error: %s", chunk.orig_error().c_str());
2678         else
2679             mprf(MSGCH_ERROR, "Lua error: %s", chunk.orig_error().c_str());
2680         return result;
2681     }
2682     if (dlua.callfn("dgn_run_map", 1, 1))
2683         dlua.fnreturns(">b", &result);
2684     else
2685     {
2686         if (die_on_lua_error)
2687         {
2688             end(1, false, "Lua error: %s",
2689                 rewrite_chunk_errors(dlua.error).c_str());
2690         }
2691         else
2692         {
2693             mprf(MSGCH_ERROR, "Lua error: %s",
2694                  rewrite_chunk_errors(dlua.error).c_str());
2695         }
2696     }
2697     return result;
2698 }
2699 
test_lua_validate(bool croak)2700 bool map_def::test_lua_validate(bool croak)
2701 {
2702     return validate.empty() || test_lua_boolchunk(validate, false, croak);
2703 }
2704 
test_lua_veto()2705 bool map_def::test_lua_veto()
2706 {
2707     return !veto.empty() && test_lua_boolchunk(veto, true);
2708 }
2709 
run_lua_epilogue(bool die_on_lua_error)2710 bool map_def::run_lua_epilogue(bool die_on_lua_error)
2711 {
2712     run_hook("pre_epilogue", die_on_lua_error);
2713     const bool epilogue_result =
2714         !epilogue.empty() && test_lua_boolchunk(epilogue, false,
2715                                                 die_on_lua_error);
2716     run_hook("post_epilogue", die_on_lua_error);
2717     return epilogue_result;
2718 }
2719 
rewrite_chunk_errors(const string & s) const2720 string map_def::rewrite_chunk_errors(const string &s) const
2721 {
2722     string res = s;
2723     if (prelude.rewrite_chunk_errors(res))
2724         return res;
2725     if (mapchunk.rewrite_chunk_errors(res))
2726         return res;
2727     if (main.rewrite_chunk_errors(res))
2728         return res;
2729     if (validate.rewrite_chunk_errors(res))
2730         return res;
2731     if (veto.rewrite_chunk_errors(res))
2732         return res;
2733     epilogue.rewrite_chunk_errors(res);
2734     return res;
2735 }
2736 
validate_temple_map()2737 string map_def::validate_temple_map()
2738 {
2739     vector<coord_def> altars = find_glyph('B');
2740 
2741     if (has_tag_prefix("temple_overflow_"))
2742     {
2743         if (has_tag_prefix("temple_overflow_generic_"))
2744         {
2745             string matching_tag = make_stringf("temple_overflow_generic_%u",
2746                 (unsigned int) altars.size());
2747             if (!has_tag(matching_tag))
2748             {
2749                 return make_stringf(
2750                     "Temple ('%s') has %u altars and a "
2751                     "'temple_overflow_generic_' tag, but does not match the "
2752                     "number of altars: should have at least '%s'.",
2753                                     tags_string().c_str(),
2754                                     (unsigned int) altars.size(),
2755                                     matching_tag.c_str());
2756             }
2757         }
2758         else
2759         {
2760             // Assume specialised altar vaults are set up correctly.
2761             return "";
2762         }
2763     }
2764 
2765     if (altars.empty())
2766         return "Temple vault must contain at least one altar.";
2767 
2768     // TODO: check for substitutions and shuffles
2769 
2770     vector<coord_def> b_glyphs = map.find_glyph('B');
2771     for (auto c : b_glyphs)
2772     {
2773         const keyed_mapspec *spec = map.mapspec_at(c);
2774         if (spec != nullptr && !spec->feat.feats.empty())
2775             return "Can't change feat 'B' in temple (KFEAT)";
2776     }
2777 
2778     vector<god_type> god_list = temple_god_list();
2779 
2780     if (altars.size() > god_list.size())
2781         return "Temple vault has too many altars";
2782 
2783     return "";
2784 }
2785 
validate_map_placeable()2786 string map_def::validate_map_placeable()
2787 {
2788     if (has_depth() || !place.empty())
2789         return "";
2790 
2791     // Ok, the map wants to be placed by tag. In this case it should have
2792     // at least one tag that's not a map flag.
2793     bool has_selectable_tag = false;
2794     for (const string &piece : tags)
2795     {
2796         if (_map_tag_is_selectable(piece))
2797         {
2798             has_selectable_tag = true;
2799             break;
2800         }
2801     }
2802 
2803     return has_selectable_tag? "" :
2804            make_stringf("Map '%s' has no DEPTH, no PLACE and no "
2805                         "selectable tag in '%s'",
2806                         name.c_str(), tags_string().c_str());
2807 }
2808 
2809 /**
2810  * Check to see if the vault can connect normally to the rest of the dungeon.
2811  */
has_exit() const2812 bool map_def::has_exit() const
2813 {
2814     map_def dup = *this;
2815     for (int y = 0, cheight = map.height(); y < cheight; ++y)
2816         for (int x = 0, cwidth = map.width(); x < cwidth; ++x)
2817         {
2818             if (!map.in_map(coord_def(x, y)))
2819                 continue;
2820             const char glyph = map.glyph(x, y);
2821             dungeon_feature_type feat =
2822                 map_feature_at(&dup, coord_def(x, y), -1);
2823             // If we have a stair, assume the vault can be disconnected.
2824             if (feat_is_stair(feat) && !feat_is_escape_hatch(feat))
2825                 return true;
2826             const bool non_floating =
2827                 glyph == '@' || glyph == '=' || glyph == '+';
2828             if (non_floating
2829                 || !feat_is_solid(feat) || feat_is_closed_door(feat))
2830             {
2831                 if (x == 0 || x == cwidth - 1 || y == 0 || y == cheight - 1)
2832                     return true;
2833                 for (orth_adjacent_iterator ai(coord_def(x, y)); ai; ++ai)
2834                     if (!map.in_map(*ai))
2835                         return true;
2836             }
2837         }
2838 
2839     return false;
2840 }
2841 
validate_map_def(const depth_ranges & default_depths)2842 string map_def::validate_map_def(const depth_ranges &default_depths)
2843 {
2844     UNUSED(default_depths);
2845 
2846     unwind_bool valid_flag(validating_map_flag, true);
2847 
2848     string err = run_lua(true);
2849     if (!err.empty())
2850         return err;
2851 
2852     fixup();
2853     resolve();
2854     test_lua_validate(true);
2855     run_lua_epilogue(true);
2856 
2857     if (!has_depth() && !lc_default_depths.empty())
2858         depths.add_depths(lc_default_depths);
2859 
2860     if (place.is_usable_in(level_id(BRANCH_TEMPLE))
2861         || has_tag_prefix("temple_overflow_"))
2862     {
2863         err = validate_temple_map();
2864         if (!err.empty())
2865             return err;
2866     }
2867 
2868     if (has_tag("overwrite_floor_cell") && (map.width() != 1 || map.height() != 1))
2869         return "Map tagged 'overwrite_floor_cell' must be 1x1";
2870 
2871     // Abyssal vaults have additional size and orientation restrictions.
2872     if (has_tag("abyss") || has_tag("abyss_rune"))
2873     {
2874         if (orient == MAP_ENCOMPASS)
2875         {
2876             return make_stringf(
2877                 "Map '%s' cannot use 'encompass' orientation in the abyss",
2878                 name.c_str());
2879         }
2880 
2881         const int max_abyss_map_width =
2882             GXM / 2 - MAPGEN_BORDER - ABYSS_AREA_SHIFT_RADIUS;
2883         const int max_abyss_map_height =
2884             GYM / 2 - MAPGEN_BORDER - ABYSS_AREA_SHIFT_RADIUS;
2885 
2886         if (map.width() > max_abyss_map_width
2887             || map.height() > max_abyss_map_height)
2888         {
2889             return make_stringf(
2890                 "Map '%s' is too big for the Abyss: %dx%d - max %dx%d",
2891                 name.c_str(),
2892                 map.width(), map.height(),
2893                 max_abyss_map_width, max_abyss_map_height);
2894         }
2895 
2896         // Unless both height and width fit in the smaller dimension,
2897         // map rotation will be disallowed.
2898         const int dimension_lower_bound =
2899             min(max_abyss_map_height, max_abyss_map_width);
2900         if ((map.width() > dimension_lower_bound
2901              || map.height() > dimension_lower_bound)
2902             && !has_tag("no_rotate"))
2903         {
2904             add_tags("no_rotate");
2905         }
2906     }
2907 
2908     if (orient == MAP_FLOAT || is_minivault())
2909     {
2910         if (map.width() > GXM - MAPGEN_BORDER * 2
2911             || map.height() > GYM - MAPGEN_BORDER * 2)
2912         {
2913             return make_stringf(
2914                      "%s '%s' is too big: %dx%d - max %dx%d",
2915                      is_minivault()? "Minivault" : "Float",
2916                      name.c_str(),
2917                      map.width(), map.height(),
2918                      GXM - MAPGEN_BORDER * 2,
2919                      GYM - MAPGEN_BORDER * 2);
2920         }
2921     }
2922     else
2923     {
2924         if (map.width() > GXM || map.height() > GYM)
2925         {
2926             return make_stringf(
2927                      "Map '%s' is too big: %dx%d - max %dx%d",
2928                      name.c_str(),
2929                      map.width(), map.height(),
2930                      GXM, GYM);
2931         }
2932     }
2933 
2934     switch (orient)
2935     {
2936     case MAP_NORTH: case MAP_SOUTH:
2937         if (map.height() > GYM * 2 / 3)
2938         {
2939             return make_stringf("Map too large - height %d (max %d)",
2940                                 map.height(), GYM * 2 / 3);
2941         }
2942         break;
2943     case MAP_EAST: case MAP_WEST:
2944         if (map.width() > GXM * 2 / 3)
2945         {
2946             return make_stringf("Map too large - width %d (max %d)",
2947                                 map.width(), GXM * 2 / 3);
2948         }
2949         break;
2950     case MAP_NORTHEAST: case MAP_SOUTHEAST:
2951     case MAP_NORTHWEST: case MAP_SOUTHWEST:
2952     case MAP_FLOAT:     case MAP_CENTRE:
2953         if (map.width() > GXM * 2 / 3 || map.height() > GYM * 2 / 3)
2954         {
2955             return make_stringf("Map too large - %dx%d (max %dx%d)",
2956                                 map.width(), map.height(),
2957                                 GXM * 2 / 3, GYM * 2 / 3);
2958         }
2959         break;
2960     default:
2961         break;
2962     }
2963 
2964     // Encompass vaults, pure subvaults, and dummy vaults are exempt from
2965     // exit-checking.
2966     if (orient != MAP_ENCOMPASS && !has_tag("unrand") && !has_tag("dummy")
2967         && !has_tag("no_exits") && map.width() > 0 && map.height() > 0)
2968     {
2969         if (!has_exit())
2970         {
2971             return make_stringf(
2972                 "Map '%s' has no (possible) exits; use TAGS: no_exits if "
2973                 "this is intentional",
2974                 name.c_str());
2975         }
2976     }
2977 
2978     dlua_set_map dl(this);
2979     return validate_map_placeable();
2980 }
2981 
is_usable_in(const level_id & lid) const2982 bool map_def::is_usable_in(const level_id &lid) const
2983 {
2984     return depths.is_usable_in(lid);
2985 }
2986 
add_depth(const level_range & range)2987 void map_def::add_depth(const level_range &range)
2988 {
2989     depths.add_depth(range);
2990 }
2991 
has_depth() const2992 bool map_def::has_depth() const
2993 {
2994     return !depths.empty();
2995 }
2996 
update_cached_tags()2997 void map_def::update_cached_tags()
2998 {
2999     cache_minivault = has_tag("minivault");
3000     cache_overwritable = has_tag("overwritable");
3001     cache_extra = has_tag("extra");
3002 }
3003 
is_minivault() const3004 bool map_def::is_minivault() const
3005 {
3006 #ifdef DEBUG_TAG_PROFILING
3007     ASSERT(cache_minivault == has_tag("minivault"));
3008 #endif
3009     return cache_minivault;
3010 }
3011 
3012 // Returns true if the map is a layout that allows other vaults to be
3013 // built on it.
is_overwritable_layout() const3014 bool map_def::is_overwritable_layout() const
3015 {
3016 #ifdef DEBUG_TAG_PROFILING
3017     ASSERT(cache_overwritable == has_tag("overwritable"));
3018 #endif
3019     return cache_overwritable;
3020 }
3021 
is_extra_vault() const3022 bool map_def::is_extra_vault() const
3023 {
3024 #ifdef DEBUG_TAG_PROFILING
3025     ASSERT(cache_extra == has_tag("extra"));
3026 #endif
3027     return cache_extra;
3028 }
3029 
3030 // Tries to dock a floating vault - push it to one edge of the level.
3031 // Docking will only succeed if two contiguous edges are all x/c/b/v
3032 // (other walls prevent docking). If the vault's width is > GXM*2/3,
3033 // it's also eligible for north/south docking, and if the height >
3034 // GYM*2/3, it's eligible for east/west docking. Although docking is
3035 // similar to setting the orientation, it doesn't affect 'orient'.
float_dock()3036 coord_def map_def::float_dock()
3037 {
3038     const map_section_type orients[] =
3039         { MAP_NORTH, MAP_SOUTH, MAP_EAST, MAP_WEST,
3040           MAP_NORTHEAST, MAP_SOUTHEAST, MAP_NORTHWEST, MAP_SOUTHWEST };
3041     map_section_type which_orient = MAP_NONE;
3042     int norients = 0;
3043 
3044     for (map_section_type sec : orients)
3045     {
3046         if (map.solid_borders(sec) && can_dock(sec)
3047             && one_chance_in(++norients))
3048         {
3049             which_orient = sec;
3050         }
3051     }
3052 
3053     if (which_orient == MAP_NONE || which_orient == MAP_FLOAT)
3054         return coord_def(-1, -1);
3055 
3056     dprf(DIAG_DNGN, "Docking floating vault to %s",
3057          map_section_name(which_orient));
3058 
3059     return dock_pos(which_orient);
3060 }
3061 
dock_pos(map_section_type norient) const3062 coord_def map_def::dock_pos(map_section_type norient) const
3063 {
3064     const int minborder = 6;
3065 
3066     switch (norient)
3067     {
3068     case MAP_NORTH:
3069         return coord_def((GXM - map.width()) / 2, minborder);
3070     case MAP_SOUTH:
3071         return coord_def((GXM - map.width()) / 2,
3072                           GYM - minborder - map.height());
3073     case MAP_EAST:
3074         return coord_def(GXM - minborder - map.width(),
3075                           (GYM - map.height()) / 2);
3076     case MAP_WEST:
3077         return coord_def(minborder,
3078                           (GYM - map.height()) / 2);
3079     case MAP_NORTHEAST:
3080         return coord_def(GXM - minborder - map.width(), minborder);
3081     case MAP_NORTHWEST:
3082         return coord_def(minborder, minborder);
3083     case MAP_SOUTHEAST:
3084         return coord_def(GXM - minborder - map.width(),
3085                           GYM - minborder - map.height());
3086     case MAP_SOUTHWEST:
3087         return coord_def(minborder,
3088                           GYM - minborder - map.height());
3089     case MAP_CENTRE:
3090         return coord_def((GXM - map.width())  / 2,
3091                          (GYM - map.height()) / 2);
3092     default:
3093         return coord_def(-1, -1);
3094     }
3095 }
3096 
can_dock(map_section_type norient) const3097 bool map_def::can_dock(map_section_type norient) const
3098 {
3099     switch (norient)
3100     {
3101     case MAP_NORTH: case MAP_SOUTH:
3102         return map.width() > GXM * 2 / 3;
3103     case MAP_EAST: case MAP_WEST:
3104         return map.height() > GYM * 2 / 3;
3105     default:
3106         return true;
3107     }
3108 }
3109 
float_random_place() const3110 coord_def map_def::float_random_place() const
3111 {
3112     // Try to leave enough around the float for roomification.
3113     int minhborder = MAPGEN_BORDER + 11,
3114         minvborder = minhborder;
3115 
3116     if (GXM - 2 * minhborder < map.width())
3117         minhborder = (GXM - map.width()) / 2 - 1;
3118 
3119     if (GYM - 2 * minvborder < map.height())
3120         minvborder = (GYM - map.height()) / 2 - 1;
3121 
3122     coord_def result;
3123     result.x = random_range(minhborder, GXM - minhborder - map.width());
3124     result.y = random_range(minvborder, GYM - minvborder - map.height());
3125     return result;
3126 }
3127 
anchor_points() const3128 point_vector map_def::anchor_points() const
3129 {
3130     point_vector points;
3131     for (int y = 0, cheight = map.height(); y < cheight; ++y)
3132         for (int x = 0, cwidth = map.width(); x < cwidth; ++x)
3133             if (map.glyph(x, y) == '@')
3134                 points.emplace_back(x, y);
3135     return points;
3136 }
3137 
float_aligned_place() const3138 coord_def map_def::float_aligned_place() const
3139 {
3140     const point_vector our_anchors = anchor_points();
3141     const coord_def fail(-1, -1);
3142 
3143     dprf(DIAG_DNGN, "Aligning floating vault with %u points vs %u"
3144                     " reference points",
3145                     (unsigned int)our_anchors.size(),
3146                     (unsigned int)map_anchor_points.size());
3147 
3148     // Mismatch in the number of points we have to align, bail.
3149     if (our_anchors.size() != map_anchor_points.size())
3150         return fail;
3151 
3152     // Align first point of both vectors, then check that the others match.
3153     const coord_def pos = map_anchor_points[0] - our_anchors[0];
3154 
3155     for (int i = 1, psize = map_anchor_points.size(); i < psize; ++i)
3156         if (pos + our_anchors[i] != map_anchor_points[i])
3157             return fail;
3158 
3159     // Looking good, check bounds.
3160     if (!map_bounds(pos) || !map_bounds(pos + size() - 1))
3161         return fail;
3162 
3163     // Go us!
3164     return pos;
3165 }
3166 
float_place()3167 coord_def map_def::float_place()
3168 {
3169     ASSERT(orient == MAP_FLOAT);
3170 
3171     coord_def pos(-1, -1);
3172 
3173     if (!map_anchor_points.empty())
3174         pos = float_aligned_place();
3175     else
3176     {
3177         if (coinflip())
3178             pos = float_dock();
3179 
3180         if (pos.x == -1)
3181             pos = float_random_place();
3182     }
3183 
3184     return pos;
3185 }
3186 
hmirror()3187 void map_def::hmirror()
3188 {
3189     if (has_tag("no_hmirror"))
3190         return;
3191 
3192     dprf(DIAG_DNGN, "Mirroring %s horizontally.", name.c_str());
3193     map.hmirror();
3194 
3195     switch (orient)
3196     {
3197     case MAP_EAST:      orient = MAP_WEST; break;
3198     case MAP_NORTHEAST: orient = MAP_NORTHWEST; break;
3199     case MAP_SOUTHEAST: orient = MAP_SOUTHWEST; break;
3200     case MAP_WEST:      orient = MAP_EAST; break;
3201     case MAP_NORTHWEST: orient = MAP_NORTHEAST; break;
3202     case MAP_SOUTHWEST: orient = MAP_SOUTHEAST; break;
3203     default: break;
3204     }
3205 
3206     for (subvault_place &sv : subvault_places)
3207     {
3208 
3209         coord_def old_tl = sv.tl;
3210         coord_def old_br = sv.br;
3211         sv.tl.x = map.width() - 1 - old_br.x;
3212         sv.br.x = map.width() - 1 - old_tl.x;
3213 
3214         sv.subvault->map.hmirror();
3215     }
3216 }
3217 
vmirror()3218 void map_def::vmirror()
3219 {
3220     if (has_tag("no_vmirror"))
3221         return;
3222 
3223     dprf(DIAG_DNGN, "Mirroring %s vertically.", name.c_str());
3224     map.vmirror();
3225 
3226     switch (orient)
3227     {
3228     case MAP_NORTH:     orient = MAP_SOUTH; break;
3229     case MAP_NORTHEAST: orient = MAP_SOUTHEAST; break;
3230     case MAP_NORTHWEST: orient = MAP_SOUTHWEST; break;
3231 
3232     case MAP_SOUTH:     orient = MAP_NORTH; break;
3233     case MAP_SOUTHEAST: orient = MAP_NORTHEAST; break;
3234     case MAP_SOUTHWEST: orient = MAP_NORTHWEST; break;
3235     default: break;
3236     }
3237 
3238     for (subvault_place& sv : subvault_places)
3239     {
3240         coord_def old_tl = sv.tl;
3241         coord_def old_br = sv.br;
3242         sv.tl.y = map.height() - 1 - old_br.y;
3243         sv.br.y = map.height() - 1 - old_tl.y;
3244 
3245         sv.subvault->map.vmirror();
3246     }
3247 }
3248 
rotate(bool clock)3249 void map_def::rotate(bool clock)
3250 {
3251     if (has_tag("no_rotate"))
3252         return;
3253 
3254 #define GMINM ((GXM) < (GYM)? (GXM) : (GYM))
3255     // Make sure the largest dimension fits in the smaller map bound.
3256     if (map.width() <= GMINM && map.height() <= GMINM)
3257     {
3258         dprf(DIAG_DNGN, "Rotating %s %sclockwise.",
3259              name.c_str(), !clock? "anti-" : "");
3260         map.rotate(clock);
3261 
3262         // Orientation shifts for clockwise rotation:
3263         const map_section_type clockrotate_orients[][2] =
3264         {
3265             { MAP_NORTH,        MAP_EAST        },
3266             { MAP_NORTHEAST,    MAP_SOUTHEAST   },
3267             { MAP_EAST,         MAP_SOUTH       },
3268             { MAP_SOUTHEAST,    MAP_SOUTHWEST   },
3269             { MAP_SOUTH,        MAP_WEST        },
3270             { MAP_SOUTHWEST,    MAP_NORTHWEST   },
3271             { MAP_WEST,         MAP_NORTH       },
3272             { MAP_NORTHWEST,    MAP_NORTHEAST   },
3273         };
3274         const int nrots = ARRAYSZ(clockrotate_orients);
3275 
3276         const int refindex = !clock;
3277         for (int i = 0; i < nrots; ++i)
3278             if (orient == clockrotate_orients[i][refindex])
3279             {
3280                 orient = clockrotate_orients[i][!refindex];
3281                 break;
3282             }
3283 
3284         for (subvault_place& sv : subvault_places)
3285         {
3286             coord_def p1, p2;
3287             if (clock) //Clockwise
3288             {
3289                 p1 = coord_def(map.width() - 1 - sv.tl.y, sv.tl.x);
3290                 p2 = coord_def(map.width() - 1 - sv.br.y, sv.br.x);
3291             }
3292             else
3293             {
3294                 p1 = coord_def(sv.tl.y, map.height() - 1 - sv.tl.x);
3295                 p2 = coord_def(sv.br.y, map.height() - 1 - sv.br.x);
3296             }
3297 
3298             sv.tl = coord_def(min(p1.x, p2.x), min(p1.y, p2.y));
3299             sv.br = coord_def(max(p1.x, p2.x), max(p1.y, p2.y));
3300 
3301             sv.subvault->map.rotate(clock);
3302         }
3303     }
3304 }
3305 
normalise()3306 void map_def::normalise()
3307 {
3308     // Pad out lines that are shorter than max.
3309     map.normalise(' ');
3310 }
3311 
resolve()3312 string map_def::resolve()
3313 {
3314     dlua_set_map dl(this);
3315     return "";
3316 }
3317 
fixup()3318 void map_def::fixup()
3319 {
3320     normalise();
3321 
3322     // Fixup minivaults into floating vaults tagged "minivault".
3323     if (orient == MAP_NONE)
3324     {
3325         orient = MAP_FLOAT;
3326         add_tags("minivault");
3327     }
3328 }
3329 
has_all_tags(const string & tagswanted) const3330 bool map_def::has_all_tags(const string &tagswanted) const
3331 {
3332     const auto &tags_set = parse_tags(tagswanted);
3333     return has_all_tags(tags_set.begin(), tags_set.end());
3334 }
3335 
has_tag(const string & tagwanted) const3336 bool map_def::has_tag(const string &tagwanted) const
3337 {
3338 #ifdef DEBUG_TAG_PROFILING
3339     _profile_inc_tag(tagwanted);
3340 #endif
3341     return tags.count(tagwanted) > 0;
3342 }
3343 
has_tag_prefix(const string & prefix) const3344 bool map_def::has_tag_prefix(const string &prefix) const
3345 {
3346     if (prefix.empty())
3347         return false;
3348     for (const auto &tag : tags)
3349         if (starts_with(tag, prefix))
3350             return true;
3351     return false;
3352 }
3353 
has_tag_suffix(const string & suffix) const3354 bool map_def::has_tag_suffix(const string &suffix) const
3355 {
3356     if (suffix.empty())
3357         return false;
3358     for (const auto &tag : tags)
3359         if (ends_with(tag, suffix))
3360             return true;
3361     return false;
3362 }
3363 
get_tags_unsorted() const3364 const unordered_set<string> map_def::get_tags_unsorted() const
3365 {
3366     return tags;
3367 }
3368 
get_tags() const3369 const vector<string> map_def::get_tags() const
3370 {
3371     // this might seem inefficient, but get_tags is not called very much; the
3372     // hotspot revealed by profiling is actually has_tag checks.
3373     vector<string> result(tags.begin(), tags.end());
3374     sort(result.begin(), result.end());
3375     return result;
3376 }
3377 
add_tags(const string & tag)3378 void map_def::add_tags(const string &tag)
3379 {
3380     auto parsed_tags = parse_tags(tag);
3381     tags.insert(parsed_tags.begin(), parsed_tags.end());
3382     update_cached_tags();
3383 }
3384 
remove_tags(const string & tag)3385 bool map_def::remove_tags(const string &tag)
3386 {
3387     bool removed = false;
3388     auto parsed_tags = parse_tags(tag);
3389     for (auto &t : parsed_tags)
3390         removed = tags.erase(t) || removed; // would iterator overload be ok?
3391     update_cached_tags();
3392     return removed;
3393 }
3394 
clear_tags()3395 void map_def::clear_tags()
3396 {
3397     tags.clear();
3398     update_cached_tags();
3399 }
3400 
set_tags(const string & tag)3401 void map_def::set_tags(const string &tag)
3402 {
3403     clear_tags();
3404     add_tags(tag);
3405     update_cached_tags();
3406 }
3407 
tags_string() const3408 string map_def::tags_string() const
3409 {
3410     auto sorted_tags = get_tags();
3411     return join_strings(sorted_tags.begin(), sorted_tags.end());
3412 }
3413 
mapspec_at(const coord_def & c)3414 keyed_mapspec *map_def::mapspec_at(const coord_def &c)
3415 {
3416     return map.mapspec_at(c);
3417 }
3418 
mapspec_at(const coord_def & c) const3419 const keyed_mapspec *map_def::mapspec_at(const coord_def &c) const
3420 {
3421     return map.mapspec_at(c);
3422 }
3423 
subvault_from_tagstring(const string & sub)3424 string map_def::subvault_from_tagstring(const string &sub)
3425 {
3426     string s = trimmed_string(sub);
3427 
3428     if (s.empty())
3429         return "";
3430 
3431     int sep = 0;
3432     string key;
3433     string substitute;
3434 
3435     string err = mapdef_split_key_item(sub, &key, &sep, &substitute, -1);
3436     if (!err.empty())
3437         return err;
3438 
3439     // Randomly picking a different vault per-glyph is not supported.
3440     if (sep != ':')
3441         return "SUBVAULT does not support '='. Use ':' instead.";
3442 
3443     map_string_list vlist;
3444     err = _parse_weighted_str<map_string_list>(substitute, vlist);
3445     if (!err.empty())
3446         return err;
3447 
3448     bool fix = false;
3449     string_spec spec(key, fix, vlist);
3450 
3451     // Although it's unfortunate to not be able to validate subvaults except a
3452     // run-time, this allows subvaults to reference maps by tag that may not
3453     // have been loaded yet.
3454     if (!is_validating())
3455         err = apply_subvault(spec);
3456 
3457     if (!err.empty())
3458         return err;
3459 
3460     return "";
3461 }
3462 
_register_subvault(const string & name,const string & spaced_tags)3463 static void _register_subvault(const string &name, const string &spaced_tags)
3464 {
3465     auto parsed_tags = parse_tags(spaced_tags);
3466     if (!parsed_tags.count("allow_dup") || parsed_tags.count("luniq"))
3467         env.new_used_subvault_names.insert(name);
3468 
3469     for (const string &tag : parsed_tags)
3470         if (starts_with(tag, "uniq_") || starts_with(tag, "luniq_"))
3471             env.new_used_subvault_tags.insert(tag);
3472 }
3473 
_reset_subvault_stack(const int reg_stack)3474 static void _reset_subvault_stack(const int reg_stack)
3475 {
3476     env.new_subvault_names.resize(reg_stack);
3477     env.new_subvault_tags.resize(reg_stack);
3478 
3479     env.new_used_subvault_names.clear();
3480     env.new_used_subvault_tags.clear();
3481     for (int i = 0; i < reg_stack; i++)
3482     {
3483         _register_subvault(env.new_subvault_names[i],
3484                            env.new_subvault_tags[i]);
3485     }
3486 }
3487 
apply_subvault(string_spec & spec)3488 string map_def::apply_subvault(string_spec &spec)
3489 {
3490     // Find bounding box for key glyphs
3491     coord_def tl, br;
3492     if (!map.find_bounds(spec.key.c_str(), tl, br))
3493     {
3494         // No glyphs, so do nothing.
3495         return "";
3496     }
3497 
3498     int vwidth = br.x - tl.x + 1;
3499     int vheight = br.y - tl.y + 1;
3500     Matrix<bool> flags(vwidth, vheight);
3501     map.fill_mask_matrix(spec.key, tl, br, flags);
3502 
3503     // Remember the subvault registration pointer, so we can clear it.
3504     const int reg_stack = env.new_subvault_names.size();
3505     ASSERT(reg_stack == (int)env.new_subvault_tags.size());
3506     ASSERT(reg_stack >= (int)env.new_used_subvault_names.size());
3507 
3508     const int max_tries = 100;
3509     int ntries = 0;
3510 
3511     string tag = spec.get_property();
3512     while (++ntries <= max_tries)
3513     {
3514         // Each iteration, restore tags and names. This is because this vault
3515         // may successfully load a subvault (registering its tag and name), but
3516         // then itself fail.
3517         _reset_subvault_stack(reg_stack);
3518 
3519         const map_def *orig = random_map_for_tag(tag, true);
3520         if (!orig)
3521             return make_stringf("No vault found for tag '%s'", tag.c_str());
3522 
3523         map_def vault = *orig;
3524 
3525         vault.load();
3526 
3527         // Temporarily set the subvault mask so this subvault can know
3528         // that it is being generated as a subvault.
3529         vault.svmask = &flags;
3530 
3531         if (!resolve_subvault(vault))
3532         {
3533             if (crawl_state.last_builder_error_fatal)
3534                 break;
3535             else
3536                 continue;
3537         }
3538 
3539         ASSERT(vault.map.width() <= vwidth);
3540         ASSERT(vault.map.height() <= vheight);
3541 
3542         const map_corner_t subvault_corners =
3543             map.merge_subvault(tl, br, flags, vault);
3544 
3545         copy_hooks_from(vault, "post_place");
3546         env.new_subvault_names.push_back(vault.name);
3547         const string vault_tags = vault.tags_string();
3548         env.new_subvault_tags.push_back(vault_tags);
3549         _register_subvault(vault.name, vault_tags);
3550         subvault_places.emplace_back(subvault_corners.first,
3551                                      subvault_corners.second, vault);
3552 
3553         return "";
3554     }
3555 
3556     // Failure, drop subvault registrations.
3557     _reset_subvault_stack(reg_stack);
3558 
3559     if (crawl_state.last_builder_error_fatal)
3560     {
3561         // I think the error should get printed elsewhere?
3562         return make_stringf("Fatal lua error while resolving subvault '%s'",
3563             tag.c_str());
3564     }
3565     return make_stringf("Could not fit '%s' in (%d,%d) to (%d, %d).",
3566                         tag.c_str(), tl.x, tl.y, br.x, br.y);
3567 }
3568 
is_subvault() const3569 bool map_def::is_subvault() const
3570 {
3571     return svmask != nullptr;
3572 }
3573 
apply_subvault_mask()3574 void map_def::apply_subvault_mask()
3575 {
3576     if (!svmask)
3577         return;
3578 
3579     map.clear();
3580     map.extend(subvault_width(), subvault_height(), ' ');
3581 
3582     for (rectangle_iterator ri(map.get_iter()); ri; ++ri)
3583     {
3584         const coord_def mc = *ri;
3585         if (subvault_cell_valid(mc))
3586             map(mc) = '.';
3587         else
3588             map(mc) = ' ';
3589     }
3590 }
3591 
subvault_cell_valid(const coord_def & c) const3592 bool map_def::subvault_cell_valid(const coord_def &c) const
3593 {
3594     if (!svmask)
3595         return false;
3596 
3597     if (c.x < 0 || c.x >= subvault_width()
3598         || c.y < 0 || c.y >= subvault_height())
3599     {
3600         return false;
3601     }
3602 
3603     return (*svmask)(c.x, c.y);
3604 }
3605 
subvault_width() const3606 int map_def::subvault_width() const
3607 {
3608     if (!svmask)
3609         return 0;
3610 
3611     return svmask->width();
3612 }
3613 
subvault_height() const3614 int map_def::subvault_height() const
3615 {
3616     if (!svmask)
3617         return 0;
3618 
3619     return svmask->height();
3620 }
3621 
subvault_mismatch_count(const coord_def & offset) const3622 int map_def::subvault_mismatch_count(const coord_def &offset) const
3623 {
3624     int count = 0;
3625     if (!is_subvault())
3626         return count;
3627 
3628     for (rectangle_iterator ri(map.get_iter()); ri; ++ri)
3629     {
3630         // Coordinate in the subvault
3631         const coord_def sc = *ri;
3632         // Coordinate in the mask
3633         const coord_def mc = sc + offset;
3634 
3635         bool valid_subvault_cell = (map(sc) != ' ');
3636         bool valid_mask = (*svmask)(mc.x, mc.y);
3637 
3638         if (valid_subvault_cell && !valid_mask)
3639             count++;
3640     }
3641 
3642     return count;
3643 }
3644 
3645 ///////////////////////////////////////////////////////////////////
3646 // mons_list
3647 //
3648 
mons_list()3649 mons_list::mons_list() : mons()
3650 {
3651 }
3652 
pick_monster(mons_spec_slot & slot)3653 mons_spec mons_list::pick_monster(mons_spec_slot &slot)
3654 {
3655     int totweight = 0;
3656     mons_spec pick;
3657 
3658     for (const auto &spec : slot.mlist)
3659     {
3660         const int weight = spec.genweight;
3661         if (x_chance_in_y(weight, totweight += weight))
3662             pick = spec;
3663     }
3664 
3665 #if TAG_MAJOR_VERSION == 34
3666     // Force rebuild of the des cache to drop this check.
3667     if ((int)pick.type < -1)
3668         pick = (monster_type)(-100 - (int)pick.type);
3669 #endif
3670 
3671     if (slot.fix_slot)
3672     {
3673         slot.mlist.clear();
3674         slot.mlist.push_back(pick);
3675         slot.fix_slot = false;
3676     }
3677 
3678     return pick;
3679 }
3680 
get_monster(int index)3681 mons_spec mons_list::get_monster(int index)
3682 {
3683     if (index < 0 || index >= (int)mons.size())
3684         return mons_spec(RANDOM_MONSTER);
3685 
3686     return pick_monster(mons[index]);
3687 }
3688 
get_monster(int slot_index,int list_index) const3689 mons_spec mons_list::get_monster(int slot_index, int list_index) const
3690 {
3691     if (slot_index < 0 || slot_index >= (int)mons.size())
3692         return mons_spec(RANDOM_MONSTER);
3693 
3694     const mons_spec_list &list = mons[slot_index].mlist;
3695 
3696     if (list_index < 0 || list_index >= (int)list.size())
3697         return mons_spec(RANDOM_MONSTER);
3698 
3699     return list[list_index];
3700 }
3701 
clear()3702 void mons_list::clear()
3703 {
3704     mons.clear();
3705 }
3706 
set_from_slot(const mons_list & list,int slot_index)3707 void mons_list::set_from_slot(const mons_list &list, int slot_index)
3708 {
3709     clear();
3710 
3711     // Don't set anything if an invalid index.
3712     // Future calls to get_monster will just return a random monster.
3713     if (slot_index < 0 || (size_t)slot_index >= list.mons.size())
3714         return;
3715 
3716     mons.push_back(list.mons[slot_index]);
3717 }
3718 
parse_mons_spells(mons_spec & spec,vector<string> & spells)3719 void mons_list::parse_mons_spells(mons_spec &spec, vector<string> &spells)
3720 {
3721     spec.explicit_spells = true;
3722 
3723     for (const string &slotspec : spells)
3724     {
3725         monster_spells cur_spells;
3726 
3727         const vector<string> spell_names(split_string(";", slotspec));
3728 
3729         for (unsigned i = 0, ssize = spell_names.size(); i < ssize; ++i)
3730         {
3731             cur_spells.emplace_back();
3732             const string spname(
3733                 lowercase_string(replace_all_of(spell_names[i], "_", " ")));
3734             if (spname.empty() || spname == "." || spname == "none"
3735                 || spname == "no spell")
3736             {
3737                 cur_spells[i].spell = SPELL_NO_SPELL;
3738             }
3739             else
3740             {
3741                 const vector<string> slot_vals = split_string(".", spname);
3742                 if (slot_vals.size() < 2)
3743                 {
3744                     error = make_stringf(
3745                         "Invalid spell slot format: '%s' in '%s'",
3746                         spname.c_str(), slotspec.c_str());
3747                     return;
3748                 }
3749                 const spell_type sp(spell_by_name(slot_vals[0]));
3750                 if (sp == SPELL_NO_SPELL)
3751                 {
3752                     error = make_stringf("Unknown spell name: '%s' in '%s'",
3753                                          slot_vals[0].c_str(),
3754                                          slotspec.c_str());
3755                     return;
3756                 }
3757                 if (!is_valid_mon_spell(sp))
3758                 {
3759                     error = make_stringf("Not a monster spell: '%s'",
3760                                          slot_vals[0].c_str());
3761                     return;
3762                 }
3763                 cur_spells[i].spell = sp;
3764                 const int freq = atoi(slot_vals[1].c_str());
3765                 if (freq <= 0)
3766                 {
3767                     error = make_stringf("Need a positive spell frequency;"
3768                                          "got '%s' in '%s'",
3769                                          slot_vals[1].c_str(),
3770                                          spname.c_str());
3771                     return;
3772                 }
3773                 cur_spells[i].freq = freq;
3774                 for (size_t j = 2; j < slot_vals.size(); j++)
3775                 {
3776                     if (slot_vals[j] == "emergency")
3777                         cur_spells[i].flags |= MON_SPELL_EMERGENCY;
3778                     if (slot_vals[j] == "natural")
3779                         cur_spells[i].flags |= MON_SPELL_NATURAL;
3780                     if (slot_vals[j] == "magical")
3781                         cur_spells[i].flags |= MON_SPELL_MAGICAL;
3782                     if (slot_vals[j] == "wizard")
3783                         cur_spells[i].flags |= MON_SPELL_WIZARD;
3784                     if (slot_vals[j] == "priest")
3785                         cur_spells[i].flags |= MON_SPELL_PRIEST;
3786                     if (slot_vals[j] == "vocal")
3787                         cur_spells[i].flags |= MON_SPELL_VOCAL;
3788                     if (slot_vals[j] == "breath")
3789                         cur_spells[i].flags |= MON_SPELL_BREATH;
3790                     if (slot_vals[j] == "instant")
3791                         cur_spells[i].flags |= MON_SPELL_INSTANT;
3792                     if (slot_vals[j] == "noisy")
3793                         cur_spells[i].flags |= MON_SPELL_NOISY;
3794                     if (slot_vals[j] == "short range")
3795                         cur_spells[i].flags |= MON_SPELL_SHORT_RANGE;
3796                     if (slot_vals[j] == "long range")
3797                         cur_spells[i].flags |= MON_SPELL_LONG_RANGE;
3798                 }
3799                 if (!(cur_spells[i].flags & MON_SPELL_TYPE_MASK))
3800                 {
3801                     error = make_stringf(
3802                         "Spell slot '%s' missing a casting type",
3803                         spname.c_str());
3804                     return;
3805                 }
3806             }
3807         }
3808 
3809         spec.spells.push_back(cur_spells);
3810     }
3811 }
3812 
parse_ench(string & ench_str,bool perm)3813 mon_enchant mons_list::parse_ench(string &ench_str, bool perm)
3814 {
3815     vector<string> ep = split_string(":", ench_str);
3816     if (ep.size() > (perm ? 2 : 3))
3817     {
3818         error = make_stringf("bad %sench specifier: \"%s\"",
3819                              perm ? "perm_" : "",
3820                              ench_str.c_str());
3821         return mon_enchant();
3822     }
3823 
3824     enchant_type et = name_to_ench(ep[0].c_str());
3825     if (et == ENCH_NONE)
3826     {
3827         error = make_stringf("unknown ench: \"%s\"", ep[0].c_str());
3828         return mon_enchant();
3829     }
3830 
3831     int deg = 0, dur = perm ? INFINITE_DURATION : 0;
3832     if (ep.size() > 1 && !ep[1].empty())
3833         if (!parse_int(ep[1].c_str(), deg))
3834         {
3835             error = make_stringf("invalid deg in ench specifier \"%s\"",
3836                                  ench_str.c_str());
3837             return mon_enchant();
3838         }
3839     if (ep.size() > 2 && !ep[2].empty())
3840         if (!parse_int(ep[2].c_str(), dur))
3841         {
3842             error = make_stringf("invalid dur in ench specifier \"%s\"",
3843                                  ench_str.c_str());
3844             return mon_enchant();
3845         }
3846     return mon_enchant(et, deg, 0, dur);
3847 }
3848 
parse_mons_spec(string spec)3849 mons_list::mons_spec_slot mons_list::parse_mons_spec(string spec)
3850 {
3851     mons_spec_slot slot;
3852 
3853     slot.fix_slot = strip_tag(spec, "fix_slot");
3854 
3855     vector<string> specs = split_string("/", spec);
3856 
3857     for (const string &monspec : specs)
3858     {
3859         string s(monspec);
3860         mons_spec mspec;
3861 
3862         vector<string> spells(strip_multiple_tag_prefix(s, "spells:"));
3863         if (!spells.empty())
3864         {
3865             parse_mons_spells(mspec, spells);
3866             if (!error.empty())
3867                 return slot;
3868         }
3869 
3870         vector<string> parts = split_string(";", s);
3871 
3872         if (parts.size() == 0)
3873         {
3874             error = make_stringf("Not enough non-semicolons for '%s' spec.",
3875                                  s.c_str());
3876             return slot;
3877         }
3878 
3879         string mon_str = parts[0];
3880 
3881         if (parts.size() > 2)
3882         {
3883             error = make_stringf("Too many semi-colons for '%s' spec.",
3884                                  mon_str.c_str());
3885             return slot;
3886         }
3887         else if (parts.size() == 2)
3888         {
3889             // TODO: Allow for a "fix_slot" type tag which will cause
3890             // all monsters generated from this spec to have the
3891             // exact same equipment.
3892             string items_str = parts[1];
3893             items_str = replace_all(items_str, "|", "/");
3894 
3895             vector<string> segs = split_string(".", items_str);
3896 
3897             if (segs.size() > NUM_MONSTER_SLOTS)
3898             {
3899                 error = make_stringf("More items than monster item slots "
3900                                      "for '%s'.", mon_str.c_str());
3901                 return slot;
3902             }
3903 
3904             for (const string &seg : segs)
3905             {
3906                 error = mspec.items.add_item(seg, false);
3907                 if (!error.empty())
3908                     return slot;
3909             }
3910         }
3911 
3912         mspec.genweight = find_weight(mon_str);
3913         if (mspec.genweight == TAG_UNFOUND || mspec.genweight <= 0)
3914             mspec.genweight = 10;
3915 
3916         mspec.generate_awake = strip_tag(mon_str, "generate_awake");
3917         mspec.patrolling     = strip_tag(mon_str, "patrolling");
3918         mspec.band           = strip_tag(mon_str, "band");
3919 
3920         const string att = strip_tag_prefix(mon_str, "att:");
3921         if (att.empty() || att == "hostile")
3922             mspec.attitude = ATT_HOSTILE;
3923         else if (att == "friendly")
3924             mspec.attitude = ATT_FRIENDLY;
3925         else if (att == "good_neutral")
3926             mspec.attitude = ATT_GOOD_NEUTRAL;
3927         else if (att == "fellow_slime" || att == "strict_neutral")
3928             mspec.attitude = ATT_STRICT_NEUTRAL;
3929         else if (att == "neutral")
3930             mspec.attitude = ATT_NEUTRAL;
3931 
3932         // Useful for summoned monsters.
3933         if (strip_tag(mon_str, "seen"))
3934             mspec.extra_monster_flags |= MF_SEEN;
3935 
3936         if (strip_tag(mon_str, "always_corpse"))
3937             mspec.props["always_corpse"] = true;
3938 
3939         if (strip_tag(mon_str, NEVER_CORPSE_KEY))
3940             mspec.props[NEVER_CORPSE_KEY] = true;
3941 
3942         if (!mon_str.empty() && isadigit(mon_str[0]))
3943         {
3944             // Look for space after initial digits.
3945             string::size_type pos = mon_str.find_first_not_of("0123456789");
3946             if (pos != string::npos && mon_str[pos] == ' ')
3947             {
3948                 const string mcount = mon_str.substr(0, pos);
3949                 const int count = atoi(mcount.c_str()); // safe atoi()
3950                 if (count >= 1 && count <= 99)
3951                     mspec.quantity = count;
3952 
3953                 mon_str = mon_str.substr(pos);
3954             }
3955         }
3956 
3957         // place:Elf:$ to choose monsters appropriate for that level,
3958         // for example.
3959         const string place = strip_tag_prefix(mon_str, "place:");
3960         if (!place.empty())
3961         {
3962             try
3963             {
3964                 mspec.place = level_id::parse_level_id(place);
3965             }
3966             catch (const bad_level_id &err)
3967             {
3968                 error = err.what();
3969                 return slot;
3970             }
3971         }
3972 
3973         mspec.hd = min(100, strip_number_tag(mon_str, "hd:"));
3974         if (mspec.hd == TAG_UNFOUND)
3975             mspec.hd = 0;
3976 
3977         mspec.hp = strip_number_tag(mon_str, "hp:");
3978         if (mspec.hp == TAG_UNFOUND)
3979             mspec.hp = 0;
3980 
3981         int dur = strip_number_tag(mon_str, "dur:");
3982         if (dur == TAG_UNFOUND)
3983             dur = 0;
3984         else if (dur < 1 || dur > 6)
3985             dur = 0;
3986 
3987         mspec.abjuration_duration = dur;
3988 
3989         string shifter_name = replace_all_of(strip_tag_prefix(mon_str, "shifter:"), "_", " ");
3990 
3991         if (!shifter_name.empty())
3992         {
3993             mspec.initial_shifter = get_monster_by_name(shifter_name);
3994             if (mspec.initial_shifter == MONS_PROGRAM_BUG)
3995                 mspec.initial_shifter = RANDOM_MONSTER;
3996         }
3997 
3998         int summon_type = 0;
3999         string s_type = strip_tag_prefix(mon_str, "sum:");
4000         if (!s_type.empty())
4001         {
4002             // In case of spells!
4003             s_type = replace_all_of(s_type, "_", " ");
4004             summon_type = static_cast<int>(str_to_summon_type(s_type));
4005             if (summon_type == SPELL_NO_SPELL)
4006             {
4007                 error = make_stringf("bad monster summon type: \"%s\"",
4008                                 s_type.c_str());
4009                 return slot;
4010             }
4011             if (mspec.abjuration_duration == 0)
4012             {
4013                 error = "marked summon with no duration";
4014                 return slot;
4015             }
4016         }
4017 
4018         mspec.summon_type = summon_type;
4019 
4020         string non_actor_summoner = strip_tag_prefix(mon_str, "nas:");
4021         if (!non_actor_summoner.empty())
4022         {
4023             non_actor_summoner = replace_all_of(non_actor_summoner, "_", " ");
4024             mspec.non_actor_summoner = non_actor_summoner;
4025             if (mspec.abjuration_duration == 0)
4026             {
4027                 error = "marked summon with no duration";
4028                 return slot;
4029             }
4030         }
4031 
4032         string colour = strip_tag_prefix(mon_str, "col:");
4033         if (!colour.empty())
4034         {
4035             if (colour == "any")
4036                 mspec.colour = COLOUR_UNDEF;
4037             else
4038             {
4039                 mspec.colour = str_to_colour(colour, COLOUR_UNDEF, false, true);
4040                 if (mspec.colour == COLOUR_UNDEF)
4041                 {
4042                     error = make_stringf("bad monster colour \"%s\" in \"%s\"",
4043                                          colour.c_str(), monspec.c_str());
4044                     return slot;
4045                 }
4046             }
4047         }
4048 
4049         string mongod = strip_tag_prefix(mon_str, "god:");
4050         if (!mongod.empty())
4051         {
4052             const string god_name(replace_all_of(mongod, "_", " "));
4053 
4054             mspec.god = str_to_god(god_name);
4055 
4056             if (mspec.god == GOD_NO_GOD)
4057             {
4058                 error = make_stringf("bad monster god: \"%s\"",
4059                                      god_name.c_str());
4060                 return slot;
4061             }
4062 
4063             if (strip_tag(mon_str, "god_gift"))
4064                 mspec.god_gift = true;
4065         }
4066 
4067         string tile = strip_tag_prefix(mon_str, "tile:");
4068         if (!tile.empty())
4069         {
4070             tileidx_t index;
4071             if (!tile_player_index(tile.c_str(), &index))
4072             {
4073                 error = make_stringf("bad tile name: \"%s\".", tile.c_str());
4074                 return slot;
4075             }
4076             // Store name along with the tile.
4077             mspec.props["monster_tile_name"].get_string() = tile;
4078             mspec.props["monster_tile"] = short(index);
4079         }
4080 
4081         string dbname = strip_tag_prefix(mon_str, "dbname:");
4082         if (!dbname.empty())
4083         {
4084             dbname = replace_all_of(dbname, "_", " ");
4085             mspec.props["dbname"].get_string() = dbname;
4086         }
4087 
4088         string name = strip_tag_prefix(mon_str, "name:");
4089         if (!name.empty())
4090         {
4091             name = replace_all_of(name, "_", " ");
4092             mspec.monname = name;
4093 
4094             if (strip_tag(mon_str, "name_suffix")
4095                 || strip_tag(mon_str, "n_suf"))
4096             {
4097                 mspec.extra_monster_flags |= MF_NAME_SUFFIX;
4098             }
4099             else if (strip_tag(mon_str, "name_adjective")
4100                      || strip_tag(mon_str, "n_adj"))
4101             {
4102                 mspec.extra_monster_flags |= MF_NAME_ADJECTIVE;
4103             }
4104             else if (strip_tag(mon_str, "name_replace")
4105                      || strip_tag(mon_str, "n_rpl"))
4106             {
4107                 mspec.extra_monster_flags |= MF_NAME_REPLACE;
4108             }
4109 
4110             if (strip_tag(mon_str, "name_definite")
4111                 || strip_tag(mon_str, "n_the"))
4112             {
4113                 mspec.extra_monster_flags |= MF_NAME_DEFINITE;
4114             }
4115 
4116             // Reasoning for setting more than one flag: suffixes and
4117             // adjectives need NAME_DESCRIPTOR to get proper grammar,
4118             // and definite names do nothing with the description unless
4119             // NAME_DESCRIPTOR is also set.
4120             const auto name_flags = mspec.extra_monster_flags & MF_NAME_MASK;
4121             const bool need_name_desc =
4122                 name_flags == MF_NAME_SUFFIX
4123                    || name_flags == MF_NAME_ADJECTIVE
4124                    || (mspec.extra_monster_flags & MF_NAME_DEFINITE);
4125 
4126             if (strip_tag(mon_str, "name_descriptor")
4127                 || strip_tag(mon_str, "n_des")
4128                 || need_name_desc)
4129             {
4130                 mspec.extra_monster_flags |= MF_NAME_DESCRIPTOR;
4131             }
4132 
4133             if (strip_tag(mon_str, "name_species")
4134                 || strip_tag(mon_str, "n_spe"))
4135             {
4136                 mspec.extra_monster_flags |= MF_NAME_SPECIES;
4137             }
4138 
4139             if (strip_tag(mon_str, "name_zombie")
4140                 || strip_tag(mon_str, "n_zom"))
4141             {
4142                 mspec.extra_monster_flags |= MF_NAME_ZOMBIE;
4143             }
4144             if (strip_tag(mon_str, "name_nocorpse")
4145                 || strip_tag(mon_str, "n_noc"))
4146             {
4147                 mspec.extra_monster_flags |= MF_NAME_NOCORPSE;
4148             }
4149         }
4150 
4151         string ench_str;
4152         while (!(ench_str = strip_tag_prefix(mon_str, "ench:")).empty())
4153         {
4154             mspec.ench.push_back(parse_ench(ench_str, false));
4155             if (!error.empty())
4156                 return slot;
4157         }
4158         while (!(ench_str = strip_tag_prefix(mon_str, "perm_ench:")).empty())
4159         {
4160             mspec.ench.push_back(parse_ench(ench_str, true));
4161             if (!error.empty())
4162                 return slot;
4163         }
4164 
4165         trim_string(mon_str);
4166 
4167         if (mon_str == "8")
4168             mspec.type = RANDOM_SUPER_OOD;
4169         else if (mon_str == "9")
4170             mspec.type = RANDOM_MODERATE_OOD;
4171         else if (mspec.place.is_valid())
4172         {
4173             // For monster specs such as place:Orc:4 zombie, we may
4174             // have a monster modifier, in which case we set the
4175             // modifier in monbase.
4176             const mons_spec nspec = mons_by_name("orc " + mon_str);
4177             if (nspec.type != MONS_PROGRAM_BUG)
4178             {
4179                 // Is this a modified monster?
4180                 if (nspec.monbase != MONS_PROGRAM_BUG
4181                     && mons_class_is_zombified(static_cast<monster_type>(nspec.type)))
4182                 {
4183                     mspec.monbase = static_cast<monster_type>(nspec.type);
4184                 }
4185             }
4186         }
4187         else if (mon_str != "0")
4188         {
4189             const mons_spec nspec = mons_by_name(mon_str);
4190 
4191             if (nspec.type == MONS_PROGRAM_BUG)
4192             {
4193                 error = make_stringf("unknown monster: \"%s\"",
4194                                      mon_str.c_str());
4195                 return slot;
4196             }
4197 
4198             if (mons_class_flag(nspec.type, M_CANT_SPAWN))
4199             {
4200                 error = make_stringf("can't place dummy monster: \"%s\"",
4201                                      mon_str.c_str());
4202                 return slot;
4203             }
4204 
4205             mspec.type    = nspec.type;
4206             mspec.monbase = nspec.monbase;
4207             if (nspec.colour > COLOUR_UNDEF && mspec.colour <= COLOUR_UNDEF)
4208                 mspec.colour = nspec.colour;
4209             if (nspec.hd != 0)
4210                 mspec.hd = nspec.hd;
4211 #define MAYBE_COPY(x) \
4212             if (nspec.props.exists((x))) \
4213             { \
4214                 mspec.props[(x)] \
4215                     = nspec.props[(x)]; \
4216             }
4217             MAYBE_COPY(MUTANT_BEAST_FACETS);
4218             MAYBE_COPY(MGEN_BLOB_SIZE);
4219             MAYBE_COPY(MGEN_NUM_HEADS);
4220             MAYBE_COPY(MGEN_NO_AUTO_CRUMBLE);
4221 #undef MAYBE_COPY
4222         }
4223 
4224         if (!mspec.items.empty())
4225         {
4226             monster_type type = (monster_type)mspec.type;
4227             if (type == RANDOM_DRACONIAN
4228                 || type == RANDOM_BASE_DRACONIAN
4229                 || type == RANDOM_NONBASE_DRACONIAN)
4230             {
4231                 type = MONS_DRACONIAN;
4232             }
4233 
4234             if (type >= NUM_MONSTERS)
4235             {
4236                 error = "Can't give spec items to a random monster.";
4237                 return slot;
4238             }
4239             else if (mons_class_itemuse(type) < MONUSE_STARTING_EQUIPMENT
4240                      && (!mons_class_is_animated_object(type)
4241                          || mspec.items.size() > 1)
4242                      && (type != MONS_ZOMBIE && type != MONS_SKELETON
4243                          || invalid_monster_type(mspec.monbase)
4244                          || mons_class_itemuse(mspec.monbase)
4245                             < MONUSE_STARTING_EQUIPMENT))
4246             {
4247                 // TODO: skip this error if the monspec is `nothing`
4248                 error = make_stringf("Monster '%s' can't use items.",
4249                     mon_str.c_str());
4250             }
4251             else if (mons_class_is_animated_object(type))
4252             {
4253                 auto item = mspec.items.get_item(0);
4254                 const auto *unrand = item.ego < SP_FORBID_EGO
4255                     ? get_unrand_entry(-item.ego) : nullptr;
4256                 const auto base = unrand && unrand->base_type != OBJ_UNASSIGNED
4257                     ? unrand->base_type : item.base_type;
4258                 const auto sub = unrand && unrand->base_type != OBJ_UNASSIGNED
4259                     ? unrand->sub_type : item.sub_type;
4260 
4261                 const auto def_slot = mons_class_is_animated_weapon(type)
4262                     ? EQ_WEAPON
4263                     : EQ_BODY_ARMOUR;
4264 
4265                 if (get_item_slot(base, sub) != def_slot)
4266                 {
4267                     error = make_stringf("Monster '%s' needs a defining item.",
4268                                          mon_str.c_str());
4269                 }
4270             }
4271         }
4272 
4273         slot.mlist.push_back(mspec);
4274     }
4275 
4276     return slot;
4277 }
4278 
add_mons(const string & s,bool fix)4279 string mons_list::add_mons(const string &s, bool fix)
4280 {
4281     error.clear();
4282 
4283     mons_spec_slot slotmons = parse_mons_spec(s);
4284     if (!error.empty())
4285         return error;
4286 
4287     if (fix)
4288     {
4289         slotmons.fix_slot = true;
4290         pick_monster(slotmons);
4291     }
4292 
4293     mons.push_back(slotmons);
4294 
4295     return error;
4296 }
4297 
set_mons(int index,const string & s)4298 string mons_list::set_mons(int index, const string &s)
4299 {
4300     error.clear();
4301 
4302     if (index < 0)
4303         return error = make_stringf("Index out of range: %d", index);
4304 
4305     mons_spec_slot slotmons = parse_mons_spec(s);
4306     if (!error.empty())
4307         return error;
4308 
4309     if (index >= (int) mons.size())
4310     {
4311         mons.reserve(index + 1);
4312         mons.resize(index + 1, mons_spec_slot());
4313     }
4314     mons[index] = slotmons;
4315     return error;
4316 }
4317 
_fixup_mon_type(monster_type orig)4318 static monster_type _fixup_mon_type(monster_type orig)
4319 {
4320     if (mons_class_flag(orig, M_CANT_SPAWN))
4321         return MONS_PROGRAM_BUG;
4322 
4323     if (orig < 0)
4324         orig = MONS_PROGRAM_BUG;
4325 
4326     monster_type dummy_mons = MONS_PROGRAM_BUG;
4327     coord_def dummy_pos;
4328     level_id place = level_id::current();
4329     return resolve_monster_type(orig, dummy_mons, PROX_ANYWHERE, &dummy_pos, 0,
4330                                 &place);
4331 }
4332 
get_zombie_type(string s,mons_spec & spec) const4333 void mons_list::get_zombie_type(string s, mons_spec &spec) const
4334 {
4335     static const char *zombie_types[] =
4336     {
4337         " zombie", " skeleton", " simulacrum", " spectre", nullptr
4338     };
4339 
4340     // This order must match zombie_types, indexed from one.
4341     static const monster_type zombie_montypes[] =
4342     {
4343         MONS_PROGRAM_BUG, MONS_ZOMBIE, MONS_SKELETON, MONS_SIMULACRUM,
4344         MONS_SPECTRAL_THING,
4345     };
4346 
4347     int mod = ends_with(s, zombie_types);
4348     if (!mod)
4349     {
4350         if (starts_with(s, "spectral "))
4351         {
4352             mod = ends_with(" spectre", zombie_types);
4353             s = s.substr(9); // strlen("spectral ")
4354         }
4355         else
4356         {
4357             spec.type = MONS_PROGRAM_BUG;
4358             return;
4359         }
4360     }
4361     else
4362         s = s.substr(0, s.length() - strlen(zombie_types[mod - 1]));
4363 
4364     trim_string(s);
4365 
4366     mons_spec base_monster = mons_by_name(s);
4367     base_monster.type = _fixup_mon_type(base_monster.type);
4368     if (base_monster.type == MONS_PROGRAM_BUG)
4369     {
4370         spec.type = MONS_PROGRAM_BUG;
4371         return;
4372     }
4373 
4374     spec.monbase = static_cast<monster_type>(base_monster.type);
4375     if (base_monster.props.exists(MGEN_NUM_HEADS))
4376         spec.props[MGEN_NUM_HEADS] = base_monster.props[MGEN_NUM_HEADS];
4377 
4378     const int zombie_size = mons_zombie_size(spec.monbase);
4379     if (!zombie_size)
4380     {
4381         spec.type = MONS_PROGRAM_BUG;
4382         return;
4383     }
4384     if (mod == 1 && mons_class_flag(spec.monbase, M_NO_ZOMBIE))
4385     {
4386         spec.type = MONS_PROGRAM_BUG;
4387         return;
4388     }
4389     if (mod == 2 && mons_class_flag(spec.monbase, M_NO_SKELETON))
4390     {
4391         spec.type = MONS_PROGRAM_BUG;
4392         return;
4393     }
4394 
4395     spec.type = zombie_montypes[mod];
4396 }
4397 
get_hydra_spec(const string & name) const4398 mons_spec mons_list::get_hydra_spec(const string &name) const
4399 {
4400     string prefix = name.substr(0, name.find("-"));
4401 
4402     int nheads = atoi(prefix.c_str());
4403     if (nheads != 0)
4404         ;
4405     else if (prefix == "0")
4406         nheads = 0;
4407     else
4408     {
4409         // Might be "two-headed hydra" type string.
4410         for (int i = 0; i <= 20; ++i)
4411             if (number_in_words(i) == prefix)
4412             {
4413                 nheads = i;
4414                 break;
4415             }
4416     }
4417 
4418     if (nheads < 1)
4419         nheads = 27;  // What can I say? :P
4420     else if (nheads > 20)
4421     {
4422 #if defined(DEBUG) || defined(DEBUG_DIAGNOSTICS)
4423         mprf(MSGCH_DIAGNOSTICS, "Hydra spec wants %d heads, clamping to 20.",
4424              nheads);
4425 #endif
4426         nheads = 20;
4427     }
4428 
4429     mons_spec spec(MONS_HYDRA);
4430     spec.props[MGEN_NUM_HEADS] = nheads;
4431     return spec;
4432 }
4433 
get_slime_spec(const string & name) const4434 mons_spec mons_list::get_slime_spec(const string &name) const
4435 {
4436     string prefix = name.substr(0, name.find(" slime creature"));
4437 
4438     int slime_size = 1;
4439 
4440     if (prefix == "large")
4441         slime_size = 2;
4442     else if (prefix == "very large")
4443         slime_size = 3;
4444     else if (prefix == "enormous")
4445         slime_size = 4;
4446     else if (prefix == "titanic")
4447         slime_size = 5;
4448     else
4449     {
4450 #if defined(DEBUG) || defined(DEBUG_DIAGNOSTICS)
4451         mprf(MSGCH_DIAGNOSTICS, "Slime spec wants invalid size '%s'",
4452              prefix.c_str());
4453 #endif
4454     }
4455 
4456     mons_spec spec(MONS_SLIME_CREATURE);
4457     spec.props[MGEN_BLOB_SIZE] = slime_size;
4458     return spec;
4459 }
4460 
4461 /**
4462  * Build a monster specification for a specified pillar of salt. The pillar of
4463  * salt won't crumble over time, since that seems unuseful for any version of
4464  * this function.
4465  *
4466  * @param name      The description of the pillar of salt; e.g.
4467  *                  "human-shaped pillar of salt",
4468  *                  "titanic slime creature-shaped pillar of salt."
4469  *                  XXX: doesn't currently work with zombie specifiers
4470  *                  e.g. "zombie-shaped..." (does this matter?)
4471  * @return          A specifier for a pillar of salt.
4472  */
get_salt_spec(const string & name) const4473 mons_spec mons_list::get_salt_spec(const string &name) const
4474 {
4475     const string prefix = name.substr(0, name.find("-shaped pillar of salt"));
4476     mons_spec base_mon = mons_by_name(prefix);
4477     if (base_mon.type == MONS_PROGRAM_BUG)
4478         return base_mon; // invalid specifier
4479 
4480     mons_spec spec(MONS_PILLAR_OF_SALT);
4481     spec.monbase = _fixup_mon_type(base_mon.type);
4482     spec.props[MGEN_NO_AUTO_CRUMBLE] = true;
4483     return spec;
4484 }
4485 
4486 // Handle draconians specified as:
4487 // Exactly as in mon-data.h:
4488 //    yellow draconian or draconian knight - the monster specified.
4489 //
4490 // Others:
4491 //    any draconian => any random draconain
4492 //    any base draconian => any unspecialised coloured draconian.
4493 //    any nonbase draconian => any specialised coloured draconian.
4494 //    any <colour> draconian => any draconian of the colour.
4495 //    any nonbase <colour> draconian => any specialised drac of the colour.
4496 //
drac_monspec(string name) const4497 mons_spec mons_list::drac_monspec(string name) const
4498 {
4499     mons_spec spec;
4500 
4501     spec.type = get_monster_by_name(name);
4502 
4503     // Check if it's a simple drac name, we're done.
4504     if (spec.type != MONS_PROGRAM_BUG)
4505         return spec;
4506 
4507     spec.type = RANDOM_DRACONIAN;
4508 
4509     // Request for any draconian?
4510     if (starts_with(name, "any "))
4511         name = name.substr(4); // Strip "any "
4512 
4513     if (starts_with(name, "base "))
4514     {
4515         // Base dracs need no further work.
4516         return RANDOM_BASE_DRACONIAN;
4517     }
4518     else if (starts_with(name, "nonbase "))
4519     {
4520         spec.type = RANDOM_NONBASE_DRACONIAN;
4521         name = name.substr(8);
4522     }
4523 
4524     trim_string(name);
4525 
4526     // Match "any draconian"
4527     if (name == "draconian")
4528         return spec;
4529 
4530     // Check for recognition again to match any (nonbase) <colour> draconian.
4531     const monster_type colour = get_monster_by_name(name);
4532     if (colour != MONS_PROGRAM_BUG)
4533     {
4534         spec.monbase = colour;
4535         return spec;
4536     }
4537 
4538     // Only legal possibility left is <colour> boss drac.
4539     string::size_type wordend = name.find(' ');
4540     if (wordend == string::npos)
4541         return MONS_PROGRAM_BUG;
4542 
4543     string scolour = name.substr(0, wordend);
4544     if ((spec.monbase = draconian_colour_by_name(scolour)) == MONS_PROGRAM_BUG)
4545         return MONS_PROGRAM_BUG;
4546 
4547     name = trimmed_string(name.substr(wordend + 1));
4548     spec.type = get_monster_by_name(name);
4549 
4550     // We should have a non-base draconian here.
4551     if (spec.type == MONS_PROGRAM_BUG
4552         || mons_genus(static_cast<monster_type>(spec.type)) != MONS_DRACONIAN
4553         || mons_is_base_draconian(spec.type))
4554     {
4555         return MONS_PROGRAM_BUG;
4556     }
4557 
4558     return spec;
4559 }
4560 
4561 // As with draconians, so with demonspawn.
demonspawn_monspec(string name) const4562 mons_spec mons_list::demonspawn_monspec(string name) const
4563 {
4564     mons_spec spec;
4565 
4566     spec.type = get_monster_by_name(name);
4567 
4568     // Check if it's a simple demonspawn name, we're done.
4569     if (spec.type != MONS_PROGRAM_BUG)
4570         return spec;
4571 
4572     spec.type = RANDOM_DEMONSPAWN;
4573 
4574     // Request for any demonspawn?
4575     if (starts_with(name, "any "))
4576         name = name.substr(4); // Strip "any "
4577 
4578     if (starts_with(name, "base "))
4579     {
4580         // Base demonspawn need no further work.
4581         return RANDOM_BASE_DEMONSPAWN;
4582     }
4583     else if (starts_with(name, "nonbase "))
4584     {
4585         spec.type = RANDOM_NONBASE_DEMONSPAWN;
4586         name = name.substr(8);
4587     }
4588 
4589     trim_string(name);
4590 
4591     // Match "any demonspawn"
4592     if (name == "demonspawn")
4593         return spec;
4594 
4595     // Check for recognition again to match any (nonbase) <base> demonspawn.
4596     const monster_type base = get_monster_by_name(name);
4597     if (base != MONS_PROGRAM_BUG)
4598     {
4599         spec.monbase = base;
4600         return spec;
4601     }
4602 
4603     // Only legal possibility left is <base> boss demonspawn.
4604     string::size_type wordend = name.find(' ');
4605     if (wordend == string::npos)
4606         return MONS_PROGRAM_BUG;
4607 
4608     string sbase = name.substr(0, wordend);
4609     if ((spec.monbase = demonspawn_base_by_name(sbase)) == MONS_PROGRAM_BUG)
4610         return MONS_PROGRAM_BUG;
4611 
4612     name = trimmed_string(name.substr(wordend + 1));
4613     spec.type = get_monster_by_name(name);
4614 
4615     // We should have a non-base demonspawn here.
4616     if (spec.type == MONS_PROGRAM_BUG
4617         || mons_genus(static_cast<monster_type>(spec.type)) != MONS_DEMONSPAWN
4618         || spec.type == MONS_DEMONSPAWN
4619         || (spec.type >= MONS_FIRST_BASE_DEMONSPAWN
4620             && spec.type <= MONS_LAST_BASE_DEMONSPAWN))
4621     {
4622         return MONS_PROGRAM_BUG;
4623     }
4624 
4625     return spec;
4626 }
4627 
soh_monspec(string name) const4628 mons_spec mons_list::soh_monspec(string name) const
4629 {
4630     // "serpent of hell " is 16 characters
4631     name = name.substr(16);
4632     string abbrev =
4633         uppercase_first(lowercase(name)).substr(0, 3);
4634     switch (branch_by_abbrevname(abbrev))
4635     {
4636         case BRANCH_GEHENNA:
4637             return MONS_SERPENT_OF_HELL;
4638         case BRANCH_COCYTUS:
4639             return MONS_SERPENT_OF_HELL_COCYTUS;
4640         case BRANCH_DIS:
4641             return MONS_SERPENT_OF_HELL_DIS;
4642         case BRANCH_TARTARUS:
4643             return MONS_SERPENT_OF_HELL_TARTARUS;
4644         default:
4645             return MONS_PROGRAM_BUG;
4646     }
4647 }
4648 
4649 /**
4650  * What mutant beast facet corresponds to the given name?
4651  *
4652  * @param name      The name in question (e.g. 'bat')
4653  * @return          The corresponding facet (e.g. BF_BAT), or BF_NONE.
4654  */
_beast_facet_by_name(const string & name)4655 static int _beast_facet_by_name(const string &name)
4656 {
4657     for (int bf = BF_FIRST; bf < NUM_BEAST_FACETS; ++bf)
4658         if (mutant_beast_facet_names[bf] == lowercase_string(name))
4659             return bf;
4660     return BF_NONE;
4661 }
4662 
4663 /**
4664  * What HD corresponds to the given mutant beast tier name?
4665  *
4666  * XXX: refactor this together with _beast_facet_by_name()?
4667  *
4668  * @param tier      The name in question (e.g. 'juvenile')
4669  * @return          The corresponding tier XL (e.g. 9), or 0 if none is valid.
4670  */
_mutant_beast_xl(const string & tier)4671 static int _mutant_beast_xl(const string &tier)
4672 {
4673     for (int bt = BT_FIRST; bt < NUM_BEAST_TIERS; ++bt)
4674         if (mutant_beast_tier_names[bt] == lowercase_string(tier))
4675             return beast_tiers[bt];
4676     return 0;
4677 }
4678 
mons_by_name(string name) const4679 mons_spec mons_list::mons_by_name(string name) const
4680 {
4681     name = replace_all_of(name, "_", " ");
4682     name = replace_all(name, "random", "any");
4683 
4684     if (name == "nothing")
4685         return MONS_NO_MONSTER;
4686 
4687     // Special casery:
4688     if (name == "pandemonium lord")
4689         return MONS_PANDEMONIUM_LORD;
4690 
4691     if (name == "any" || name == "any monster")
4692         return RANDOM_MONSTER;
4693 
4694     if (name == "any demon")
4695         return RANDOM_DEMON;
4696 
4697     if (name == "any lesser demon" || name == "lesser demon")
4698         return RANDOM_DEMON_LESSER;
4699 
4700     if (name == "any common demon" || name == "common demon")
4701         return RANDOM_DEMON_COMMON;
4702 
4703     if (name == "any greater demon" || name == "greater demon")
4704         return RANDOM_DEMON_GREATER;
4705 
4706     if (name == "small zombie" || name == "large zombie"
4707         || name == "small skeleton" || name == "large skeleton"
4708         || name == "small simulacrum" || name == "large simulacrum")
4709     {
4710         return MONS_PROGRAM_BUG;
4711     }
4712 
4713     if (name == "small abomination")
4714         return MONS_ABOMINATION_SMALL;
4715     if (name == "large abomination")
4716         return MONS_ABOMINATION_LARGE;
4717 
4718     if (ends_with(name, "-headed hydra") && !starts_with(name, "spectral "))
4719         return get_hydra_spec(name);
4720 
4721     if (ends_with(name, " slime creature"))
4722         return get_slime_spec(name);
4723 
4724     if (ends_with(name, "-shaped pillar of salt"))
4725         return get_salt_spec(name);
4726 
4727     const auto m_index = name.find(" mutant beast");
4728     if (m_index != string::npos)
4729     {
4730         mons_spec spec = MONS_MUTANT_BEAST;
4731 
4732         const string trimmed = name.substr(0, m_index);
4733         const vector<string> segments = split_string(" ", trimmed);
4734         if (segments.size() > 2)
4735             return MONS_PROGRAM_BUG; // too many words
4736 
4737         const bool fully_specified = segments.size() == 2;
4738         spec.hd = _mutant_beast_xl(segments[0]);
4739         if (spec.hd == 0 && fully_specified)
4740             return MONS_PROGRAM_BUG; // gave invalid tier spec
4741 
4742         if (spec.hd == 0 || fully_specified)
4743         {
4744             const int seg = segments.size() - 1;
4745             const vector<string> facet_names
4746                 = split_string("-", segments[seg]);
4747             for (const string &facet_name : facet_names)
4748             {
4749                 const int facet = _beast_facet_by_name(facet_name);
4750                 if (facet == BF_NONE)
4751                     return MONS_PROGRAM_BUG; // invalid facet
4752                 spec.props[MUTANT_BEAST_FACETS].get_vector().push_back(facet);
4753             }
4754         }
4755 
4756         return spec;
4757     }
4758 
4759     mons_spec spec;
4760     if (name.find(" ugly thing") != string::npos)
4761     {
4762         const string::size_type wordend = name.find(' ');
4763         const string first_word = name.substr(0, wordend);
4764 
4765         const int colour = str_to_ugly_thing_colour(first_word);
4766         if (colour)
4767         {
4768             spec = mons_by_name(name.substr(wordend + 1));
4769             spec.colour = colour;
4770             return spec;
4771         }
4772     }
4773 
4774     get_zombie_type(name, spec);
4775     if (spec.type != MONS_PROGRAM_BUG)
4776         return spec;
4777 
4778     if (name.find("draconian") != string::npos)
4779         return drac_monspec(name);
4780 
4781     // FIXME: cleaner way to do this?
4782     if (name.find("demonspawn") != string::npos
4783         || name.find("black sun") != string::npos
4784         || name.find("blood saint") != string::npos
4785         || name.find("corrupter") != string::npos
4786         || name.find("warmonger") != string::npos)
4787     {
4788         return demonspawn_monspec(name);
4789     }
4790 
4791     // The space is important - it indicates a flavour is being specified.
4792     if (name.find("serpent of hell ") != string::npos)
4793         return soh_monspec(name);
4794 
4795     // Allow access to her second form, which shares display names.
4796     if (name == "bai suzhen dragon")
4797         return MONS_BAI_SUZHEN_DRAGON;
4798 
4799     return get_monster_by_name(name);
4800 }
4801 
4802 //////////////////////////////////////////////////////////////////////
4803 // item_list
4804 
item_spec(const item_spec & other)4805 item_spec::item_spec(const item_spec &other)
4806     : _corpse_monster_spec(nullptr)
4807 {
4808     *this = other;
4809 }
4810 
operator =(const item_spec & other)4811 item_spec &item_spec::operator = (const item_spec &other)
4812 {
4813     if (this != &other)
4814     {
4815         genweight = other.genweight;
4816         base_type = other.base_type;
4817         sub_type  = other.sub_type;
4818         plus = other.plus;
4819         plus2 = other.plus2;
4820         ego = other.ego;
4821         allow_uniques = other.allow_uniques;
4822         level = other.level;
4823         item_special = other.item_special;
4824         qty = other.qty;
4825         acquirement_source = other.acquirement_source;
4826         place = other.place;
4827         props = other.props;
4828 
4829         release_corpse_monster_spec();
4830         if (other._corpse_monster_spec)
4831             set_corpse_monster_spec(other.corpse_monster_spec());
4832     }
4833     return *this;
4834 }
4835 
~item_spec()4836 item_spec::~item_spec()
4837 {
4838     release_corpse_monster_spec();
4839 }
4840 
release_corpse_monster_spec()4841 void item_spec::release_corpse_monster_spec()
4842 {
4843     delete _corpse_monster_spec;
4844     _corpse_monster_spec = nullptr;
4845 }
4846 
corpselike() const4847 bool item_spec::corpselike() const
4848 {
4849     return base_type == OBJ_CORPSES;
4850 }
4851 
corpse_monster_spec() const4852 const mons_spec &item_spec::corpse_monster_spec() const
4853 {
4854     ASSERT(_corpse_monster_spec);
4855     return *_corpse_monster_spec;
4856 }
4857 
set_corpse_monster_spec(const mons_spec & spec)4858 void item_spec::set_corpse_monster_spec(const mons_spec &spec)
4859 {
4860     if (&spec != _corpse_monster_spec)
4861     {
4862         release_corpse_monster_spec();
4863         _corpse_monster_spec = new mons_spec(spec);
4864     }
4865 }
4866 
clear()4867 void item_list::clear()
4868 {
4869     items.clear();
4870 }
4871 
random_item()4872 item_spec item_list::random_item()
4873 {
4874     if (items.empty())
4875     {
4876         const item_spec none;
4877         return none;
4878     }
4879 
4880     return get_item(random2(size()));
4881 }
4882 
4883 typedef pair<item_spec, int> item_pair;
4884 
random_item_weighted()4885 item_spec item_list::random_item_weighted()
4886 {
4887     const item_spec none;
4888 
4889     vector<item_pair> pairs;
4890     for (int i = 0, sz = size(); i < sz; ++i)
4891     {
4892         item_spec item = get_item(i);
4893         pairs.emplace_back(item, item.genweight);
4894     }
4895 
4896     item_spec* rn_item = random_choose_weighted(pairs);
4897     if (rn_item)
4898         return *rn_item;
4899 
4900     return none;
4901 }
4902 
pick_item(item_spec_slot & slot)4903 item_spec item_list::pick_item(item_spec_slot &slot)
4904 {
4905     int cumulative = 0;
4906     item_spec pick;
4907     for (const auto &spec : slot.ilist)
4908     {
4909         const int weight = spec.genweight;
4910         if (x_chance_in_y(weight, cumulative += weight))
4911             pick = spec;
4912     }
4913 
4914     if (slot.fix_slot)
4915     {
4916         slot.ilist.clear();
4917         slot.ilist.push_back(pick);
4918         slot.fix_slot = false;
4919     }
4920 
4921     return pick;
4922 }
4923 
get_item(int index)4924 item_spec item_list::get_item(int index)
4925 {
4926     if (index < 0 || index >= (int) items.size())
4927     {
4928         const item_spec none;
4929         return none;
4930     }
4931 
4932     return pick_item(items[index]);
4933 }
4934 
add_item(const string & spec,bool fix)4935 string item_list::add_item(const string &spec, bool fix)
4936 {
4937     error.clear();
4938 
4939     item_spec_slot sp = parse_item_spec(spec);
4940     if (error.empty())
4941     {
4942         if (fix)
4943         {
4944             sp.fix_slot = true;
4945             pick_item(sp);
4946         }
4947 
4948         items.push_back(sp);
4949     }
4950 
4951     return error;
4952 }
4953 
set_item(int index,const string & spec)4954 string item_list::set_item(int index, const string &spec)
4955 {
4956     error.clear();
4957     if (index < 0)
4958         return error = make_stringf("Index %d out of range", index);
4959 
4960     item_spec_slot sp = parse_item_spec(spec);
4961     if (error.empty())
4962     {
4963         if (index >= (int) items.size())
4964         {
4965             items.reserve(index + 1);
4966             items.resize(index + 1, item_spec_slot());
4967         }
4968         items.push_back(sp);
4969     }
4970 
4971     return error;
4972 }
4973 
set_from_slot(const item_list & list,int slot_index)4974 void item_list::set_from_slot(const item_list &list, int slot_index)
4975 {
4976     clear();
4977 
4978     // Don't set anything if an invalid index.
4979     // Future calls to get_item will just return no item.
4980     if (slot_index < 0 || (size_t)slot_index >= list.items.size())
4981         return;
4982 
4983     items.push_back(list.items[slot_index]);
4984 }
4985 
4986 // TODO: More checking for inappropriate combinations, like the holy
4987 // wrath brand on a demonic weapon or the running ego on a helmet.
4988 // NOTE: Be sure to update the reference in syntax.txt if this gets moved!
str_to_ego(object_class_type item_type,string ego_str)4989 int str_to_ego(object_class_type item_type, string ego_str)
4990 {
4991     const char* armour_egos[] =
4992     {
4993 #if TAG_MAJOR_VERSION == 34
4994         "running",
4995 #endif
4996         "fire_resistance",
4997         "cold_resistance",
4998         "poison_resistance",
4999         "see_invisible",
5000         "invisibility",
5001         "strength",
5002         "dexterity",
5003         "intelligence",
5004         "ponderousness",
5005         "flying",
5006         "willpower",
5007         "protection",
5008         "stealth",
5009         "resistance",
5010         "positive_energy",
5011         "archmagi",
5012         "preservation",
5013         "reflection",
5014         "spirit_shield",
5015         "archery",
5016 #if TAG_MAJOR_VERSION == 34
5017         "jumping",
5018 #endif
5019         "repulsion",
5020 #if TAG_MAJOR_VERSION == 34
5021         "cloud_immunity",
5022 #endif
5023         "harm",
5024         "shadows",
5025         "rampaging",
5026         nullptr
5027     };
5028     COMPILE_CHECK(ARRAYSZ(armour_egos) == NUM_REAL_SPECIAL_ARMOURS);
5029 
5030     const char* weapon_brands[] =
5031     {
5032         "flaming",
5033         "freezing",
5034         "holy_wrath",
5035         "electrocution",
5036 #if TAG_MAJOR_VERSION == 34
5037         "orc_slaying",
5038         "dragon_slaying",
5039 #endif
5040         "venom",
5041         "protection",
5042         "draining",
5043         "speed",
5044         "vorpal",
5045 #if TAG_MAJOR_VERSION == 34
5046         "flame",
5047         "frost",
5048 #endif
5049         "vampirism",
5050         "pain",
5051         "antimagic",
5052         "distortion",
5053 #if TAG_MAJOR_VERSION == 34
5054         "reaching",
5055         "returning",
5056 #endif
5057         "chaos",
5058 #if TAG_MAJOR_VERSION == 34
5059         "evasion",
5060         "confuse",
5061 #endif
5062         "penetration",
5063         "reaping",
5064         "spectral",
5065         nullptr
5066     };
5067     COMPILE_CHECK(ARRAYSZ(weapon_brands) == NUM_REAL_SPECIAL_WEAPONS);
5068 
5069     const char* missile_brands[] =
5070     {
5071         "flame",
5072         "frost",
5073         "poisoned",
5074         "curare",
5075 #if TAG_MAJOR_VERSION == 34
5076         "returning",
5077 #endif
5078         "chaos",
5079 #if TAG_MAJOR_VERSION == 34
5080         "penetration",
5081 #endif
5082         "dispersal",
5083         "exploding",
5084 #if TAG_MAJOR_VERSION == 34
5085         "steel",
5086 #endif
5087         "silver",
5088 #if TAG_MAJOR_VERSION == 34
5089         "paralysis",
5090         "slow",
5091         "sleep",
5092         "confusion",
5093         "sickness",
5094 #endif
5095         "datura",
5096         "atropa",
5097         nullptr
5098     };
5099     COMPILE_CHECK(ARRAYSZ(missile_brands) == NUM_REAL_SPECIAL_MISSILES);
5100 
5101     const char** name_lists[3] = {armour_egos, weapon_brands, missile_brands};
5102 
5103     int armour_order[3]  = {0, 1, 2};
5104     int weapon_order[3]  = {1, 0, 2};
5105     int missile_order[3] = {2, 0, 1};
5106 
5107     int *order;
5108 
5109     switch (item_type)
5110     {
5111     case OBJ_ARMOUR:
5112         order = armour_order;
5113         break;
5114 
5115     case OBJ_WEAPONS:
5116         order = weapon_order;
5117         break;
5118 
5119     case OBJ_MISSILES:
5120 #if TAG_MAJOR_VERSION == 34
5121         // HACK to get an old save to load; remove me soon?
5122         if (ego_str == "sleeping")
5123             return SPMSL_SLEEP;
5124 #endif
5125         order = missile_order;
5126         break;
5127 
5128     default:
5129         die("Bad base_type for ego'd item.");
5130         return 0;
5131     }
5132 
5133     const char** allowed = name_lists[order[0]];
5134 
5135     for (int i = 0; allowed[i] != nullptr; i++)
5136     {
5137         if (ego_str == allowed[i])
5138             return i + 1;
5139     }
5140 
5141     // Incompatible or non-existent ego type
5142     for (int i = 1; i <= 2; i++)
5143     {
5144         const char** list = name_lists[order[i]];
5145 
5146         for (int j = 0; list[j] != nullptr; j++)
5147             if (ego_str == list[j])
5148                 // Ego incompatible with base type.
5149                 return -1;
5150     }
5151 
5152     // Non-existent ego
5153     return 0;
5154 }
5155 
parse_acquirement_source(const string & source)5156 int item_list::parse_acquirement_source(const string &source)
5157 {
5158     const string god_name(replace_all_of(source, "_", " "));
5159     const god_type god(str_to_god(god_name));
5160     if (god == GOD_NO_GOD)
5161         error = make_stringf("unknown god name: '%s'", god_name.c_str());
5162     return god;
5163 }
5164 
monster_corpse_is_valid(monster_type * mons,const string & name,bool skeleton)5165 bool item_list::monster_corpse_is_valid(monster_type *mons,
5166                                         const string &name,
5167                                         bool skeleton)
5168 {
5169     if (*mons == RANDOM_NONBASE_DRACONIAN || *mons == RANDOM_NONBASE_DEMONSPAWN)
5170     {
5171         error = "Can't use non-base monster for corpse/chunk items";
5172         return false;
5173     }
5174 
5175     // Accept randomised types without further checks:
5176     if (*mons >= NUM_MONSTERS)
5177         return true;
5178 
5179     // Convert to the monster species:
5180     *mons = mons_species(*mons);
5181 
5182     if (!mons_class_can_leave_corpse(*mons))
5183     {
5184         error = make_stringf("'%s' cannot leave corpses", name.c_str());
5185         return false;
5186     }
5187 
5188     if (skeleton && !mons_skeleton(*mons))
5189     {
5190         error = make_stringf("'%s' has no skeleton", name.c_str());
5191         return false;
5192     }
5193 
5194     // We're ok.
5195     return true;
5196 }
5197 
parse_corpse_spec(item_spec & result,string s)5198 bool item_list::parse_corpse_spec(item_spec &result, string s)
5199 {
5200     const bool never_decay = strip_tag(s, "never_decay");
5201 
5202     if (never_decay)
5203         result.props[CORPSE_NEVER_DECAYS].get_bool() = true;
5204 
5205     const bool corpse = strip_suffix(s, "corpse");
5206     const bool skeleton = !corpse && strip_suffix(s, "skeleton");
5207 
5208     result.base_type = OBJ_CORPSES;
5209     result.sub_type  = static_cast<int>(corpse ? CORPSE_BODY :
5210                                          CORPSE_SKELETON);
5211 
5212     // The caller wants a specific monster, no doubt with the best of
5213     // motives. Let's indulge them:
5214     mons_list mlist;
5215     const string mons_parse_err = mlist.add_mons(s, true);
5216     if (!mons_parse_err.empty())
5217     {
5218         error = mons_parse_err;
5219         return false;
5220     }
5221 
5222     // Get the actual monster spec:
5223     mons_spec spec = mlist.get_monster(0);
5224     monster_type mtype = static_cast<monster_type>(spec.type);
5225     if (!monster_corpse_is_valid(&mtype, s, skeleton))
5226     {
5227         error = make_stringf("Requested corpse '%s' is invalid",
5228                              s.c_str());
5229         return false;
5230     }
5231 
5232     // Ok, looking good, the caller can have their requested toy.
5233     result.set_corpse_monster_spec(spec);
5234     return true;
5235 }
5236 
parse_single_spec(item_spec & result,string s)5237 bool item_list::parse_single_spec(item_spec& result, string s)
5238 {
5239     // If there's a colon, this must be a generation weight.
5240     int weight = find_weight(s);
5241     if (weight != TAG_UNFOUND)
5242     {
5243         result.genweight = weight;
5244         if (result.genweight <= 0)
5245         {
5246             error = make_stringf("Bad item generation weight: '%d'",
5247                                  result.genweight);
5248             return false;
5249         }
5250     }
5251 
5252     const int qty = strip_number_tag(s, "q:");
5253     if (qty != TAG_UNFOUND)
5254         result.qty = qty;
5255 
5256     const int fresh = strip_number_tag(s, "fresh:");
5257     if (fresh != TAG_UNFOUND)
5258         result.item_special = fresh;
5259     const int special = strip_number_tag(s, "special:");
5260     if (special != TAG_UNFOUND)
5261         result.item_special = special;
5262 
5263     // When placing corpses, use place:Elf:$ to choose monsters
5264     // appropriate for that level, as an example.
5265     const string place = strip_tag_prefix(s, "place:");
5266     if (!place.empty())
5267     {
5268         try
5269         {
5270             result.place = level_id::parse_level_id(place);
5271         }
5272         catch (const bad_level_id &err)
5273         {
5274             error = err.what();
5275             return false;
5276         }
5277     }
5278 
5279     const string acquirement_source = strip_tag_prefix(s, "acquire:");
5280     if (!acquirement_source.empty() || strip_tag(s, "acquire"))
5281     {
5282         if (!acquirement_source.empty())
5283         {
5284             result.acquirement_source =
5285                 parse_acquirement_source(acquirement_source);
5286         }
5287         // If requesting acquirement, must specify item base type or
5288         // "any".
5289         result.level = ISPEC_ACQUIREMENT;
5290         if (s == "any")
5291             result.base_type = OBJ_RANDOM;
5292         else
5293             parse_random_by_class(s, result);
5294         return true;
5295     }
5296 
5297     string ego_str  = strip_tag_prefix(s, "ego:");
5298 
5299     string id_str = strip_tag_prefix(s, "ident:");
5300     if (id_str == "all")
5301         result.props["ident"].get_int() = ISFLAG_IDENT_MASK;
5302     else if (!id_str.empty())
5303     {
5304         vector<string> ids = split_string("|", id_str);
5305         int id = 0;
5306         for (const auto &is : ids)
5307         {
5308             if (is == "type")
5309                 id |= ISFLAG_KNOW_TYPE;
5310             else if (is == "pluses")
5311                 id |= ISFLAG_KNOW_PLUSES;
5312             else if (is == "properties")
5313                 id |= ISFLAG_KNOW_PROPERTIES;
5314             else
5315             {
5316                 error = make_stringf("Bad identify status: %s", id_str.c_str());
5317                 return false;
5318             }
5319         }
5320         result.props["ident"].get_int() = id;
5321     }
5322 
5323     if (strip_tag(s, "good_item"))
5324         result.level = ISPEC_GOOD_ITEM;
5325     else
5326     {
5327         int number = strip_number_tag(s, "level:");
5328         if (number != TAG_UNFOUND)
5329         {
5330             if (number <= 0 && number != ISPEC_STAR && number != ISPEC_SUPERB
5331                 && number != ISPEC_DAMAGED && number != ISPEC_BAD)
5332             {
5333                 error = make_stringf("Bad item level: %d", number);
5334                 return false;
5335             }
5336 
5337             result.level = number;
5338         }
5339     }
5340 
5341     if (strip_tag(s, "mundane"))
5342     {
5343         result.level = ISPEC_MUNDANE;
5344         result.ego   = -1;
5345     }
5346     if (strip_tag(s, "damaged"))
5347         result.level = ISPEC_DAMAGED;
5348     if (strip_tag(s, "randart"))
5349         result.level = ISPEC_RANDART;
5350     if (strip_tag(s, "useful"))
5351         result.props["useful"] = bool(true);
5352     if (strip_tag(s, "unobtainable"))
5353         result.props["unobtainable"] = true;
5354 
5355     const int mimic = strip_number_tag(s, "mimic:");
5356     if (mimic != TAG_UNFOUND)
5357         result.props["mimic"] = mimic;
5358     if (strip_tag(s, "mimic"))
5359         result.props["mimic"] = 1;
5360 
5361     if (strip_tag(s, "no_pickup"))
5362         result.props["no_pickup"] = true;
5363 
5364     const short charges = strip_number_tag(s, "charges:");
5365     if (charges >= 0)
5366         result.props["charges"].get_int() = charges;
5367 
5368     const int plus = strip_number_tag(s, "plus:");
5369     if (plus != TAG_UNFOUND)
5370         result.props["plus"].get_int() = plus;
5371     const int plus2 = strip_number_tag(s, "plus2:");
5372     if (plus2 != TAG_UNFOUND)
5373         result.props["plus2"].get_int() = plus2;
5374 
5375     if (strip_tag(s, "no_uniq"))
5376         result.allow_uniques = 0;
5377     if (strip_tag(s, "allow_uniq"))
5378         result.allow_uniques = 1;
5379     else
5380     {
5381         int uniq = strip_number_tag(s, "uniq:");
5382         if (uniq != TAG_UNFOUND)
5383         {
5384             if (uniq <= 0)
5385             {
5386                 error = make_stringf("Bad uniq level: %d", uniq);
5387                 return false;
5388             }
5389             result.allow_uniques = uniq;
5390         }
5391     }
5392 
5393     // XXX: This is nice-ish now, but could probably do with being improved.
5394     if (strip_tag(s, "randbook"))
5395     {
5396         result.props["build_themed_book"] = true;
5397         // build_themed_book requires the following properties:
5398         // disc: <first discipline>, disc2: <optional second discipline>
5399         // numspells: <total number of spells>, slevels: <maximum levels>
5400         // spell: <include this spell>, owner:<name of owner>
5401         // None of these are required, but if you don't intend on using any
5402         // of them, use "any fixed theme book" instead.
5403         spschool disc1 = spschool::none;
5404         spschool disc2 = spschool::none;
5405 
5406         string st_disc1 = strip_tag_prefix(s, "disc:");
5407         if (!st_disc1.empty())
5408         {
5409             disc1 = school_by_name(st_disc1);
5410             if (disc1 == spschool::none)
5411             {
5412                 error = make_stringf("Bad spell school: %s", st_disc1.c_str());
5413                 return false;
5414             }
5415         }
5416 
5417         string st_disc2 = strip_tag_prefix(s, "disc2:");
5418         if (!st_disc2.empty())
5419         {
5420             disc2 = school_by_name(st_disc2);
5421             if (disc2 == spschool::none)
5422             {
5423                 error = make_stringf("Bad spell school: %s", st_disc2.c_str());
5424                 return false;
5425             }
5426         }
5427 
5428         if (disc1 == spschool::none && disc2 != spschool::none)
5429         {
5430             // Don't fail, just quietly swap. Any errors in disc1's syntax will
5431             // have been caught above, anyway.
5432             swap(disc1, disc2);
5433         }
5434 
5435         short num_spells = strip_number_tag(s, "numspells:");
5436         if (num_spells == TAG_UNFOUND)
5437             num_spells = -1;
5438         else if (num_spells <= 0 || num_spells > RANDBOOK_SIZE)
5439         {
5440             error = make_stringf("Bad spellbook size: %d", num_spells);
5441             return false;
5442         }
5443 
5444         short slevels = strip_number_tag(s, "slevels:");
5445         if (slevels == TAG_UNFOUND)
5446             slevels = -1;
5447         else if (slevels == 0)
5448         {
5449             error = make_stringf("Bad slevels: %d.", slevels);
5450             return false;
5451         }
5452 
5453         const string title = replace_all_of(strip_tag_prefix(s, "title:"),
5454                                             "_", " ");
5455 
5456         const string spells = strip_tag_prefix(s, "spells:");
5457 
5458         vector<string> spell_list = split_string("&", spells);
5459         CrawlVector &incl_spells
5460             = result.props[RANDBK_SPELLS_KEY].new_vector(SV_INT);
5461 
5462         for (const string &spnam : spell_list)
5463         {
5464             string spell_name = replace_all_of(spnam, "_", " ");
5465             spell_type spell = spell_by_name(spell_name);
5466             if (spell == SPELL_NO_SPELL)
5467             {
5468                 error = make_stringf("Bad spell: %s", spnam.c_str());
5469                 return false;
5470             }
5471             incl_spells.push_back(spell);
5472         }
5473 
5474         const string owner = replace_all_of(strip_tag_prefix(s, "owner:"),
5475                                             "_", " ");
5476 
5477         result.props[RANDBK_DISC1_KEY].get_short() = static_cast<short>(disc1);
5478         result.props[RANDBK_DISC2_KEY].get_short() = static_cast<short>(disc2);
5479         result.props[RANDBK_NSPELLS_KEY] = num_spells;
5480         result.props[RANDBK_SLVLS_KEY] = slevels;
5481         result.props[RANDBK_TITLE_KEY] = title;
5482         result.props[RANDBK_OWNER_KEY] = owner;
5483 
5484         result.base_type = OBJ_BOOKS;
5485         // This is changed in build_themed_book().
5486         result.sub_type = BOOK_MINOR_MAGIC;
5487         result.plus = -1;
5488 
5489         return true;
5490     }
5491 
5492     if (s.find("deck") != string::npos)
5493     {
5494         error = make_stringf("removed deck: \"%s\".", s.c_str());
5495         return false;
5496     }
5497 
5498     string tile = strip_tag_prefix(s, "tile:");
5499     if (!tile.empty())
5500     {
5501         tileidx_t index;
5502         if (!tile_main_index(tile.c_str(), &index))
5503         {
5504             error = make_stringf("bad tile name: \"%s\".", tile.c_str());
5505             return false;
5506         }
5507         result.props["item_tile_name"].get_string() = tile;
5508     }
5509 
5510     tile = strip_tag_prefix(s, "wtile:");
5511     if (!tile.empty())
5512     {
5513         tileidx_t index;
5514         if (!tile_player_index(tile.c_str(), &index))
5515         {
5516             error = make_stringf("bad tile name: \"%s\".", tile.c_str());
5517             return false;
5518         }
5519         result.props["worn_tile_name"].get_string() = tile;
5520     }
5521 
5522     // Clean up after any tag brain damage.
5523     trim_string(s);
5524 
5525     if (!ego_str.empty())
5526         error = "Can't set an ego for random items.";
5527 
5528     // Completely random?
5529     if (s == "random" || s == "any" || s == "%")
5530         return true;
5531 
5532     if (s == "*" || s == "star_item")
5533     {
5534         result.level = ISPEC_STAR;
5535         return true;
5536     }
5537     else if (s == "|" || s == "superb_item")
5538     {
5539         result.level = ISPEC_SUPERB;
5540         return true;
5541     }
5542     else if (s == "$" || s == "gold")
5543     {
5544         if (!ego_str.empty())
5545         {
5546             error = "Can't set an ego for gold.";
5547             return false;
5548         }
5549 
5550         result.base_type = OBJ_GOLD;
5551         result.sub_type  = OBJ_RANDOM;
5552         return true;
5553     }
5554 
5555     if (s == "nothing")
5556     {
5557         error.clear();
5558         result.base_type = OBJ_UNASSIGNED;
5559         return true;
5560     }
5561 
5562     error.clear();
5563 
5564     // Look for corpses, skeletons:
5565     if (ends_with(s, "corpse") || ends_with(s, "skeleton"))
5566         return parse_corpse_spec(result, s);
5567 
5568     const int unrand_id = get_unrandart_num(s.c_str());
5569     if (unrand_id)
5570     {
5571         result.ego = -unrand_id; // lol
5572         return true;
5573     }
5574 
5575     // Check for "any objclass"
5576     if (starts_with(s, "any "))
5577         parse_random_by_class(s.substr(4), result);
5578     else if (starts_with(s, "random "))
5579         parse_random_by_class(s.substr(7), result);
5580     // Check for actual item names.
5581     else
5582         parse_raw_name(s, result);
5583 
5584     if (!error.empty())
5585         return false;
5586 
5587     if (ego_str.empty())
5588         return true;
5589 
5590     if (result.base_type != OBJ_WEAPONS
5591         && result.base_type != OBJ_MISSILES
5592         && result.base_type != OBJ_ARMOUR)
5593     {
5594         error = "An ego can only be applied to a weapon, missile or "
5595             "armour.";
5596         return false;
5597     }
5598 
5599     if (ego_str == "none")
5600     {
5601         result.ego = -1;
5602         return true;
5603     }
5604 
5605     const int ego = str_to_ego(result.base_type, ego_str);
5606 
5607     if (ego == 0)
5608     {
5609         error = make_stringf("No such ego as: %s", ego_str.c_str());
5610         return false;
5611     }
5612     else if (ego == -1)
5613     {
5614         error = make_stringf("Ego '%s' is invalid for item '%s'.",
5615                              ego_str.c_str(), s.c_str());
5616         return false;
5617     }
5618     else if (result.sub_type == OBJ_RANDOM)
5619     {
5620         // it will be assigned among appropriate ones later
5621     }
5622     else if (result.base_type == OBJ_WEAPONS
5623                 && !is_weapon_brand_ok(result.sub_type, ego, false)
5624              || result.base_type == OBJ_ARMOUR
5625                 && !is_armour_brand_ok(result.sub_type, ego, false)
5626              || result.base_type == OBJ_MISSILES
5627                 && !is_missile_brand_ok(result.sub_type, ego, false))
5628     {
5629         error = make_stringf("Ego '%s' is incompatible with item '%s'.",
5630                              ego_str.c_str(), s.c_str());
5631         return false;
5632     }
5633     result.ego = ego;
5634     return true;
5635 }
5636 
parse_random_by_class(string c,item_spec & spec)5637 void item_list::parse_random_by_class(string c, item_spec &spec)
5638 {
5639     trim_string(c);
5640     if (c == "?" || c.empty())
5641     {
5642         error = make_stringf("Bad item class: '%s'", c.c_str());
5643         return;
5644     }
5645 
5646     for (int type = OBJ_WEAPONS; type < NUM_OBJECT_CLASSES; ++type)
5647     {
5648         if (c == item_class_name(type, true))
5649         {
5650             spec.base_type = static_cast<object_class_type>(type);
5651             return;
5652         }
5653     }
5654 
5655     // Random manual?
5656     if (c == "manual")
5657     {
5658         spec.base_type = OBJ_BOOKS;
5659         spec.sub_type  = BOOK_MANUAL;
5660         spec.plus      = -1;
5661         return;
5662     }
5663     else if (c == "fixed theme book")
5664     {
5665         spec.base_type = OBJ_BOOKS;
5666         spec.sub_type  = BOOK_RANDART_THEME;
5667         spec.plus      = -1;
5668         return;
5669     }
5670     else if (c == "fixed level book")
5671     {
5672         spec.base_type = OBJ_BOOKS;
5673         spec.sub_type  = BOOK_RANDART_LEVEL;
5674         spec.plus      = -1;
5675         return;
5676     }
5677     else if (c == "ring")
5678     {
5679         spec.base_type = OBJ_JEWELLERY;
5680         spec.sub_type = NUM_RINGS;
5681         return;
5682     }
5683     else if (c == "amulet")
5684     {
5685         spec.base_type = OBJ_JEWELLERY;
5686         spec.sub_type = NUM_JEWELLERY;
5687         return;
5688     }
5689 
5690     error = make_stringf("Bad item class: '%s'", c.c_str());
5691 }
5692 
parse_raw_name(string name,item_spec & spec)5693 void item_list::parse_raw_name(string name, item_spec &spec)
5694 {
5695     trim_string(name);
5696     if (name.empty())
5697     {
5698         error = make_stringf("Bad item name: '%s'", name.c_str());
5699         return ;
5700     }
5701 
5702     item_kind parsed = item_kind_by_name(name);
5703     if (parsed.base_type != OBJ_UNASSIGNED)
5704     {
5705         spec.base_type = parsed.base_type;
5706         spec.sub_type  = parsed.sub_type;
5707         spec.plus      = parsed.plus;
5708         spec.plus2     = parsed.plus2;
5709         return;
5710     }
5711 
5712     error = make_stringf("Bad item name: '%s'", name.c_str());
5713 }
5714 
parse_item_spec(string spec)5715 item_list::item_spec_slot item_list::parse_item_spec(string spec)
5716 {
5717     // lowercase(spec);
5718 
5719     item_spec_slot list;
5720 
5721     list.fix_slot = strip_tag(spec, "fix_slot");
5722 
5723     for (const string &specifier : split_string("/", spec))
5724     {
5725         item_spec result;
5726         if (parse_single_spec(result, specifier))
5727             list.ilist.push_back(result);
5728         else
5729             dprf(DIAG_DNGN, "Failed to parse: %s", specifier.c_str());
5730     }
5731 
5732     return list;
5733 }
5734 
5735 /////////////////////////////////////////////////////////////////////////
5736 // subst_spec
5737 
subst_spec(string _k,bool _f,const glyph_replacements_t & g)5738 subst_spec::subst_spec(string _k, bool _f, const glyph_replacements_t &g)
5739     : key(_k), count(-1), fix(_f), frozen_value(0), repl(g)
5740 {
5741 }
5742 
subst_spec(int _count,bool dofix,const glyph_replacements_t & g)5743 subst_spec::subst_spec(int _count, bool dofix, const glyph_replacements_t &g)
5744     : key(""), count(_count), fix(dofix), frozen_value(0), repl(g)
5745 {
5746 }
5747 
value()5748 int subst_spec::value()
5749 {
5750     if (frozen_value)
5751         return frozen_value;
5752 
5753     int cumulative = 0;
5754     int chosen = 0;
5755     for (glyph_weighted_replacement_t rep : repl)
5756         if (x_chance_in_y(rep.second, cumulative += rep.second))
5757             chosen = rep.first;
5758 
5759     if (fix)
5760         frozen_value = chosen;
5761 
5762     return chosen;
5763 }
5764 
5765 //////////////////////////////////////////////////////////////////////////
5766 // nsubst_spec
5767 
nsubst_spec(string _key,const vector<subst_spec> & _specs)5768 nsubst_spec::nsubst_spec(string _key, const vector<subst_spec> &_specs)
5769     : key(_key), specs(_specs)
5770 {
5771 }
5772 
5773 //////////////////////////////////////////////////////////////////////////
5774 // colour_spec
5775 
get_colour()5776 int colour_spec::get_colour()
5777 {
5778     if (fixed_colour != BLACK)
5779         return fixed_colour;
5780 
5781     int chosen = BLACK;
5782     int cweight = 0;
5783     for (map_weighted_colour col : colours)
5784         if (x_chance_in_y(col.second, cweight += col.second))
5785             chosen = col.first;
5786     if (fix)
5787         fixed_colour = chosen;
5788     return chosen;
5789 }
5790 
5791 //////////////////////////////////////////////////////////////////////////
5792 // fprop_spec
5793 
get_property()5794 feature_property_type fprop_spec::get_property()
5795 {
5796     if (fixed_prop != FPROP_NONE)
5797         return fixed_prop;
5798 
5799     feature_property_type chosen = FPROP_NONE;
5800     int cweight = 0;
5801     for (map_weighted_fprop fprop : fprops)
5802         if (x_chance_in_y(fprop.second, cweight += fprop.second))
5803             chosen = fprop.first;
5804     if (fix)
5805         fixed_prop = chosen;
5806     return chosen;
5807 }
5808 
5809 //////////////////////////////////////////////////////////////////////////
5810 // fheight_spec
5811 
get_height()5812 int fheight_spec::get_height()
5813 {
5814     if (fixed_height != INVALID_HEIGHT)
5815         return fixed_height;
5816 
5817     int chosen = INVALID_HEIGHT;
5818     int cweight = 0;
5819     for (map_weighted_fheight fh : fheights)
5820         if (x_chance_in_y(fh.second, cweight += fh.second))
5821             chosen = fh.first;
5822     if (fix)
5823         fixed_height = chosen;
5824     return chosen;
5825 }
5826 
5827 //////////////////////////////////////////////////////////////////////////
5828 // string_spec
5829 
get_property()5830 string string_spec::get_property()
5831 {
5832     if (!fixed_str.empty())
5833         return fixed_str;
5834 
5835     string chosen = "";
5836     int cweight = 0;
5837     for (const map_weighted_string &str : strlist)
5838         if (x_chance_in_y(str.second, cweight += str.second))
5839             chosen = str.first;
5840     if (fix)
5841         fixed_str = chosen;
5842     return chosen;
5843 }
5844 
5845 //////////////////////////////////////////////////////////////////////////
5846 // map_marker_spec
5847 
apply_transform(map_lines & map)5848 string map_marker_spec::apply_transform(map_lines &map)
5849 {
5850     vector<coord_def> positions = map.find_glyph(key);
5851 
5852     // Markers with no key are not an error.
5853     if (positions.empty())
5854         return "";
5855 
5856     for (coord_def p : positions)
5857     {
5858         try
5859         {
5860             map_marker *mark = create_marker();
5861             if (!mark)
5862             {
5863                 return make_stringf("Unable to parse marker from %s",
5864                                     marker.c_str());
5865             }
5866             mark->pos = p;
5867             map.add_marker(mark);
5868         }
5869         catch (const bad_map_marker &err)
5870         {
5871             return err.what();
5872         }
5873     }
5874     return "";
5875 }
5876 
create_marker()5877 map_marker *map_marker_spec::create_marker()
5878 {
5879     return lua_fn
5880         ? new map_lua_marker(*lua_fn)
5881         : map_marker::parse_marker(marker);
5882 }
5883 
5884 //////////////////////////////////////////////////////////////////////////
5885 // map_flags
map_flags()5886 map_flags::map_flags()
5887     : flags_set(0), flags_unset(0)
5888 {
5889 }
5890 
clear()5891 void map_flags::clear()
5892 {
5893     flags_set = flags_unset = 0;
5894 }
5895 
operator |=(const map_flags & o)5896 map_flags &map_flags::operator |= (const map_flags &o)
5897 {
5898     flags_set |= o.flags_set;
5899     flags_unset |= o.flags_unset;
5900 
5901     // In the event of conflict, the later flag set (o here) wins.
5902     flags_set &= ~o.flags_unset;
5903     flags_unset &= ~o.flags_set;
5904 
5905     return *this;
5906 }
5907 typedef map<string, unsigned long> flag_map;
5908 
parse(const string flag_list[],const string & s)5909 map_flags map_flags::parse(const string flag_list[], const string &s)
5910 {
5911     map_flags mf;
5912 
5913     const vector<string> segs = split_string("/", s);
5914 
5915     flag_map flag_vals;
5916     for (int i = 0; !flag_list[i].empty(); i++)
5917         flag_vals[flag_list[i]] = 1 << i;
5918 
5919     for (string flag: segs)
5920     {
5921         bool negate = false;
5922 
5923         if (flag[0] == '!')
5924         {
5925             flag   = flag.substr(1);
5926             negate = true;
5927         }
5928 
5929         if (unsigned long *val = map_find(flag_vals, flag))
5930         {
5931             if (negate)
5932                 mf.flags_unset |= *val;
5933             else
5934                 mf.flags_set |= *val;
5935         }
5936         else
5937             throw bad_map_flag(flag);
5938     }
5939 
5940     return mf;
5941 }
5942 
5943 //////////////////////////////////////////////////////////////////////////
5944 // keyed_mapspec
5945 
keyed_mapspec()5946 keyed_mapspec::keyed_mapspec()
5947     :  key_glyph(-1), feat(), item(), mons()
5948 {
5949 }
5950 
set_feat(const string & s,bool fix)5951 string keyed_mapspec::set_feat(const string &s, bool fix)
5952 {
5953     err.clear();
5954     parse_features(s);
5955     feat.fix_slot = fix;
5956 
5957     // Fix this feature.
5958     if (fix)
5959         get_feat();
5960 
5961     return err;
5962 }
5963 
copy_feat(const keyed_mapspec & spec)5964 void keyed_mapspec::copy_feat(const keyed_mapspec &spec)
5965 {
5966     feat = spec.feat;
5967 }
5968 
parse_features(const string & s)5969 void keyed_mapspec::parse_features(const string &s)
5970 {
5971     feat.feats.clear();
5972     for (const string &spec : split_string("/", s))
5973     {
5974         feature_spec_list feats = parse_feature(spec);
5975         if (!err.empty())
5976             return;
5977         feat.feats.insert(feat.feats.end(),
5978                            feats.begin(),
5979                            feats.end());
5980     }
5981 }
5982 
5983 /**
5984  * Convert a trap string into a trap_spec.
5985  *
5986  * This function converts an incoming trap specification string from a vault
5987  * into a trap_spec.
5988  *
5989  * @param s       The string to be parsed.
5990  * @param weight  The weight of this string.
5991  * @return        A feature_spec with the contained, parsed trap_spec stored via
5992  *                unique_ptr as feature_spec->trap.
5993 **/
parse_trap(string s,int weight)5994 feature_spec keyed_mapspec::parse_trap(string s, int weight)
5995 {
5996     strip_tag(s, "trap");
5997 
5998     trim_string(s);
5999     lowercase(s);
6000 
6001     const int trap = str_to_trap(s);
6002     if (trap == -1)
6003         err = make_stringf("bad trap name: '%s'", s.c_str());
6004 
6005     feature_spec fspec(1, weight);
6006     fspec.trap.reset(new trap_spec(static_cast<trap_type>(trap)));
6007     return fspec;
6008 }
6009 
6010 /**
6011  * Convert a shop string into a shop_spec.
6012  *
6013  * This function converts an incoming shop specification string from a vault
6014  * into a shop_spec.
6015  *
6016  * @param s        The string to be parsed.
6017  * @param weight   The weight of this string.
6018  * @param mimic    What kind of mimic (if any) to set for this shop.
6019  * @param no_mimic Whether to prohibit mimics altogether for this shop.
6020  * @return         A feature_spec with the contained, parsed shop_spec stored
6021  *                 via unique_ptr as feature_spec->shop.
6022 **/
parse_shop(string s,int weight,int mimic,bool no_mimic)6023 feature_spec keyed_mapspec::parse_shop(string s, int weight, int mimic,
6024                                        bool no_mimic)
6025 {
6026     string orig(s);
6027 
6028     strip_tag(s, "shop");
6029     trim_string(s);
6030 
6031     bool use_all = strip_tag(s, "use_all");
6032 
6033     const bool gozag = strip_tag(s, "gozag");
6034 
6035     string shop_name = replace_all_of(strip_tag_prefix(s, "name:"), "_", " ");
6036     string shop_type_name = replace_all_of(strip_tag_prefix(s, "type:"),
6037                                            "_", " ");
6038     string shop_suffix_name = replace_all_of(strip_tag_prefix(s, "suffix:"),
6039                                              "_", " ");
6040 
6041     int num_items = min(20, strip_number_tag(s, "count:"));
6042     if (num_items == TAG_UNFOUND)
6043         num_items = -1;
6044 
6045     int greed = strip_number_tag(s, "greed:");
6046     if (greed == TAG_UNFOUND)
6047         greed = -1;
6048 
6049     vector<string> parts = split_string(";", s);
6050     string main_part = parts[0];
6051 
6052     const shop_type shop = str_to_shoptype(main_part);
6053     if (shop == SHOP_UNASSIGNED)
6054         err = make_stringf("bad shop type: '%s'", s.c_str());
6055 
6056     if (parts.size() > 2)
6057         err = make_stringf("too many semi-colons for '%s' spec", orig.c_str());
6058 
6059     item_list items;
6060     if (err.empty() && parts.size() == 2)
6061     {
6062         string item_list = parts[1];
6063         vector<string> str_items = split_string("|", item_list);
6064         for (const string &si : str_items)
6065         {
6066             err = items.add_item(si);
6067             if (!err.empty())
6068                 break;
6069         }
6070     }
6071 
6072     feature_spec fspec(-1, weight, mimic, no_mimic);
6073     fspec.shop.reset(new shop_spec(shop, shop_name, shop_type_name,
6074                                    shop_suffix_name, greed,
6075                                    num_items, use_all, gozag));
6076     fspec.shop->items = items;
6077     return fspec;
6078 }
6079 
parse_feature(const string & str)6080 feature_spec_list keyed_mapspec::parse_feature(const string &str)
6081 {
6082     string s = str;
6083     int weight = find_weight(s);
6084     if (weight == TAG_UNFOUND || weight <= 0)
6085         weight = 10;
6086 
6087     int mimic = strip_number_tag(s, "mimic:");
6088     if (mimic == TAG_UNFOUND && strip_tag(s, "mimic"))
6089         mimic = 1;
6090     const bool no_mimic = strip_tag(s, "no_mimic");
6091 
6092     trim_string(s);
6093 
6094     feature_spec_list list;
6095     if (s.length() == 1)
6096     {
6097         feature_spec fsp(-1, weight, mimic, no_mimic);
6098         fsp.glyph = s[0];
6099         list.push_back(fsp);
6100     }
6101     else if (strip_tag(s, "trap") || s == "web")
6102         list.push_back(parse_trap(s, weight));
6103     else if (strip_tag(s, "shop"))
6104         list.push_back(parse_shop(s, weight, mimic, no_mimic));
6105     else if (auto ftype = dungeon_feature_by_name(s)) // DNGN_UNSEEN == 0
6106         list.emplace_back(ftype, weight, mimic, no_mimic);
6107     else
6108         err = make_stringf("no features matching \"%s\"", str.c_str());
6109 
6110     return list;
6111 }
6112 
set_mons(const string & s,bool fix)6113 string keyed_mapspec::set_mons(const string &s, bool fix)
6114 {
6115     err.clear();
6116     mons.clear();
6117 
6118     for (const string &segment : split_string(",", s))
6119     {
6120         const string error = mons.add_mons(segment, fix);
6121         if (!error.empty())
6122             return error;
6123     }
6124 
6125     return "";
6126 }
6127 
set_item(const string & s,bool fix)6128 string keyed_mapspec::set_item(const string &s, bool fix)
6129 {
6130     err.clear();
6131     item.clear();
6132 
6133     for (const string &seg : split_string(",", s))
6134     {
6135         err = item.add_item(seg, fix);
6136         if (!err.empty())
6137             return err;
6138     }
6139 
6140     return err;
6141 }
6142 
set_mask(const string & s,bool)6143 string keyed_mapspec::set_mask(const string &s, bool /*garbage*/)
6144 {
6145     err.clear();
6146 
6147     try
6148     {
6149         // Be sure to change the order of map_mask_type to match!
6150         static string flag_list[] =
6151             {"vault", "no_item_gen", "no_monster_gen", "no_pool_fixup",
6152              "UNUSED",
6153              "no_wall_fixup", "opaque", "no_trap_gen", ""};
6154         map_mask |= map_flags::parse(flag_list, s);
6155     }
6156     catch (const bad_map_flag &error)
6157     {
6158         err = make_stringf("Unknown flag: '%s'", error.what());
6159     }
6160 
6161     return err;
6162 }
6163 
copy_mons(const keyed_mapspec & spec)6164 void keyed_mapspec::copy_mons(const keyed_mapspec &spec)
6165 {
6166     mons = spec.mons;
6167 }
6168 
copy_item(const keyed_mapspec & spec)6169 void keyed_mapspec::copy_item(const keyed_mapspec &spec)
6170 {
6171     item = spec.item;
6172 }
6173 
copy_mask(const keyed_mapspec & spec)6174 void keyed_mapspec::copy_mask(const keyed_mapspec &spec)
6175 {
6176     map_mask = spec.map_mask;
6177 }
6178 
get_feat()6179 feature_spec keyed_mapspec::get_feat()
6180 {
6181     return feat.get_feat('.');
6182 }
6183 
get_monsters()6184 mons_list &keyed_mapspec::get_monsters()
6185 {
6186     return mons;
6187 }
6188 
get_items()6189 item_list &keyed_mapspec::get_items()
6190 {
6191     return item;
6192 }
6193 
get_mask()6194 map_flags &keyed_mapspec::get_mask()
6195 {
6196     return map_mask;
6197 }
6198 
replaces_glyph()6199 bool keyed_mapspec::replaces_glyph()
6200 {
6201     return !(mons.empty() && item.empty() && feat.feats.empty());
6202 }
6203 
6204 //////////////////////////////////////////////////////////////////////////
6205 // feature_spec and feature_slot
6206 
feature_spec()6207 feature_spec::feature_spec()
6208 {
6209     genweight = 0;
6210     feat = 0;
6211     glyph = -1;
6212     shop.reset(nullptr);
6213     trap.reset(nullptr);
6214     mimic = 0;
6215     no_mimic = false;
6216 }
6217 
feature_spec(int f,int wt,int _mimic,bool _no_mimic)6218 feature_spec::feature_spec(int f, int wt, int _mimic, bool _no_mimic)
6219 {
6220     genweight = wt;
6221     feat = f;
6222     glyph = -1;
6223     shop.reset(nullptr);
6224     trap.reset(nullptr);
6225     mimic = _mimic;
6226     no_mimic = _no_mimic;
6227 }
6228 
feature_spec(const feature_spec & other)6229 feature_spec::feature_spec(const feature_spec &other)
6230 {
6231     init_with(other);
6232 }
6233 
operator =(const feature_spec & other)6234 feature_spec& feature_spec::operator = (const feature_spec& other)
6235 {
6236     if (this != &other)
6237         init_with(other);
6238     return *this;
6239 }
6240 
init_with(const feature_spec & other)6241 void feature_spec::init_with(const feature_spec& other)
6242 {
6243     genweight = other.genweight;
6244     feat = other.feat;
6245     glyph = other.glyph;
6246     mimic = other.mimic;
6247     no_mimic = other.no_mimic;
6248 
6249     if (other.trap)
6250         trap.reset(new trap_spec(*other.trap));
6251     else
6252         trap.reset(nullptr);
6253 
6254     if (other.shop)
6255         shop.reset(new shop_spec(*other.shop));
6256     else
6257         shop.reset(nullptr);
6258 }
6259 
feature_slot()6260 feature_slot::feature_slot() : feats(), fix_slot(false)
6261 {
6262 }
6263 
get_feat(int def_glyph)6264 feature_spec feature_slot::get_feat(int def_glyph)
6265 {
6266     int tweight = 0;
6267     feature_spec chosen_feat = feature_spec(DNGN_FLOOR);
6268 
6269     if (def_glyph != -1)
6270     {
6271         chosen_feat.feat = -1;
6272         chosen_feat.glyph = def_glyph;
6273     }
6274 
6275     for (const feature_spec &feat : feats)
6276         if (x_chance_in_y(feat.genweight, tweight += feat.genweight))
6277             chosen_feat = feat;
6278 
6279     if (fix_slot)
6280     {
6281         feats.clear();
6282         feats.push_back(chosen_feat);
6283     }
6284     return chosen_feat;
6285 }
6286