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