/* * File: mapdef.cc * Summary: Support code for Crawl des files. * Created by: dshaligram on Wed Nov 22 08:41:20 2006 UTC */ #include "AppHdr.h" #include #include #include #include #include #include #include "artefact.h" #include "branch.h" #include "colour.h" #include "coord.h" #include "coordit.h" #include "describe.h" #include "directn.h" #include "dungeon.h" #include "exclude.h" #include "files.h" #include "initfile.h" #include "invent.h" #include "items.h" #include "l_defs.h" #include "libutil.h" #include "mapdef.h" #include "mapmark.h" #include "maps.h" #include "misc.h" #include "mon-cast.h" #include "mon-place.h" #include "mon-util.h" #include "place.h" #include "random.h" #include "religion.h" #include "spl-util.h" #include "spl-book.h" #include "stuff.h" #include "env.h" #include "tags.h" #ifdef USE_TILE #include "tiledef-dngn.h" #include "tiledef-player.h" #endif static const char *map_section_names[] = { "", "north", "south", "east", "west", "northwest", "northeast", "southwest", "southeast", "encompass", "float", }; // atoi that rejects strings containing non-numeric trailing characters. // returns defval for invalid input. template static V strict_aton(const char *s, V defval = 0) { char *end; const V res = strtol(s, &end, 10); return (!*s || *end) ? defval : res; } const char *map_section_name(int msect) { if (msect < 0 || msect >= MAP_NUM_SECTION_TYPES) return ""; return map_section_names[msect]; } static int find_weight(std::string &s, int defweight = TAG_UNFOUND) { int weight = strip_number_tag(s, "weight:"); if (weight == TAG_UNFOUND) weight = strip_number_tag(s, "w:"); return (weight == TAG_UNFOUND? defweight : weight); } std::string mapdef_split_key_item(const std::string &s, std::string *key, int *separator, std::string *arg, int key_max_len) { std::string::size_type norm = s.find("=", 1), fixe = s.find(":", 1); const std::string::size_type sep = norm < fixe? norm : fixe; if (sep == std::string::npos) return ("malformed declaration - must use = or :"); *key = trimmed_string(s.substr(0, sep)); std::string substitute = trimmed_string(s.substr(sep + 1)); if (key->empty() || (key_max_len != -1 && (int) key->length() > key_max_len)) { return make_stringf( "selector '%s' must be <= %d characters in '%s'", key->c_str(), key_max_len, s.c_str()); } if (substitute.empty()) return make_stringf("no substitute defined in '%s'", s.c_str()); *arg = substitute; *separator = s[sep]; return (""); } /////////////////////////////////////////////// // level_range // level_range::level_range(branch_type br, int s, int d) : level_type(LEVEL_DUNGEON), branch(br), shallowest(), deepest(), deny(false) { set(s, d); } level_range::level_range(const raw_range &r) : branch(r.branch), shallowest(r.shallowest), deepest(r.deepest), deny(r.deny) { } void level_range::write(writer& outf) const { marshallShort(outf, level_type); marshallShort(outf, branch); marshallShort(outf, shallowest); marshallShort(outf, deepest); marshallByte(outf, deny); } void level_range::read(reader& inf) { level_type = static_cast( unmarshallShort(inf) ); branch = static_cast( unmarshallShort(inf) ); shallowest = unmarshallShort(inf); deepest = unmarshallShort(inf); deny = unmarshallByte(inf); } std::string level_range::str_depth_range() const { if (shallowest == -1) return (":??"); if (deepest >= branches[branch].depth) return (shallowest == 1? "" : make_stringf("%d-", shallowest)); if (shallowest == deepest) return make_stringf(":%d", shallowest); return make_stringf(":%d-%d", shallowest, deepest); } std::string level_range::describe() const { return make_stringf("%s%s%s", deny? "!" : "", branch == NUM_BRANCHES? "Any" : branches[branch].abbrevname, str_depth_range().c_str()); } level_range::operator raw_range () const { raw_range r; r.branch = branch; r.shallowest = shallowest; r.deepest = deepest; r.deny = deny; return (r); } void level_range::set(const std::string &br, int s, int d) throw (std::string) { if (br == "any" || br == "Any") branch = NUM_BRANCHES; else if ((branch = str_to_branch(br)) == NUM_BRANCHES && (level_type = str_to_level_area_type(br)) == LEVEL_DUNGEON) throw make_stringf("Unknown branch: '%s'", br.c_str()); shallowest = s; deepest = d; if (branch != NUM_BRANCHES) { if (shallowest == -1) shallowest = branches[branch].depth; if (deepest == -1) deepest = branches[branch].depth; } if (deepest < shallowest) throw make_stringf("Level-range %s:%d-%d is malformed", br.c_str(), s, d); } level_range level_range::parse(std::string s) throw (std::string) { level_range lr; trim_string(s); if (s[0] == '!') { lr.deny = true; s = trimmed_string(s.substr(1)); } std::string::size_type cpos = s.find(':'); if (cpos == std::string::npos) parse_partial(lr, s); else { std::string branch = trimmed_string(s.substr(0, cpos)); std::string depth = trimmed_string(s.substr(cpos + 1)); parse_depth_range(depth, &lr.shallowest, &lr.deepest); lr.set(branch, lr.shallowest, lr.deepest); } return (lr); } void level_range::parse_partial(level_range &lr, const std::string &s) throw (std::string) { if (isdigit(s[0])) { lr.branch = NUM_BRANCHES; parse_depth_range(s, &lr.shallowest, &lr.deepest); } else lr.set(s, 1, 100); } void level_range::parse_depth_range(const std::string &s, int *l, int *h) throw (std::string) { if (s == "*") { *l = 1; *h = 100; return; } if (s == "$") { *l = *h = -1; return; } std::string::size_type hy = s.find('-'); if (hy == std::string::npos) { *l = *h = strict_aton(s.c_str()); if (!*l) throw std::string("Bad depth: ") + s; } else { *l = strict_aton(s.substr(0, hy).c_str()); std::string tail = s.substr(hy + 1); if (tail.empty()) *h = 100; else *h = strict_aton(tail.c_str()); if (!*l || !*h || *l > *h) throw std::string("Bad depth: ") + s; } } void level_range::set(int s, int d) { shallowest = s; deepest = d; if (deepest == -1 || deepest < shallowest) deepest = shallowest; } void level_range::reset() { deepest = shallowest = -1; level_type = LEVEL_DUNGEON; } bool level_range::matches(const level_id &lid) const { // Level types must always match. if (lid.level_type != level_type) return (false); if (lid.level_type != LEVEL_DUNGEON) return (true); if (branch == NUM_BRANCHES) return (matches(absdungeon_depth(lid.branch, lid.depth))); else return (branch == lid.branch && lid.depth >= shallowest && lid.depth <= deepest); } bool level_range::matches(int x) const { // [ds] The level ranges used by the game are zero-based, adjust for that. ++x; return (x >= shallowest && x <= deepest); } bool level_range::operator == (const level_range &lr) const { return (deny == lr.deny && level_type == lr.level_type && (level_type != LEVEL_DUNGEON || (shallowest == lr.shallowest && deepest == lr.deepest && branch == lr.branch))); } bool level_range::valid() const { return (shallowest > 0 && deepest >= shallowest); } int level_range::span() const { return (deepest - shallowest); } //////////////////////////////////////////////////////////////////////// // map_lines map_lines::map_lines() : markers(), lines(), overlay(), map_width(0), solid_north(false), solid_east(false), solid_south(false), solid_west(false), solid_checked(false) { } map_lines::map_lines(const map_lines &map) { init_from(map); } rectangle_iterator map_lines::get_iter() const { ASSERT(width() > 0); ASSERT(height() > 0); coord_def tl(0, 0); coord_def br(width() - 1, height() - 1); return rectangle_iterator(tl, br); } char map_lines::operator () (const coord_def &c) const { return (lines[c.y][c.x]); } char& map_lines::operator () (const coord_def &c) { return (lines[c.y][c.x]); } char map_lines::operator () (int x, int y) const { return (lines[y][x]); } char& map_lines::operator () (int x, int y) { return (lines[y][x]); } bool map_lines::in_bounds(const coord_def &c) const { return (c.x >= 0 && c.y >= 0 && c.x < width() && c.y < height()); } bool map_lines::in_map(const coord_def &c) const { return (lines[c.y][c.x] != ' '); } map_lines &map_lines::operator = (const map_lines &map) { if (this != &map) init_from(map); return (*this); } map_lines::~map_lines() { clear_markers(); } void map_lines::init_from(const map_lines &map) { // Markers have to be regenerated, they will not be copied. clear_markers(); overlay.reset(NULL); lines = map.lines; map_width = map.map_width; solid_north = map.solid_north; solid_east = map.solid_east; solid_south = map.solid_south; solid_west = map.solid_west; solid_checked = map.solid_checked; } template void map_lines::clear_vector(V &vect) { for (int i = 0, vsize = vect.size(); i < vsize; ++i) delete vect[i]; vect.clear(); } void map_lines::clear_markers() { clear_vector(markers); } void map_lines::add_marker(map_marker *marker) { markers.push_back(marker); } std::string map_lines::add_feature_marker(const std::string &s) { std::string key, arg; int sep = 0; std::string err = mapdef_split_key_item(s, &key, &sep, &arg, -1); if (!err.empty()) return (err); map_marker_spec spec(key, arg); spec.apply_transform(*this); return (""); } std::string map_lines::add_lua_marker(const std::string &key, const lua_datum &function) { map_marker_spec spec(key, function); spec.apply_transform(*this); return (""); } void map_lines::apply_markers(const coord_def &c) { for (int i = 0, vsize = markers.size(); i < vsize; ++i) { markers[i]->pos += c; env.markers.add(markers[i]); } // *not* clear_markers() since we've offloaded marker ownership to // the crawl env. markers.clear(); } void map_lines::apply_grid_overlay(const coord_def &c) { if (!overlay.get()) return; for (int y = height() - 1; y >= 0; --y) for (int x = width() - 1; x >= 0; --x) { coord_def gc(c.x + x, c.y + y); const int colour = (*overlay)(x, y).colour; if (colour) dgn_set_grid_colour_at(gc, colour); const int property = (*overlay)(x, y).property; if (property >= FPROP_BLOODY) // Over-ride whatever property is already there. env.pgrid(gc) |= property; #ifdef USE_TILE int floor = (*overlay)(x, y).floortile; if (floor) { if (colour) floor = tile_dngn_coloured(floor, colour); int offset = random2(tile_dngn_count(floor)); env.tile_flv(gc).floor = floor + offset; } int rock = (*overlay)(x, y).rocktile; if (rock) { if (colour) rock = tile_dngn_coloured(rock, colour); int offset = random2(tile_dngn_count(rock)); env.tile_flv(gc).wall = rock + offset; } int tile = (*overlay)(x, y).tile; if (tile) { if (colour) tile = tile_dngn_coloured(tile, colour); int offset = random2(tile_dngn_count(tile)); if (grd(gc) == DNGN_FLOOR && !floor) env.tile_flv(gc).floor = tile + offset; else if (grd(gc) == DNGN_ROCK_WALL && !rock) env.tile_flv(gc).wall = tile + offset; else { if ((*overlay)(x, y).no_random) offset = 0; env.tile_flv(gc).feat = tile + offset; } } #endif } } void map_lines::apply_overlays(const coord_def &c) { apply_markers(c); apply_grid_overlay(c); } const std::vector &map_lines::get_lines() const { return (lines); } std::vector &map_lines::get_lines() { return (lines); } void map_lines::add_line(const std::string &s) { lines.push_back(s); if (static_cast(s.length()) > map_width) map_width = s.length(); } std::string map_lines::clean_shuffle(std::string s) { return (replace_all_of(s, " \t", "")); } std::string map_lines::check_block_shuffle(const std::string &s) { const std::vector segs = split_string("/", s); const unsigned seglen = segs[0].length(); for (int i = 1, vsize = segs.size(); i < vsize; ++i) { if (seglen != segs[i].length()) return ("block shuffle segment length mismatch"); } return (""); } std::string map_lines::check_shuffle(std::string &s) { if (s.find(',') != std::string::npos) return ("use / for block shuffle, or multiple SHUFFLE: lines"); s = clean_shuffle(s); if (s.find('/') != std::string::npos) return check_block_shuffle(s); return (""); } std::string map_lines::parse_glyph_replacements(std::string s, glyph_replacements_t &gly) { s = replace_all_of(s, "\t", " "); std::vector segs = split_string(" ", s); for (int i = 0, vsize = segs.size(); i < vsize; ++i) { const std::string &is = segs[i]; if (is.length() > 2 && is[1] == ':') { const int glych = is[0]; int weight = atoi( is.substr(2).c_str() ); if (weight < 1) weight = 10; gly.push_back( glyph_weighted_replacement_t(glych, weight) ); } else { for (int c = 0, cs = is.length(); c < cs; ++c) gly.push_back( glyph_weighted_replacement_t(is[c], 10) ); } } return (""); } template std::string parse_weighted_str(const std::string &spec, T &list) { std::vector speclist = split_string("/", spec); for (int i = 0, vsize = speclist.size(); i < vsize; ++i) { std::string val = speclist[i]; lowercase(val); int weight = find_weight(val); if (weight == TAG_UNFOUND) { weight = 10; // :number suffix? std::string::size_type cpos = val.find(':'); if (cpos != std::string::npos) { weight = atoi(val.substr(cpos + 1).c_str()); if (weight <= 0) weight = 10; val.erase(cpos); trim_string(val); } } if (!list.parse(val, weight)) { return make_stringf("bad spec: '%s' in '%s'", val.c_str(), spec.c_str()); } } return (""); } bool map_colour_list::parse(const std::string &col, int weight) { const int colour = col == "none" ? BLACK : str_to_colour(col, -1); if (colour == -1) return (false); push_back(map_weighted_colour(colour, weight)); return (true); } std::string map_lines::add_colour(const std::string &sub) { std::string s = trimmed_string(sub); if (s.empty()) return (""); int sep = 0; std::string key; std::string substitute; std::string err = mapdef_split_key_item(sub, &key, &sep, &substitute, -1); if (!err.empty()) return (err); map_colour_list colours; err = parse_weighted_str(substitute, colours); if (!err.empty()) return (err); colour_spec spec(key, sep == ':', colours); overlay_colours(spec); return (""); } bool map_fprop_list::parse(const std::string &fp, int weight) { unsigned long fprop; if (fp == "nothing") fprop = FPROP_NONE; else if (fp.empty()) return (false); else if (str_to_fprop(fp) == FPROP_NONE) return false; else fprop = str_to_fprop(fp); push_back(map_weighted_fprop(fprop, weight)); return true; } std::string map_lines::add_fproperty(const std::string &sub) { std::string s = trimmed_string(sub); if (s.empty()) return (""); int sep = 0; std::string key; std::string substitute; std::string err = mapdef_split_key_item(sub, &key, &sep, &substitute, -1); if (!err.empty()) return (err); map_fprop_list fprops; err = parse_weighted_str(substitute, fprops); if (!err.empty()) return (err); fprop_spec spec(key, sep == ':', fprops); overlay_fprops(spec); return (""); } bool map_string_list::parse(const std::string &fp, int weight) { push_back(map_weighted_string(fp, weight)); return (!fp.empty()); } std::string map_lines::add_subst(const std::string &sub) { std::string s = trimmed_string(sub); if (s.empty()) return (""); int sep = 0; std::string key; std::string substitute; std::string err = mapdef_split_key_item(sub, &key, &sep, &substitute, -1); if (!err.empty()) return (err); glyph_replacements_t repl; err = parse_glyph_replacements(substitute, repl); if (!err.empty()) return (err); subst_spec spec(key, sep == ':', repl); subst(spec); return (""); } std::string map_lines::parse_nsubst_spec(const std::string &s, subst_spec &spec) { std::string key, arg; int sep; std::string err = mapdef_split_key_item(s, &key, &sep, &arg, -1); if (!err.empty()) return err; const int count = key == "*"? -1 : atoi(key.c_str()); if (!count) return make_stringf("Illegal spec: %s", s.c_str()); glyph_replacements_t repl; err = parse_glyph_replacements(arg, repl); if (!err.empty()) return (err); spec = subst_spec(count, sep == ':', repl); return (""); } std::string map_lines::add_nsubst(const std::string &s) { std::vector substs; int sep; std::string key, arg; std::string err = mapdef_split_key_item(s, &key, &sep, &arg, -1); if (!err.empty()) return (err); std::vector segs = split_string("/", arg); for (int i = 0, vsize = segs.size(); i < vsize; ++i) { std::string &ns = segs[i]; if (ns.find('=') == std::string::npos && ns.find(':') == std::string::npos) { if (i < vsize - 1) ns = "1=" + ns; else ns = "*=" + ns; } subst_spec spec; err = parse_nsubst_spec(ns, spec); if (!err.empty()) return (make_stringf("Bad NSUBST spec: %s (%s)", s.c_str(), err.c_str())); substs.push_back(spec); } nsubst_spec spec(key, substs); nsubst(spec); return (""); } std::string map_lines::add_shuffle(const std::string &raws) { std::string s = raws; const std::string err = check_shuffle(s); if (err.empty()) resolve_shuffle(s); return (err); } int map_lines::width() const { return map_width; } int map_lines::height() const { return lines.size(); } void map_lines::extend(int min_width, int min_height, char fill) { min_width = std::max(1, min_width); min_height = std::max(1, min_height); bool dirty = false; int old_width = width(); int old_height = height(); if (static_cast(lines.size()) < min_height) { dirty = true; while (static_cast(lines.size()) < min_height) add_line(std::string(min_width, fill)); } if (width() < min_width) { dirty = true; lines[0] += std::string(min_width - width(), fill); map_width = std::max(map_width, min_width); } if (!dirty) return; normalise(fill); // Extend overlay matrix as well. if (overlay.get()) { std::auto_ptr new_overlay( new overlay_matrix(width(), height())); for (int y = 0; y < old_height; ++y) for (int x = 0; x < old_width; ++x) (*new_overlay)(x, y) = (*overlay)(x, y); overlay = new_overlay; } } coord_def map_lines::size() const { return coord_def(width(), height()); } int map_lines::glyph(int x, int y) const { return lines[y][x]; } int map_lines::glyph(const coord_def &c) const { return glyph(c.x, c.y); } bool map_lines::is_solid(int gly) const { return (gly == 'x' || gly == 'c' || gly == 'b' || gly == 'v' || gly == 't'); } void map_lines::check_borders() { if (solid_checked) return; const int wide = width(), high = height(); solid_north = solid_south = true; for (int x = 0; x < wide && (solid_north || solid_south); ++x) { if (solid_north && !is_solid(glyph(x, 0))) solid_north = false; if (solid_south && !is_solid(glyph(x, high - 1))) solid_south = false; } solid_east = solid_west = true; for (int y = 0; y < high && (solid_east || solid_west); ++y) { if (solid_west && !is_solid(glyph(0, y))) solid_west = false; if (solid_east && !is_solid(glyph(wide - 1, y))) solid_east = false; } solid_checked = true; } bool map_lines::solid_borders(map_section_type border) { check_borders(); switch (border) { case MAP_NORTH: return solid_north; case MAP_SOUTH: return solid_south; case MAP_EAST: return solid_east; case MAP_WEST: return solid_west; case MAP_NORTHEAST: return solid_north && solid_east; case MAP_NORTHWEST: return solid_north && solid_west; case MAP_SOUTHEAST: return solid_south && solid_east; case MAP_SOUTHWEST: return solid_south && solid_west; default: return (false); } } void map_lines::clear() { clear_markers(); lines.clear(); keyspecs.clear(); overlay.reset(NULL); map_width = 0; solid_checked = false; // First non-legal character. next_keyspec_idx = 256; } void map_lines::subst(std::string &s, subst_spec &spec) { std::string::size_type pos = 0; while ((pos = s.find_first_of(spec.key, pos)) != std::string::npos) s[pos++] = spec.value(); } void map_lines::subst(subst_spec &spec) { ASSERT(!spec.key.empty()); for (int y = 0, ysize = lines.size(); y < ysize; ++y) subst(lines[y], spec); } void map_lines::overlay_colours(colour_spec &spec) { if (!overlay.get()) overlay.reset(new overlay_matrix(width(), height())); for (int y = 0, ysize = lines.size(); y < ysize; ++y) { std::string::size_type pos = 0; while ((pos = lines[y].find_first_of(spec.key, pos)) != std::string::npos) { (*overlay)(pos, y).colour = spec.get_colour(); ++pos; } } } void map_lines::overlay_fprops(fprop_spec &spec) { if (!overlay.get()) overlay.reset(new overlay_matrix(width(), height())); for (int y = 0, ysize = lines.size(); y < ysize; ++y) { std::string::size_type pos = 0; while ((pos = lines[y].find_first_of(spec.key, pos)) != std::string::npos) { (*overlay)(pos, y).property |= spec.get_property(); ++pos; } } } void map_lines::fill_mask_matrix(const std::string &glyphs, const coord_def &tl, const coord_def &br, Matrix &flags) { ASSERT(tl.x >= 0); ASSERT(tl.y >= 0); ASSERT(br.x < width()); ASSERT(br.y < height()); ASSERT(tl.x <= br.x); ASSERT(tl.y <= br.y); for (int y = tl.y; y <= br.y; ++y) for (int x = tl.x; x <= br.x; ++x) { int ox = x - tl.x; int oy = y - tl.y; flags(ox, oy) = (strchr(glyphs.c_str(), (*this)(x, y)) != NULL); } } void map_lines::merge_subvault(const coord_def &mtl, const coord_def &mbr, const Matrix &mask, const map_def &vmap) { const map_lines &vlines = vmap.map; // If vault is bigger than the mask region (mtl, mbr), then it gets // randomly centered. (vtl, vbr) stores the vault's region. coord_def vtl = mtl; coord_def vbr = mbr; int width_diff = (mbr.x - mtl.x + 1) - vlines.width(); int height_diff = (mbr.y - mtl.y + 1) - vlines.height(); // Adjust vault coords with a random offset. int ox = random2(width_diff + 1); int oy = random2(height_diff + 1); vtl.x += ox; vtl.y += oy; vbr.x -= (width_diff - ox); vbr.y -= (height_diff - oy); if (!overlay.get()) overlay.reset(new overlay_matrix(width(), height())); // Clear any markers in the vault's grid for (size_t i = 0; i < markers.size(); ++i) { map_marker *mm = markers[i]; if (mm->pos.x >= vtl.x && mm->pos.x <= vbr.x && mm->pos.y >= vtl.y && mm->pos.y <= vbr.y) { // Erase this marker. markers[i] = markers[markers.size() - 1]; markers.resize(markers.size() - 1); i--; } } // Copy markers and update locations. for (size_t i = 0; i < vlines.markers.size(); ++i) { map_marker *mm = vlines.markers[i]; coord_def mapc = mm->pos + vtl; coord_def maskc = mapc - mtl; if (!mask(maskc.x, maskc.y)) continue; map_marker *clone = mm->clone(); clone->pos = mapc; add_marker(clone); } // Cache keyspecs we've already pushed into the extended keyspec space. // If !ksmap[key], then we haven't seen the 'key' glyph before. keyspec_map ksmap(0); for (int y = mtl.y; y <= mbr.y; ++y) for (int x = mtl.x; x <= mbr.x; ++x) { int mx = x - mtl.x; int my = y - mtl.y; if (!mask(mx, my)) continue; // Outside subvault? if (x < vtl.x || x > vbr.x || y < vtl.y || y > vbr.y) continue; int vx = x - vtl.x; int vy = y - vtl.y; coord_def vc(vx, vy); char c = vlines(vc); if (c == ' ') continue; // Merge keyspecs. // Push vault keyspecs into extended keyspecs. // Push MONS/ITEM into KMONS/KITEM keyspecs. // Generate KFEAT keyspecs for any normal glyphs. int idx; if (ksmap[c]) { // Already generated this keyed_pmapspec. idx = ksmap[c]; } else { idx = next_keyspec_idx++; ASSERT(idx > 0); if (c != SUBVAULT_GLYPH) ksmap[c] = idx; const keyed_mapspec *kspec = vlines.mapspec_at(vc); // If c is a SUBVAULT_GLYPH, it came from a sub-subvault. // Sub-subvaults should always have mapspecs at this point. ASSERT(c != SUBVAULT_GLYPH || kspec); if (kspec) { // Copy vault keyspec into the extended keyspecs. keyspecs[idx] = *kspec; keyspecs[idx].key_glyph = SUBVAULT_GLYPH; } else if (map_def::valid_monster_array_glyph(c)) { // Translate monster array into keyed_mapspec keyed_mapspec &km = keyspecs[idx]; km.key_glyph = SUBVAULT_GLYPH; km.feat.feats.clear(); feature_spec spec(-1); spec.glyph = '.'; km.feat.feats.insert(km.feat.feats.begin(), spec); int slot = map_def::monster_array_glyph_to_slot(c); km.mons.set_from_slot(vmap.mons, slot); } else if (map_def::valid_item_array_glyph(c)) { // Translate item array into keyed_mapspec keyed_mapspec &km = keyspecs[idx]; km.key_glyph = SUBVAULT_GLYPH; km.feat.feats.clear(); feature_spec spec(-1); spec.glyph = '.'; km.feat.feats.insert(km.feat.feats.begin(), spec); int slot = map_def::item_array_glyph_to_slot(c); km.item.set_from_slot(vmap.items, slot); } else { // Normal glyph. Turn into a feature keyspec. // This is valid for non-array items and monsters // as well, e.g. '$' and '8'. keyed_mapspec &km = keyspecs[idx]; km.key_glyph = SUBVAULT_GLYPH; km.feat.feats.clear(); feature_spec spec(-1); spec.glyph = c; km.feat.feats.insert(km.feat.feats.begin(), spec); } } // Finally, handle merging the cell itself. // Glyph becomes SUBVAULT_GLYPH. (The old glyph gets merged into a // keyspec, above). This is so that the glyphs that are included // from a subvault are immutable by the parent vault. Otherwise, // latent transformations (like KMONS or KITEM) from the parent // vault might confusingly modify a glyph from the subvault. // // NOTE: It'd be possible to allow subvaults to be modified by the // parent vault, but KMONS/KITEM/KFEAT/MONS/ITEM would have to // apply immediately instead of latently. They would also then // need to be stored per-coord, rather than per-glyph. (*this)(x, y) = SUBVAULT_GLYPH; // Merge overlays if (vlines.overlay.get()) { (*overlay)(x, y) = (*vlines.overlay)(vx, vy); } else { // Erase any existing overlay, as the vault's doesn't exist. (*overlay)(x, y) = overlay_def(); } // Set keyspec index for this subvault. (*overlay)(x, y).keyspec_idx = idx; } } #ifdef USE_TILE void map_lines::overlay_tiles(tile_spec &spec) { if (!overlay.get()) overlay.reset(new overlay_matrix(width(), height())); for (int y = 0, ysize = lines.size(); y < ysize; ++y) { std::string::size_type pos = 0; while ((pos = lines[y].find_first_of(spec.key, pos)) != std::string::npos) { if (spec.floor) (*overlay)(pos, y).floortile = spec.get_tile(); else if (spec.feat) (*overlay)(pos, y).tile = spec.get_tile(); else (*overlay)(pos, y).rocktile = spec.get_tile(); (*overlay)(pos, y).no_random = spec.no_random; ++pos; } } } #endif void map_lines::nsubst(nsubst_spec &spec) { std::vector positions; for (int y = 0, ysize = lines.size(); y < ysize; ++y) { std::string::size_type pos = 0; while ((pos = lines[y].find_first_of(spec.key, pos)) != std::string::npos) positions.push_back(coord_def(pos++, y)); } std::random_shuffle(positions.begin(), positions.end(), random2); int pcount = 0; const int psize = positions.size(); for (int i = 0, vsize = spec.specs.size(); i < vsize && pcount < psize; ++i) { const int nsubsts = spec.specs[i].count; pcount += apply_nsubst(positions, pcount, nsubsts, spec.specs[i]); } } int map_lines::apply_nsubst(std::vector &pos, int start, int nsub, subst_spec &spec) { if (nsub == -1) nsub = pos.size(); const int end = std::min(start + nsub, (int) pos.size()); int substituted = 0; for (int i = start; i < end; ++i) { const int val = spec.value(); const coord_def &c = pos[i]; lines[c.y][c.x] = val; ++substituted; } return (substituted); } std::string map_lines::block_shuffle(const std::string &s) { std::vector segs = split_string("/", s); std::random_shuffle(segs.begin(), segs.end(), random2); return (comma_separated_line(segs.begin(), segs.end(), "/", "/")); } std::string map_lines::shuffle(std::string s) { std::string result; if (s.find('/') != std::string::npos) return block_shuffle(s); // Inefficient brute-force shuffle. while (!s.empty()) { const int c = random2( s.length() ); result += s[c]; s.erase(c, 1); } return (result); } void map_lines::resolve_shuffle(const std::string &shufflage) { std::string toshuffle = shufflage; std::string shuffled = shuffle(toshuffle); if (toshuffle.empty() || shuffled.empty()) return; for (int i = 0, vsize = lines.size(); i < vsize; ++i) { std::string &s = lines[i]; for (int j = 0, len = s.length(); j < len; ++j) { const char c = s[j]; std::string::size_type pos = toshuffle.find(c); if (pos != std::string::npos) s[j] = shuffled[pos]; } } } void map_lines::normalise(char fillch) { for (int i = 0, vsize = lines.size(); i < vsize; ++i) { std::string &s = lines[i]; if (static_cast(s.length()) < map_width) s += std::string( map_width - s.length(), fillch ); } } // Should never be attempted if the map has a defined orientation, or if one // of the dimensions is greater than the lesser of GXM,GYM. void map_lines::rotate(bool clockwise) { std::vector newlines; // normalise() first for convenience. normalise(); const int xs = clockwise? 0 : map_width - 1, xe = clockwise? map_width : -1, xi = clockwise? 1 : -1; const int ys = clockwise? (int) lines.size() - 1 : 0, ye = clockwise? -1 : (int) lines.size(), yi = clockwise? -1 : 1; for (int i = xs; i != xe; i += xi) { std::string line; for (int j = ys; j != ye; j += yi) line += lines[j][i]; newlines.push_back(line); } if (overlay.get()) { std::auto_ptr new_overlay( new overlay_matrix( lines.size(), map_width ) ); for (int i = xs, y = 0; i != xe; i += xi, ++y) for (int j = ys, x = 0; j != ye; j += yi, ++x) (*new_overlay)(x, y) = (*overlay)(i, j); overlay = new_overlay; } map_width = lines.size(); lines = newlines; rotate_markers(clockwise); solid_checked = false; } void map_lines::translate_marker( void (map_lines::*xform)(map_marker *, int), int par) { for (int i = 0, vsize = markers.size(); i < vsize; ++i) (this->*xform)(markers[i], par); } void map_lines::vmirror_marker(map_marker *marker, int) { marker->pos.y = height() - 1 - marker->pos.y; } void map_lines::hmirror_marker(map_marker *marker, int) { marker->pos.x = width() - 1 - marker->pos.x; } void map_lines::rotate_marker(map_marker *marker, int clockwise) { const coord_def c = marker->pos; if (clockwise) marker->pos = coord_def(width() - 1 - c.y, c.x); else marker->pos = coord_def(c.y, height() - 1 - c.x); } void map_lines::vmirror_markers() { translate_marker(&map_lines::vmirror_marker); } void map_lines::hmirror_markers() { translate_marker(&map_lines::hmirror_marker); } void map_lines::rotate_markers(bool clock) { translate_marker(&map_lines::rotate_marker, clock); } void map_lines::vmirror() { const int vsize = lines.size(); const int midpoint = vsize / 2; for (int i = 0; i < midpoint; ++i) { std::string temp = lines[i]; lines[i] = lines[vsize - 1 - i]; lines[vsize - 1 - i] = temp; } if (overlay.get()) { for (int i = 0; i < midpoint; ++i) for (int j = 0, wide = width(); j < wide; ++j) std::swap( (*overlay)(j, i), (*overlay)(j, vsize - 1 - i) ); } vmirror_markers(); solid_checked = false; } void map_lines::hmirror() { const int midpoint = map_width / 2; for (int i = 0, vsize = lines.size(); i < vsize; ++i) { std::string &s = lines[i]; for (int j = 0; j < midpoint; ++j) { int c = s[j]; s[j] = s[map_width - 1 - j]; s[map_width - 1 - j] = c; } } if (overlay.get()) { for (int i = 0, vsize = lines.size(); i < vsize; ++i) for (int j = 0; j < midpoint; ++j) std::swap( (*overlay)(j, i), (*overlay)(map_width - 1 - j, i) ); } hmirror_markers(); solid_checked = false; } keyed_mapspec *map_lines::mapspec_for_key(int key) { keyed_specs::iterator i = keyspecs.find(key); return i != keyspecs.end()? &i->second : NULL; } const keyed_mapspec *map_lines::mapspec_for_key(int key) const { keyed_specs::const_iterator i = keyspecs.find(key); return i != keyspecs.end()? &i->second : NULL; } keyed_mapspec *map_lines::mapspec_at(const coord_def &c) { int key = (*this)(c); if (key == SUBVAULT_GLYPH) { // Any subvault should create the overlay. ASSERT(overlay.get()); if (!overlay.get()) return NULL; key = (*overlay)(c.x, c.y).keyspec_idx; ASSERT(key); if (!key) return NULL; } return (mapspec_for_key(key)); } const keyed_mapspec *map_lines::mapspec_at(const coord_def &c) const { int key = (*this)(c); if (key == SUBVAULT_GLYPH) { // Any subvault should create the overlay and set the keyspec idx. ASSERT(overlay.get()); if (!overlay.get()) return NULL; key = (*overlay)(c.x, c.y).keyspec_idx; ASSERT(key); if (!key) return NULL; } return (mapspec_for_key(key)); } std::string map_lines::add_key_field( const std::string &s, std::string (keyed_mapspec::*set_field)(const std::string &s, bool fixed), void (keyed_mapspec::*copy_field)(const keyed_mapspec &spec)) { int separator = 0; std::string key, arg; std::string err = mapdef_split_key_item(s, &key, &separator, &arg, -1); if (!err.empty()) return (err); keyed_mapspec &kmbase = keyspecs[key[0]]; kmbase.key_glyph = key[0]; err = ((kmbase.*set_field)(arg, separator == ':')); if (!err.empty()) return (err); size_t len = key.length(); for (size_t i = 1; i < len; i++) { keyed_mapspec &km = keyspecs[key[i]]; km.key_glyph = key[i]; ((km.*copy_field)(kmbase)); } return (err); } std::string map_lines::add_key_item(const std::string &s) { return add_key_field(s, &keyed_mapspec::set_item, &keyed_mapspec::copy_item); } std::string map_lines::add_key_feat(const std::string &s) { return add_key_field(s, &keyed_mapspec::set_feat, &keyed_mapspec::copy_feat); } std::string map_lines::add_key_mons(const std::string &s) { return add_key_field(s, &keyed_mapspec::set_mons, &keyed_mapspec::copy_mons); } std::string map_lines::add_key_mask(const std::string &s) { return add_key_field(s, &keyed_mapspec::set_mask, &keyed_mapspec::copy_mask); } std::vector map_lines::find_glyph(int gly) const { std::vector points; for (int y = height() - 1; y >= 0; --y) { for (int x = width() - 1; x >= 0; --x) { const coord_def c(x, y); if ((*this)(c) == gly) points.push_back(c); } } return (points); } std::vector map_lines::find_glyph(const std::string &glyphs) const { std::vector points; for (int y = height() - 1; y >= 0; --y) { for (int x = width() - 1; x >= 0; --x) { const coord_def c(x, y); if (glyphs.find((*this)(c)) != std::string::npos) points.push_back(c); } } return (points); } coord_def map_lines::find_first_glyph(int gly) const { for (int y = 0, h = height(); y < h; ++y) { std::string::size_type pos = lines[y].find(gly); if (pos != std::string::npos) return coord_def(pos, y); } return coord_def(-1, -1); } coord_def map_lines::find_first_glyph(const std::string &glyphs) const { for (int y = 0, h = height(); y < h; ++y) { std::string::size_type pos = lines[y].find_first_of(glyphs); if (pos != std::string::npos) return coord_def(pos, y); } return coord_def(-1, -1); } bool map_lines::find_bounds(int gly, coord_def &tl, coord_def &br) const { tl = coord_def(width(), height()); br = coord_def(-1, -1); if (width() == 0 || height() == 0) return false; for (rectangle_iterator ri(get_iter()); ri; ++ri) { const coord_def mc = *ri; if ((*this)(mc) != gly) continue; tl.x = std::min(tl.x, mc.x); tl.y = std::min(tl.y, mc.y); br.x = std::max(br.x, mc.x); br.y = std::max(br.y, mc.y); } return (br.x >= 0); } bool map_lines::find_bounds(const char *str, coord_def &tl, coord_def &br) const { tl = coord_def(width(), height()); br = coord_def(-1, -1); if (width() == 0 || height() == 0) return false; for (rectangle_iterator ri(get_iter()); ri; ++ri) { ASSERT(ri); const coord_def &mc = *ri; const size_t len = strlen(str); for (size_t i = 0; i < len; ++i) { if ((*this)(mc) == str[i]) { tl.x = std::min(tl.x, mc.x); tl.y = std::min(tl.y, mc.y); br.x = std::max(br.x, mc.x); br.y = std::max(br.y, mc.y); break; } } } return (br.x >= 0); } bool map_lines::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) const { // This is the map_lines equivalent of _dgn_fill_zone. // It's unfortunately extremely similar, but not close enough to combine. bool ret = false; std::list points[2]; int cur = 0; for (points[cur].push_back(start); !points[cur].empty(); ) { for (std::list::const_iterator i = points[cur].begin(); i != points[cur].end(); ++i) { const coord_def &c(*i); tpd[c.x][c.y] = zone; ret |= (wanted && strchr(wanted, (*this)(c)) != NULL); for (int yi = -1; yi <= 1; ++yi) for (int xi = -1; xi <= 1; ++xi) { if (!xi && !yi) continue; const coord_def cp(c.x + xi, c.y + yi); if (cp.x < tl.x || cp.x > br.x || cp.y < tl.y || cp.y > br.y || !in_bounds(cp) || tpd[cp.x][cp.y] || passable && !strchr(passable, (*this)(cp))) { continue; } tpd[cp.x][cp.y] = zone; points[!cur].push_back(cp); } } points[cur].clear(); cur = !cur; } return (ret); } int map_lines::count_feature_in_box(const coord_def &tl, const coord_def &br, const char *feat) const { int result = 0; for (rectangle_iterator ri(tl, br); ri; ++ri) { if (strchr(feat, (*this)(*ri))) result++; } return (result); } #ifdef USE_TILE bool map_tile_list::parse(const std::string &s, int weight) { unsigned int idx = 0; if (s != "none" && !tile_dngn_index(s.c_str(), idx)) return false; push_back(map_weighted_tile((int)idx, weight)); return true; } std::string map_lines::add_tile(const std::string &sub, bool is_floor, bool is_feat) { std::string s = trimmed_string(sub); if (s.empty()) return (""); bool no_random = strip_tag(s, "no_random"); int sep = 0; std::string key; std::string substitute; std::string err = mapdef_split_key_item(s, &key, &sep, &substitute, -1); if (!err.empty()) return (err); map_tile_list list; err = parse_weighted_str(substitute, list); if (!err.empty()) return (err); tile_spec spec(key, sep == ':', no_random, is_floor, is_feat, list); overlay_tiles(spec); return (""); } std::string map_lines::add_rocktile(const std::string &sub) { return add_tile(sub, false, false); } std::string map_lines::add_floortile(const std::string &sub) { return add_tile(sub, true, false); } std::string map_lines::add_spec_tile(const std::string &sub) { return add_tile(sub, false, true); } ////////////////////////////////////////////////////////////////////////// // tile_spec int tile_spec::get_tile() { if (chose_fixed) return fixed_tile; int chosen = 0; int cweight = 0; for (int i = 0, size = tiles.size(); i < size; ++i) if (x_chance_in_y(tiles[i].second, cweight += tiles[i].second)) chosen = tiles[i].first; if (fix) { chose_fixed = true; fixed_tile = chosen; } return (chosen); } #endif /////////////////////////////////////////////// // dlua_set_map dlua_set_map::dlua_set_map(map_def *map) { dlua.callfn("dgn_set_map", "m", map); } dlua_set_map::~dlua_set_map() { dlua.callfn("dgn_set_map", 0, 0); } /////////////////////////////////////////////// // map_def // map_def::map_def() : name(), tags(), place(), depths(), orient(), chance(), weight(), weight_depth_mult(), weight_depth_div(), welcome_messages(), map(), mons(), items(), random_mons(), prelude("dlprelude"), mapchunk("dlmapchunk"), main("dlmain"), validate("dlvalidate"), veto("dlveto"), rock_colour(BLACK), floor_colour(BLACK), rock_tile(0), floor_tile(0), border_fill_type(DNGN_ROCK_WALL), index_only(false), cache_offset(0L), validating_map_flag(false) { init(); } void map_def::init() { orient = MAP_NONE; name.clear(); tags.clear(); place.clear(); depths.clear(); prelude.clear(); mapchunk.clear(); main.clear(); validate.clear(); veto.clear(); place_loaded_from.clear(); reinit(); // Subvault mask set and cleared externally. // It should *not* be in reinit. svmask = NULL; } void map_def::reinit() { items.clear(); random_mons.clear(); level_flags.clear(); branch_flags.clear(); welcome_messages.clear(); rock_colour = floor_colour = BLACK; rock_tile = floor_tile = 0; border_fill_type = DNGN_ROCK_WALL; // Chance of using this level. Nonzero chance should be used // sparingly. When selecting vaults for a place, first those // vaults with chance > 0 are considered, in the order they were // loaded (which is arbitrary). If random2(100) < chance, the // vault is picked, and all other vaults are ignored for that // random selection. weight is ignored if the vault is chosen // based on its chance. chance = 0; // If multiple alternative vaults have a chance, the order in which // they're tested is based on chance_priority: higher priority vaults // are checked first. Vaults with the same priority are tested in // unspecified order. chance_priority = 0; // Weight for this map. When selecting a map, if no map with a // nonzero chance is picked, one of the other eligible vaults is // picked with a probability of weight / (sum of weights of all // eligible vaults). weight = 10; // How to modify weight based on absolte dungeon depth. This // needs to be done in the C++ code since the map's lua code doesnt' // get called again each time the depth changes. weight_depth_mult = 0; weight_depth_div = 1; // Clearing the map also zaps map transforms. map.clear(); mons.clear(); } bool map_def::valid_item_array_glyph(int gly) { return (gly >= 'd' && gly <= 'k'); } int map_def::item_array_glyph_to_slot(int gly) { ASSERT(map_def::valid_item_array_glyph(gly)); return (gly - 'd'); } bool map_def::valid_monster_glyph(int gly) { return (gly >= '0' && gly <= '9'); } bool map_def::valid_monster_array_glyph(int gly) { return (gly >= '1' && gly <= '7'); } int map_def::monster_array_glyph_to_slot(int gly) { ASSERT(map_def::valid_monster_array_glyph(gly)); return (gly - '1'); } bool map_def::in_map(const coord_def &c) const { return map.in_map(c); } int map_def::glyph_at(const coord_def &c) const { return map(c); } void map_def::write_full(writer& outf) { cache_offset = outf.tell(); marshallShort(outf, MAP_CACHE_VERSION); // Level indicator. marshallString4(outf, name); prelude.write(outf); mapchunk.write(outf); main.write(outf); validate.write(outf); veto.write(outf); } void map_def::read_full(reader& inf) { // There's a potential race-condition here: // - If someone modifies a .des file while there are games in progress, // - a new Crawl process will overwrite the .dsc. // - older Crawl processes trying to reading the new .dsc will be hosed. // We could try to recover from the condition (by locking and // reloading the index), but it's easier to save the game at this // point and let the player reload. const short fp_version = unmarshallShort(inf); std::string fp_name; unmarshallString4(inf, fp_name); if (fp_version != MAP_CACHE_VERSION || fp_name != name) { save_game(true, make_stringf("Level file cache for %s is out-of-sync! " "Please reload your game.", file.c_str()).c_str()); } prelude.read(inf); mapchunk.read(inf); main.read(inf); validate.read(inf); veto.read(inf); } void map_def::load() { if (!index_only) return; const std::string descache_base = get_descache_path(file, ""); file_lock deslock(descache_base + ".lk", "rb", false); const std::string loadfile = descache_base + ".dsc"; FILE *fp = fopen(loadfile.c_str(), "rb"); fseek(fp, cache_offset, SEEK_SET); reader inf(fp); read_full(inf); fclose(fp); index_only = false; } std::vector map_def::find_glyph(int glyph) const { return map.find_glyph(glyph); } coord_def map_def::find_first_glyph(int glyph) const { return map.find_first_glyph(glyph); } coord_def map_def::find_first_glyph(const std::string &s) const { return map.find_first_glyph(s); } void map_def::write_index(writer& outf) const { if (!cache_offset) end(1, false, "Map %s: can't write index - cache offset not set!", name.c_str()); marshallString4(outf, name); marshallString4(outf, place_loaded_from.filename); marshallLong(outf, place_loaded_from.lineno); marshallShort(outf, orient); // XXX: This is a hack. See the comment in l_dgn.cc. marshallShort(outf, static_cast(border_fill_type)); marshallLong(outf, chance_priority); marshallLong(outf, chance); marshallLong(outf, weight); marshallLong(outf, cache_offset); marshallString4(outf, tags); place.save(outf); write_depth_ranges(outf); prelude.write(outf); } void map_def::read_index(reader& inf) { unmarshallString4(inf, name); unmarshallString4(inf, place_loaded_from.filename); place_loaded_from.lineno = unmarshallLong(inf); orient = static_cast( unmarshallShort(inf) ); // XXX: Hack. See the comment in l_dgn.cc. border_fill_type = static_cast( unmarshallShort(inf) ); chance_priority = unmarshallLong(inf); chance = unmarshallLong(inf); weight = unmarshallLong(inf); cache_offset = unmarshallLong(inf); unmarshallString4(inf, tags); place.load(inf); read_depth_ranges(inf); prelude.read(inf); index_only = true; } void map_def::write_depth_ranges(writer& outf) const { marshallShort(outf, depths.size()); for (int i = 0, sz = depths.size(); i < sz; ++i) depths[i].write(outf); } void map_def::read_depth_ranges(reader& inf) { depths.clear(); const int nranges = unmarshallShort(inf); for (int i = 0; i < nranges; ++i) { level_range lr; lr.read(inf); depths.push_back(lr); } } void map_def::set_file(const std::string &s) { prelude.set_file(s); mapchunk.set_file(s); main.set_file(s); validate.set_file(s); veto.set_file(s); file = get_base_filename(s); } std::string map_def::run_lua(bool run_main) { dlua_set_map mset(this); int err = prelude.load(dlua); if (err == -1000) lua_pushnil(dlua); else if (err) return (prelude.orig_error()); if (!run_main) { lua_pushnil(dlua); lua_pushnil(dlua); } else { err = mapchunk.load(dlua); if (err == -1000) lua_pushnil(dlua); else if (err) return (mapchunk.orig_error()); err = main.load(dlua); if (err == -1000) lua_pushnil(dlua); else if (err) return (main.orig_error()); } if (!dlua.callfn("dgn_run_map", 3, 0)) return rewrite_chunk_errors(dlua.error); return (dlua.error); } bool map_def::test_lua_boolchunk(dlua_chunk &chunk, bool defval, bool croak) { bool result = defval; dlua_set_map mset(this); int err = chunk.load(dlua); if (err == -1000) return (result); else if (err) { if (croak) end(1, false, "Lua error: %s", validate.orig_error().c_str()); else mprf(MSGCH_ERROR, "Lua error: %s", validate.orig_error().c_str()); return (result); } if (dlua.callfn("dgn_run_map", 1, 1)) dlua.fnreturns(">b", &result); else { if (croak) end(1, false, "Lua error: %s", rewrite_chunk_errors(dlua.error).c_str()); else mprf(MSGCH_ERROR, "Lua error: %s", rewrite_chunk_errors(dlua.error).c_str()); } return (result); } bool map_def::test_lua_validate(bool croak) { return validate.empty() || test_lua_boolchunk(validate, false, croak); } bool map_def::test_lua_veto() { return !veto.empty() && test_lua_boolchunk(veto, true); } std::string map_def::rewrite_chunk_errors(const std::string &s) const { std::string res = s; if (prelude.rewrite_chunk_errors(res)) return (res); if (mapchunk.rewrite_chunk_errors(res)) return (res); if (main.rewrite_chunk_errors(res)) return (res); if (validate.rewrite_chunk_errors(res)) return (res); veto.rewrite_chunk_errors(res); return (res); } std::string map_def::validate_temple_map() { std::vector altars = find_glyph('B'); if (has_tag_prefix("temple_overflow_")) { std::vector tag_list = get_tags(); std::string temple_tag; for (unsigned int i = 0; i < tag_list.size(); i++) { if (starts_with(tag_list[i], "temple_overflow_")) { temple_tag = tag_list[i]; break; } } if (temple_tag.empty()) return make_stringf("Unkown temple tag."); temple_tag = strip_tag_prefix(temple_tag, "temple_overflow_"); if (temple_tag.empty()) return ("Malformed temple_overflow_ tag"); int num = atoi(temple_tag.c_str()); if (num == 0) { temple_tag = replace_all(temple_tag, "_", " "); god_type god = string_to_god(temple_tag.c_str(), true); if (god == GOD_NO_GOD) { return make_stringf("Invalid god name '%s'", temple_tag.c_str()); } // Assume that specialized single-god temples are set up // properly. return(""); } else { if ( ( (unsigned long) num ) != altars.size() ) { return make_stringf("Temple should contain %lu altars, but " "has %d.", altars.size(), num); } } } if (altars.empty()) return ("Temple vault must contain at least one altar."); // TODO: check for substitutions and shuffles std::vector b_glyphs = map.find_glyph('B'); for (std::vector::iterator i = b_glyphs.begin(); i != b_glyphs.end(); ++i) { const keyed_mapspec *spec = map.mapspec_at(*i); if (spec != NULL && spec->feat.feats.size() > 0) return ("Can't change feat 'B' in temple (KFEAT)"); } std::vector god_list = temple_god_list(); if (altars.size() > god_list.size()) return ("Temple vault has too many altars"); return (""); } std::string map_def::validate_map_def() { unwind_bool valid_flag(validating_map_flag, true); std::string err = run_lua(true); if (!err.empty()) return (err); fixup(); resolve(); test_lua_validate(true); if ((place.branch == BRANCH_ECUMENICAL_TEMPLE && place.level_type == LEVEL_DUNGEON) || has_tag_prefix("temple_overflow_")) { err = validate_temple_map(); if (!err.empty()) return err; } if (orient == MAP_FLOAT || is_minivault()) { if (map.width() > GXM - MAPGEN_BORDER * 2 || map.height() > GYM - MAPGEN_BORDER * 2) { return make_stringf( "%s is too big: %dx%d - max %dx%d", is_minivault()? "Minivault" : "Float", map.width(), map.height(), GXM - MAPGEN_BORDER * 2, GYM - MAPGEN_BORDER * 2); } } else { if (map.width() > GXM || map.height() > GYM) { return make_stringf( "Map is too big: %dx%d - max %dx%d", map.width(), map.height(), GXM, GYM); } } switch (orient) { case MAP_NORTH: case MAP_SOUTH: if (map.height() > GYM * 2 / 3) return make_stringf("Map too large - height %d (max %d)", map.height(), GYM * 2 / 3); break; case MAP_EAST: case MAP_WEST: if (map.width() > GXM * 2 / 3) return make_stringf("Map too large - width %d (max %d)", map.width(), GXM * 2 / 3); break; case MAP_NORTHEAST: case MAP_SOUTHEAST: case MAP_NORTHWEST: case MAP_SOUTHWEST: case MAP_FLOAT: if (map.width() > GXM * 2 / 3 || map.height() > GYM * 2 / 3) return make_stringf("Map too large - %dx%d (max %dx%d)", map.width(), map.height(), GXM * 2 / 3, GYM * 2 / 3); break; default: break; } dlua_set_map dl(this); return (""); } bool map_def::is_usable_in(const level_id &lid) const { bool any_matched = false; for (int i = 0, sz = depths.size(); i < sz; ++i) { const level_range &lr = depths[i]; if (lr.matches(lid)) { if (lr.deny) return (false); any_matched = true; } } return (any_matched); } void map_def::add_depth(const level_range &range) { depths.push_back(range); } void map_def::add_depths(depth_ranges::const_iterator s, depth_ranges::const_iterator e) { depths.insert(depths.end(), s, e); } bool map_def::has_depth() const { return (!depths.empty()); } bool map_def::is_minivault() const { return (has_tag("minivault")); } // Tries to dock a floating vault - push it to one edge of the level. // Docking will only succeed if two contiguous edges are all x/c/b/v // (other walls prevent docking). If the vault's width is > GXM*2/3, // it's also eligible for north/south docking, and if the height > // GYM*2/3, it's eligible for east/west docking. Although docking is // similar to setting the orientation, it doesn't affect 'orient'. coord_def map_def::float_dock() { const map_section_type orients[] = { MAP_NORTH, MAP_SOUTH, MAP_EAST, MAP_WEST, MAP_NORTHEAST, MAP_SOUTHEAST, MAP_NORTHWEST, MAP_SOUTHWEST }; map_section_type which_orient = MAP_NONE; int norients = 0; for (unsigned i = 0; i < sizeof(orients) / sizeof(*orients); ++i) { if (map.solid_borders(orients[i]) && can_dock(orients[i]) && one_chance_in(++norients)) { which_orient = orients[i]; } } if (which_orient == MAP_NONE || which_orient == MAP_FLOAT) return coord_def(-1, -1); #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Docking floating vault to %s", map_section_name(which_orient)); #endif return dock_pos(which_orient); } coord_def map_def::dock_pos(map_section_type norient) const { const int minborder = 6; switch (norient) { case MAP_NORTH: return coord_def( (GXM - map.width()) / 2, minborder ); case MAP_SOUTH: return coord_def( (GXM - map.width()) / 2, GYM - minborder - map.height() ); case MAP_EAST: return coord_def( GXM - minborder - map.width(), (GYM - map.height()) / 2 ); case MAP_WEST: return coord_def( minborder, (GYM - map.height()) / 2 ); case MAP_NORTHEAST: return coord_def( GXM - minborder - map.width(), minborder ); case MAP_NORTHWEST: return coord_def( minborder, minborder ); case MAP_SOUTHEAST: return coord_def( GXM - minborder - map.width(), GYM - minborder - map.height() ); case MAP_SOUTHWEST: return coord_def( minborder, GYM - minborder - map.height() ); default: return coord_def(-1, -1); } } bool map_def::can_dock(map_section_type norient) const { switch (norient) { case MAP_NORTH: case MAP_SOUTH: return map.width() > GXM * 2 / 3; case MAP_EAST: case MAP_WEST: return map.height() > GYM * 2 / 3; default: return (true); } } coord_def map_def::float_random_place() const { // Try to leave enough around the float for roomification. int minhborder = MAPGEN_BORDER + 11, minvborder = minhborder; if (GXM - 2 * minhborder < map.width()) minhborder = (GXM - map.width()) / 2 - 1; if (GYM - 2 * minvborder < map.height()) minvborder = (GYM - map.height()) / 2 - 1; return coord_def( random_range(minhborder, GXM - minhborder - map.width()), random_range(minvborder, GYM - minvborder - map.height())); } point_vector map_def::anchor_points() const { point_vector points; for (int y = 0, cheight = map.height(); y < cheight; ++y) for (int x = 0, cwidth = map.width(); x < cwidth; ++x) if (map.glyph(x, y) == '@') points.push_back(coord_def(x, y)); return (points); } coord_def map_def::float_aligned_place() const { const point_vector our_anchors = anchor_points(); const coord_def fail(-1, -1); #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Aligning floating vault with %lu points vs %lu reference points", our_anchors.size(), map_anchor_points.size()); #endif // Mismatch in the number of points we have to align, bail. if (our_anchors.size() != map_anchor_points.size()) return (fail); // Align first point of both vectors, then check that the others match. const coord_def pos = map_anchor_points[0] - our_anchors[0]; for (int i = 1, psize = map_anchor_points.size(); i < psize; ++i) if (pos + our_anchors[i] != map_anchor_points[i]) return (fail); // Looking good, check bounds. if (!map_bounds(pos) || !map_bounds(pos + size() - 1)) return (fail); // Go us! return (pos); } coord_def map_def::float_place() { ASSERT(orient == MAP_FLOAT); coord_def pos(-1, -1); if (!map_anchor_points.empty()) pos = float_aligned_place(); else { if (coinflip()) pos = float_dock(); if (pos.x == -1) pos = float_random_place(); } return (pos); } void map_def::hmirror() { if (has_tag("no_hmirror")) return; #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Mirroring %s horizontally.", name.c_str()); #endif map.hmirror(); switch (orient) { case MAP_EAST: orient = MAP_WEST; break; case MAP_NORTHEAST: orient = MAP_NORTHWEST; break; case MAP_SOUTHEAST: orient = MAP_SOUTHWEST; break; case MAP_WEST: orient = MAP_EAST; break; case MAP_NORTHWEST: orient = MAP_NORTHEAST; break; case MAP_SOUTHWEST: orient = MAP_SOUTHEAST; break; default: break; } } void map_def::vmirror() { if (has_tag("no_vmirror")) return; #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Mirroring %s vertically.", name.c_str()); #endif map.vmirror(); switch (orient) { case MAP_NORTH: orient = MAP_SOUTH; break; case MAP_NORTHEAST: orient = MAP_SOUTHEAST; break; case MAP_NORTHWEST: orient = MAP_SOUTHWEST; break; case MAP_SOUTH: orient = MAP_NORTH; break; case MAP_SOUTHEAST: orient = MAP_NORTHEAST; break; case MAP_SOUTHWEST: orient = MAP_NORTHWEST; break; default: break; } } void map_def::rotate(bool clock) { if (has_tag("no_rotate")) return; #define GMINM ((GXM) < (GYM)? (GXM) : (GYM)) // Make sure the largest dimension fits in the smaller map bound. if (map.width() <= GMINM && map.height() <= GMINM) { #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Rotating %s %sclockwise.", name.c_str(), !clock? "anti-" : ""); #endif map.rotate(clock); // Orientation shifts for clockwise rotation: const map_section_type clockrotate_orients[][2] = { { MAP_NORTH, MAP_EAST }, { MAP_NORTHEAST, MAP_SOUTHEAST }, { MAP_EAST, MAP_SOUTH }, { MAP_SOUTHEAST, MAP_SOUTHWEST }, { MAP_SOUTH, MAP_WEST }, { MAP_SOUTHWEST, MAP_NORTHWEST }, { MAP_WEST, MAP_NORTH }, { MAP_NORTHWEST, MAP_NORTHEAST }, }; const int nrots = sizeof(clockrotate_orients) / sizeof(*clockrotate_orients); const int refindex = !clock; for (int i = 0; i < nrots; ++i) if (orient == clockrotate_orients[i][refindex]) { orient = clockrotate_orients[i][!refindex]; break; } } } void map_def::normalise() { // Pad out lines that are shorter than max. map.normalise(' '); } std::string map_def::resolve() { dlua_set_map dl(this); return (""); } void map_def::fixup() { normalise(); // Fixup minivaults into floating vaults tagged "minivault". if (orient == MAP_NONE) { orient = MAP_FLOAT; tags += " minivault "; } } bool map_def::has_tag(const std::string &tagwanted) const { return !tags.empty() && !tagwanted.empty() && tags.find(" " + tagwanted + " ") != std::string::npos; } bool map_def::has_tag_prefix(const std::string &prefix) const { return !tags.empty() && !prefix.empty() && tags.find(" " + prefix) != std::string::npos; } bool map_def::has_tag_suffix(const std::string &suffix) const { return !tags.empty() && !suffix.empty() && tags.find(suffix + " ") != std::string::npos; } std::vector map_def::get_tags() const { return split_string(" ", tags); } keyed_mapspec *map_def::mapspec_at(const coord_def &c) { return (map.mapspec_at(c)); } const keyed_mapspec *map_def::mapspec_at(const coord_def &c) const { return (map.mapspec_at(c)); } std::string map_def::subvault_from_tagstring(const std::string &sub) { std::string s = trimmed_string(sub); if (s.empty()) return (""); int sep = 0; std::string key; std::string substitute; std::string err = mapdef_split_key_item(sub, &key, &sep, &substitute, -1); if (!err.empty()) return (err); // Randomly picking a different vault per-glyph is not supported. if (sep != ':') return ("SUBVAULT does not support '='. Use ':' instead."); map_string_list vlist; err = parse_weighted_str(substitute, vlist); if (!err.empty()) return (err); bool fix = false; string_spec spec(key, fix, vlist); // Although it's unfortunate to not be able to validate subvaults except a // run-time, this allows subvaults to reference maps by tag that may not // have been loaded yet. if (!is_validating()) err = apply_subvault(spec); if (!err.empty()) return (err); return (""); } std::string map_def::apply_subvault(string_spec &spec) { // Find bounding box for key glyphs coord_def tl, br; if (!map.find_bounds(spec.key.c_str(), tl, br)) { // No glyphs, so do nothing. return (""); } int vwidth = br.x - tl.x + 1; int vheight = br.y - tl.y + 1; Matrix flags(vwidth, vheight); map.fill_mask_matrix(spec.key, tl, br, flags); // Backup pre-subvault unique tags and names. const std::set uniq_tags = you.uniq_map_tags; const std::set uniq_names = you.uniq_map_names; const int max_tries = 100; int ntries = 0; std::string tag = spec.get_property(); while (++ntries <= max_tries) { // Each iteration, restore tags and names. This is because this vault // may successfully load a subvault (registering its tag and name), but // then itself fail. you.uniq_map_tags = uniq_tags; you.uniq_map_names = uniq_names; const map_def *orig = random_map_for_tag(tag); if (!orig) return (make_stringf("No vault found for tag '%s'", tag.c_str())); map_def vault = *orig; vault.load(); // Temporarily set the subvault mask so this subvault can know // that it is being generated as a subvault. vault.svmask = &flags; if (!resolve_subvault(vault)) continue; ASSERT(vault.map.width() <= vwidth); ASSERT(vault.map.height() <= vheight); map.merge_subvault(tl, br, flags, vault); dgn_register_vault(vault); return (""); } // Failure, restore original unique tags and names. you.uniq_map_tags = uniq_tags; you.uniq_map_names = uniq_names; return (make_stringf("Could not fit '%s' in (%d,%d) to (%d, %d).", tag.c_str(), tl.x, tl.y, br.x, br.y)); } bool map_def::is_subvault() const { return (svmask != NULL); } void map_def::apply_subvault_mask() { if (!svmask) return; map.clear(); map.extend(subvault_width(), subvault_height(), ' '); for (rectangle_iterator ri(map.get_iter()); ri; ++ri) { const coord_def mc = *ri; if (subvault_cell_valid(mc)) map(mc) = '.'; else map(mc) = ' '; } } bool map_def::subvault_cell_valid(const coord_def &c) const { if (!svmask) return false; if (c.x < 0 || c.x >= subvault_width() || c.y < 0 || c.y >= subvault_height()) { return false; } return (*svmask)(c.x, c.y); } int map_def::subvault_width() const { if (!svmask) return 0; return (svmask->width()); } int map_def::subvault_height() const { if (!svmask) return 0; return (svmask->height()); } int map_def::subvault_mismatch_count(const coord_def &offset) const { int count = 0; if (!is_subvault()) return (count); for (rectangle_iterator ri(map.get_iter()); ri; ++ri) { // Coordinate in the subvault const coord_def sc = *ri; // Coordinate in the mask const coord_def mc = sc + offset; bool valid_subvault_cell = (map(sc) != ' '); bool valid_mask = (*svmask)(mc.x, mc.y); if (valid_subvault_cell && !valid_mask) count++; } return (count); } /////////////////////////////////////////////////////////////////// // mons_list // mons_list::mons_list() : mons() { } int mons_list::fix_demon(int demon) const { if (demon >= -1) return (demon); demon = -100 - demon; return (summon_any_demon(static_cast(demon))); } mons_spec mons_list::pick_monster(mons_spec_slot &slot) { int totweight = 0; mons_spec pick; for (mons_spec_list::iterator i = slot.mlist.begin(); i != slot.mlist.end(); ++i) { const int weight = i->genweight; if (x_chance_in_y(weight, totweight += weight)) { pick = *i; if (pick.mid < 0 && pick.fix_mons) pick.mid = i->mid = fix_demon(pick.mid); } } if (pick.mid < 0) pick = fix_demon(pick.mid); if (slot.fix_slot) { slot.mlist.clear(); slot.mlist.push_back(pick); slot.fix_slot = false; } if (pick.mid == MONS_WEAPON_MIMIC && !pick.fix_mons) pick.mid = random_range(MONS_GOLD_MIMIC, MONS_POTION_MIMIC); return (pick); } mons_spec mons_list::get_monster(int index) { if (index < 0 || index >= (int)mons.size()) return (mons_spec(RANDOM_MONSTER)); return (pick_monster(mons[index])); } mons_spec mons_list::get_monster(int slot_index, int list_index) const { if (slot_index < 0 || slot_index >= (int)mons.size()) return (mons_spec(RANDOM_MONSTER)); const mons_spec_list &list = mons[slot_index].mlist; if (list_index < 0 || list_index >= (int)list.size()) return (mons_spec(RANDOM_MONSTER)); return (list[list_index]); } void mons_list::clear() { mons.clear(); } void mons_list::set_from_slot(const mons_list &list, int slot_index) { clear(); // Don't set anything if an invalid index. // Future calls to get_monster will just return a random monster. if (slot_index < 0 || (size_t)slot_index >= list.mons.size()) return; mons.push_back(list.mons[slot_index]); } bool mons_list::check_mimic(const std::string &s, int *mid, bool *fix) const { if (s == "mimic") { *mid = MONS_WEAPON_MIMIC; *fix = false; return (true); } else if (s == "gold mimic") *mid = MONS_GOLD_MIMIC; else if (s == "weapon mimic") *mid = MONS_WEAPON_MIMIC; else if (s == "armour mimic") *mid = MONS_ARMOUR_MIMIC; else if (s == "scroll mimic") *mid = MONS_SCROLL_MIMIC; else if (s == "potion mimic") *mid = MONS_POTION_MIMIC; else return (false); *fix = true; return (true); } void mons_list::parse_mons_spells(mons_spec &spec, const std::string &spells) { spec.explicit_spells = true; spec.extra_monster_flags |= MF_SPELLCASTER; const std::vector spell_names(split_string(";", spells)); if (spell_names.size() > NUM_MONSTER_SPELL_SLOTS) { error = make_stringf("Too many monster spells (max %d) in %s", NUM_MONSTER_SPELL_SLOTS, spells.c_str()); return; } for (unsigned i = 0, ssize = spell_names.size(); i < ssize; ++i) { const std::string spname( lowercase_string(replace_all_of(spell_names[i], "_", " "))); if (spname.empty() || spname == "." || spname == "none" || spname == "no spell") { spec.spells[i] = SPELL_NO_SPELL; } else { const spell_type sp(spell_by_name(spname)); if (sp == SPELL_NO_SPELL) { error = make_stringf("Unknown spell name: '%s' in '%s'", spname.c_str(), spells.c_str()); return; } if (!is_valid_mon_spell(sp)) { error = make_stringf("Not a monster spell: '%s'", spname.c_str()); return; } spec.spells[i] = sp; } } } mons_list::mons_spec_slot mons_list::parse_mons_spec(std::string spec) { mons_spec_slot slot; slot.fix_slot = strip_tag(spec, "fix_slot"); std::vector specs = split_string("/", spec); for (int i = 0, ssize = specs.size(); i < ssize; ++i) { mons_spec mspec; std::string s = specs[i]; std::string spells(strip_tag_prefix(s, "spells:")); if (!spells.empty()) { parse_mons_spells(mspec, spells); if (!error.empty()) return (slot); } std::vector parts = split_string(";", s); std::string mon_str = parts[0]; if (parts.size() > 2) { error = make_stringf("Too many semi-colons for '%s' spec.", mon_str.c_str()); return (slot); } else if (parts.size() == 2) { // TODO: Allow for a "fix_slot" type tag which will cause // all monsters generated from this spec to have the // exact same equipment. std::string items_str = parts[1]; items_str = replace_all(items_str, "|", "/"); std::vector segs = split_string(".", items_str); if (segs.size() > NUM_MONSTER_SLOTS) { error = make_stringf("More items than monster item slots " "for '%s'.", mon_str.c_str()); return (slot); } for (int j = 0, isize = segs.size(); j < isize; ++j) { error = mspec.items.add_item(segs[j], false); if (!error.empty()) return (slot); } } mspec.genweight = find_weight(mon_str); if (mspec.genweight == TAG_UNFOUND || mspec.genweight <= 0) mspec.genweight = 10; if (strip_tag(mon_str, "priest_spells")) { mspec.extra_monster_flags &= ~MF_ACTUAL_SPELLS; mspec.extra_monster_flags |= MF_PRIEST; } if (strip_tag(mon_str, "actual_spells")) { mspec.extra_monster_flags &= ~MF_PRIEST; mspec.extra_monster_flags |= MF_ACTUAL_SPELLS; } mspec.fix_mons = strip_tag(mon_str, "fix_mons"); mspec.generate_awake = strip_tag(mon_str, "generate_awake"); mspec.patrolling = strip_tag(mon_str, "patrolling"); mspec.band = strip_tag(mon_str, "band"); const std::string att = strip_tag_prefix(mon_str, "att:"); if (att.empty() || att == "hostile") mspec.attitude = ATT_HOSTILE; else if (att == "friendly") mspec.attitude = ATT_FRIENDLY; else if (att == "good_neutral") mspec.attitude = ATT_GOOD_NEUTRAL; else if (att == "fellow_slime" || att == "strict_neutral") mspec.attitude = ATT_STRICT_NEUTRAL; else if (att == "neutral") mspec.attitude = ATT_NEUTRAL; // Useful for summoned monsters. if (strip_tag(mon_str, "seen")) mspec.extra_monster_flags |= MF_SEEN; if (!mon_str.empty() && isdigit(mon_str[0])) { // Look for space after initial digits. std::string::size_type pos = mon_str.find_first_not_of("0123456789"); if (pos != std::string::npos && mon_str[pos] == ' ') { const std::string mcount = mon_str.substr(0, pos); const int count = atoi(mcount.c_str()); if (count >= 1 && count <= 99) mspec.quantity = count; mon_str = mon_str.substr(pos); } } // place:Elf:7 to choose monsters appropriate for that level, // for example. const std::string place = strip_tag_prefix(mon_str, "place:"); if (!place.empty()) { try { mspec.place = level_id::parse_level_id(place); } catch (const std::string &err) { error = err; return (slot); } } mspec.mlevel = strip_number_tag(mon_str, "lev:"); if (mspec.mlevel == TAG_UNFOUND) mspec.mlevel = 0; mspec.hd = strip_number_tag(mon_str, "hd:"); if (mspec.hd == TAG_UNFOUND) mspec.hd = 0; mspec.hp = strip_number_tag(mon_str, "hp:"); if (mspec.hp == TAG_UNFOUND) mspec.hp = 0; int dur = strip_number_tag(mon_str, "dur:"); if (dur == TAG_UNFOUND) dur = 0; else if (dur < 1 || dur > 6) dur = 0; mspec.abjuration_duration = dur; int summon_type = 0; std::string s_type = strip_tag_prefix(mon_str, "sum:"); if (!s_type.empty()) { // In case of spells! s_type = replace_all_of(s_type, "_", " "); summon_type = static_cast(str_to_summon_type(s_type)); if (summon_type == SPELL_NO_SPELL) { error = make_stringf("bad monster summon type: \"%s\"", s_type.c_str()); return (slot); } if (mspec.abjuration_duration == 0) { error = "marked summon with no duration"; return (slot); } } mspec.summon_type = summon_type; std::string non_actor_summoner = strip_tag_prefix(mon_str, "nas:"); if (!non_actor_summoner.empty()) { non_actor_summoner = replace_all_of(non_actor_summoner, "_", " "); mspec.non_actor_summoner = non_actor_summoner; if (mspec.abjuration_duration == 0) { error = "marked summon with no duration"; return (slot); } } std::string colour = strip_tag_prefix(mon_str, "col:"); if (!colour.empty()) { if (colour == "any") // XXX: Hack mspec.colour = -1; else { mspec.colour = str_to_colour(colour, BLACK); if (mspec.colour == BLACK) { error = make_stringf("bad monster colour \"%s\" in \"%s\"", colour.c_str(), specs[i].c_str()); return (slot); } } } std::string tile = strip_tag_prefix(mon_str, "tile:"); #ifdef USE_TILE if (!tile.empty()) { unsigned int index; if (!tile_player_index(tile.c_str(), index)) { error = make_stringf("bad tile name: \"%s\".", tile.c_str()); return (slot); } mspec.props["monster_tile"] = short(index); } #endif std::string name = strip_tag_prefix(mon_str, "name:"); if (!name.empty()) { name = replace_all_of(name, "_", " "); mspec.monname = name; if (strip_tag(mon_str, "name_suffix") || strip_tag(mon_str, "n_suf")) mspec.extra_monster_flags |= MF_NAME_SUFFIX; else if (strip_tag(mon_str, "name_adjective") || strip_tag(mon_str, "n_adj")) mspec.extra_monster_flags |= MF_NAME_ADJECTIVE; else if (strip_tag(mon_str, "name_replace") || strip_tag(mon_str, "n_rpl")) mspec.extra_monster_flags |= MF_NAME_REPLACE; // We should be able to combine this with name_replace. if (strip_tag(mon_str, "name_descriptor") || strip_tag(mon_str, "n_des")) mspec.extra_monster_flags |= MF_NAME_DESCRIPTOR; // Reasoning for this setting both flags: it does nothing with the // description unless NAME_DESCRIPTOR is also set; thus, you end up // with bloated vault description lines akin to: "name:blah_blah // name_replace name_descrpitor name_definite". if (strip_tag(mon_str, "name_definite") || strip_tag(mon_str, "n_the")) { mspec.extra_monster_flags |= MF_NAME_DEFINITE; mspec.extra_monster_flags |= MF_NAME_DESCRIPTOR; } } trim_string(mon_str); if (mon_str == "8") mspec.mlevel = -8; else if (mon_str == "9") mspec.mlevel = -9; else if (check_mimic(mon_str, &mspec.mid, &mspec.fix_mons)) ; else if (mspec.place.is_valid()) { // For monster specs such as place:Orc:4 zombie, we may // have a monster modifier, in which case we set the // modifier in monbase. const mons_spec nspec = mons_by_name("orc " + mon_str); if (nspec.mid != MONS_PROGRAM_BUG) { // Is this a modified monster? if (nspec.monbase != MONS_PROGRAM_BUG && mons_class_is_zombified(nspec.mid)) { mspec.monbase = static_cast(nspec.mid); } } } else if (mon_str != "0") { const mons_spec nspec = mons_by_name(mon_str); if (nspec.mid == MONS_PROGRAM_BUG) { error = make_stringf("unknown monster: \"%s\"", mon_str.c_str()); return (slot); } mspec.mid = nspec.mid; mspec.monbase = nspec.monbase; mspec.number = nspec.number; } if (mspec.items.size() > 0) { monster_type mid = (monster_type)mspec.mid; if (mid == RANDOM_DRACONIAN || mid == RANDOM_BASE_DRACONIAN || mid == RANDOM_NONBASE_DRACONIAN) { mid = MONS_DRACONIAN; } if (mid >= NUM_MONSTERS) { error = "Can't give spec items to a random monster."; return (slot); } else if (mons_class_itemuse(mid) < MONUSE_STARTING_EQUIPMENT) { if (mid != MONS_DANCING_WEAPON || mspec.items.size() > 1) error = make_stringf("Monster '%s' can't use items.", mon_str.c_str()); } } slot.mlist.push_back(mspec); } return (slot); } std::string mons_list::add_mons(const std::string &s, bool fix) { error.clear(); mons_spec_slot slotmons = parse_mons_spec(s); if (!error.empty()) return (error); if (fix) { slotmons.fix_slot = true; pick_monster(slotmons); } mons.push_back( slotmons ); return (error); } std::string mons_list::set_mons(int index, const std::string &s) { error.clear(); if (index < 0) return (error = make_stringf("Index out of range: %d", index)); mons_spec_slot slotmons = parse_mons_spec(s); if (!error.empty()) return (error); if (index >= (int) mons.size()) { mons.reserve(index + 1); mons.resize(index + 1, mons_spec_slot()); } mons[index] = slotmons; return (error); } void mons_list::get_zombie_type(std::string s, mons_spec &spec) const { static const char *zombie_types[] = { " zombie", " skeleton", " simulacrum", " spectre", NULL }; // This order must match zombie_types, indexed from one. static const monster_type zombie_montypes[][2] = { // small // large { MONS_PROGRAM_BUG, MONS_PROGRAM_BUG }, { MONS_ZOMBIE_SMALL, MONS_ZOMBIE_LARGE }, { MONS_SKELETON_SMALL, MONS_SKELETON_LARGE }, { MONS_SIMULACRUM_SMALL, MONS_SIMULACRUM_LARGE }, { MONS_SPECTRAL_THING, MONS_SPECTRAL_THING }, }; int mod = ends_with(s, zombie_types); if (!mod) { const std::string spectre("spectral "); if (s.find(spectre) == 0) { mod = ends_with(" spectre", zombie_types); s = s.substr(spectre.length()); } else { spec.mid = MONS_PROGRAM_BUG; return; } } else { s = s.substr(0, s.length() - strlen(zombie_types[mod - 1])); } trim_string(s); mons_spec base_monster = mons_by_name(s); if (base_monster.mid < 0) base_monster.mid = MONS_PROGRAM_BUG; spec.monbase = static_cast(base_monster.mid); spec.number = base_monster.number; const int zombie_size = mons_zombie_size(spec.monbase); if (!zombie_size) { spec.mid = MONS_PROGRAM_BUG; return; } spec.mid = zombie_montypes[mod][zombie_size - 1]; } mons_spec mons_list::get_hydra_spec(const std::string &name) const { int nheads = -1; std::string prefix = name.substr(0, name.find("-")); nheads = atoi(prefix.c_str()); if (nheads != 0) ; else if (prefix == "0") nheads = 0; else { // Might be "two-headed hydra" type string. for (int i = 0; i <= 20; ++i) if (number_in_words(i) == prefix) { nheads = i; break; } } if (nheads < 1) nheads = MONS_PROGRAM_BUG; // What can I say? :P else if (nheads > 20) { #if DEBUG || DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Hydra spec wants %d heads, clamping to 20.", nheads); #endif nheads = 20; } return (mons_spec(MONS_HYDRA, MONS_NO_MONSTER, nheads)); } mons_spec mons_list::get_slime_spec(const std::string &name) const { std::string prefix = name.substr(0, name.find(" slime creature")); int slime_size = 1; if (prefix == "large") slime_size = 2; else if (prefix == "very large") slime_size = 3; else if (prefix == "enormous") slime_size = 4; else if (prefix == "titanic") slime_size = 5; else { #if DEBUG || DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Slime spec wants invalid size '%s'", prefix.c_str()); #endif } return (mons_spec(MONS_SLIME_CREATURE, MONS_NO_MONSTER, slime_size)); } // Handle draconians specified as: // Exactly as in mon-data.h: // yellow draconian or draconian knight - the monster specified. // // Others: // any draconian => any random draconain // any base draconian => any unspecialised coloured draconian. // any nonbase draconian => any specialised coloured draconian. // any draconian => any draconian of the colour. // any nonbase draconian => any specialised drac of the colour. // mons_spec mons_list::drac_monspec(std::string name) const { mons_spec spec; spec.mid = get_monster_by_name(name, true); // Check if it's a simple drac name, we're done. if (spec.mid != MONS_PROGRAM_BUG) return (spec); spec.mid = RANDOM_DRACONIAN; // Request for any draconian? if (starts_with(name, "any ")) // Strip "any " name = name.substr(4); if (starts_with(name, "base ")) { // Base dracs need no further work. return (RANDOM_BASE_DRACONIAN); } else if (starts_with(name, "nonbase ")) { spec.mid = RANDOM_NONBASE_DRACONIAN; name = name.substr(8); } trim_string(name); // Match "any draconian" if (name == "draconian") return (spec); // Check for recognition again to match any (nonbase) draconian. const monster_type colour = get_monster_by_name(name, true); if (colour != MONS_PROGRAM_BUG) { spec.monbase = colour; return (spec); } // Only legal possibility left is boss drac. std::string::size_type wordend = name.find(' '); if (wordend == std::string::npos) return (MONS_PROGRAM_BUG); std::string scolour = name.substr(0, wordend); if ((spec.monbase = draconian_colour_by_name(scolour)) == MONS_PROGRAM_BUG) return (MONS_PROGRAM_BUG); name = trimmed_string(name.substr(wordend + 1)); spec.mid = get_monster_by_name(name, true); // We should have a non-base draconian here. if (spec.mid == MONS_PROGRAM_BUG || mons_genus(spec.mid) != MONS_DRACONIAN || spec.mid == MONS_DRACONIAN || (spec.mid >= MONS_BLACK_DRACONIAN && spec.mid <= MONS_PALE_DRACONIAN)) { return (MONS_PROGRAM_BUG); } return (spec); } mons_spec mons_list::mons_by_name(std::string name) const { lowercase(name); name = replace_all_of( name, "_", " " ); name = replace_all( name, "random", "any" ); if (name == "nothing") return (-1); // Special casery: if (name == "pandemonium demon") return (MONS_PANDEMONIUM_DEMON); if (name == "any" || name == "any monster") return (RANDOM_MONSTER); if (name == "any demon") return (-100 - DEMON_RANDOM); if (name == "any lesser demon" || name == "lesser demon") return (-100 - DEMON_LESSER); if (name == "any common demon" || name == "common demon") return (-100 - DEMON_COMMON); if (name == "any greater demon" || name == "greater demon") return (-100 - DEMON_GREATER); if (name == "small zombie") return (MONS_ZOMBIE_SMALL); if (name == "large zombie") return (MONS_ZOMBIE_LARGE); if (name == "small skeleton") return (MONS_SKELETON_SMALL); if (name == "large skeleton") return (MONS_SKELETON_LARGE); if (name == "spectral thing") return (MONS_SPECTRAL_THING); if (name == "small simulacrum") return (MONS_SIMULACRUM_SMALL); if (name == "large simulacrum") return (MONS_SIMULACRUM_LARGE); if (name == "small abomination") return (MONS_ABOMINATION_SMALL); if (name == "large abomination") return (MONS_ABOMINATION_LARGE); if (ends_with(name, "-headed hydra") && !starts_with(name, "spectral ")) return (get_hydra_spec(name)); if (ends_with(name, " slime creature")) return (get_slime_spec(name)); mons_spec spec; get_zombie_type(name, spec); if (spec.mid != MONS_PROGRAM_BUG) return (spec); if (name.find("draconian") != std::string::npos) return drac_monspec(name); return (get_monster_by_name(name, true)); } ////////////////////////////////////////////////////////////////////// // item_list void item_list::clear() { items.clear(); } item_spec item_list::pick_item(item_spec_slot &slot) { int cumulative = 0; item_spec spec; for (item_spec_list::const_iterator i = slot.ilist.begin(); i != slot.ilist.end(); ++i) { const int weight = i->genweight; if (x_chance_in_y(weight, cumulative += weight)) spec = *i; } if (slot.fix_slot) { slot.ilist.clear(); slot.ilist.push_back(spec); slot.fix_slot = false; } return (spec); } item_spec item_list::get_item(int index) { if (index < 0 || index >= (int) items.size()) { const item_spec none; return (none); } return (pick_item(items[index])); } std::string item_list::add_item(const std::string &spec, bool fix) { error.clear(); item_spec_slot sp = parse_item_spec(spec); if (error.empty()) { if (fix) { sp.fix_slot = true; pick_item(sp); } items.push_back(sp); } return (error); } std::string item_list::set_item(int index, const std::string &spec) { error.clear(); if (index < 0) return (error = make_stringf("Index %d out of range", index)); item_spec_slot sp = parse_item_spec(spec); if (error.empty()) { if (index >= (int) items.size()) { items.reserve(index + 1); items.resize(index + 1, item_spec_slot()); } items.push_back(sp); } return (error); } void item_list::set_from_slot(const item_list &list, int slot_index) { clear(); // Don't set anything if an invalid index. // Future calls to get_item will just return no item. if (slot_index < 0 || (size_t)slot_index >= list.items.size()) return; items.push_back(list.items[slot_index]); } // TODO: More checking for innapropriate combinations, like the holy // wrath brand on a demonic weapon or the running ego on a helmet. static int str_to_ego(item_spec &spec, std::string ego_str) { const char* armour_egos[] = { "running", "fire_resistance", "cold_resistance", "poison_resistance", "see_invisible", "darkness", "strength", "dexterity", "intelligence", "ponderousness", "levitation", "magic_resistance", "protection", "stealth", "resistance", "positive_energy", "archmagi", "preservation", "reflection", "spirit shield", "archery", NULL }; const char* weapon_brands[] = { "flaming", // 1 "freezing", "holy_wrath", "electrocution", "orc_slaying", // 5 "dragon_slaying", "venom", "protection", "draining", "speed", // 10 "vorpal", "flame", "frost", "vampiricism", "pain", // 15 "distortion", "reaching", "returning", "chaos", "confuse", // 20 "penetration", "reaping", NULL }; const char* missile_brands[] = { "flame", "ice", "poisoned", "curare", "returning", "chaos", "penetration", "reaping", "dispersal", "exploding", "steel", "silver", "electric", "paralysis", "slow", "sleep", "confusion", "sickness", "wrath", NULL }; const char** name_lists[3] = {armour_egos, weapon_brands, missile_brands}; int armour_order[3] = {0, 1, 2}; int weapon_order[3] = {1, 0, 2}; int missile_order[3] = {2, 0, 1}; int *order; switch (spec.base_type) { case OBJ_ARMOUR: order = armour_order; break; case OBJ_WEAPONS: order = weapon_order; break; case OBJ_MISSILES: order = missile_order; break; default: DEBUGSTR("Bad base_type for ego'd item."); return 0; } const char** allowed = name_lists[order[0]]; for (int i = 0; allowed[i] != NULL; i++) { if (ego_str == allowed[i]) return (i + 1); } // Incompatible or non-existant ego type for (int i = 1; i <= 2; i++) { const char** list = name_lists[order[i]]; for (int j = 0; list[j] != NULL; j++) if (ego_str == list[j]) // Ego incompatible with base type. return (-1); } // Non-existant ego return 0; } int item_list::parse_acquirement_source(const std::string &source) { const std::string god_name(replace_all_of(source, "_", " ")); const god_type god(string_to_god(god_name.c_str(), true)); if (god == GOD_NO_GOD) error = make_stringf("unknown god name: '%s'", god_name.c_str()); return (god); } item_spec item_list::parse_single_spec(std::string s) { item_spec result; // If there's a colon, this must be a generation weight. int weight = find_weight(s); if (weight != TAG_UNFOUND) { result.genweight = weight; if (result.genweight <= 0) { error = make_stringf("Bad item generation weight: '%d'", result.genweight); return (result); } } const int qty = strip_number_tag(s, "q:"); if (qty != TAG_UNFOUND) result.qty = qty; const std::string acquirement_source = strip_tag_prefix(s, "acquire:"); if (!acquirement_source.empty() || strip_tag(s, "acquire")) { if (!acquirement_source.empty()) result.acquirement_source = parse_acquirement_source(acquirement_source); // If requesting acquirement, must specify item base type or // "any". result.level = ISPEC_ACQUIREMENT; if (s == "any") result.base_type = OBJ_RANDOM; else parse_random_by_class(s, result); return (result); } std::string ego_str = strip_tag_prefix(s, "ego:"); std::string race_str = strip_tag_prefix(s, "race:"); lowercase(ego_str); lowercase(race_str); if (race_str == "elven") result.race = MAKE_ITEM_ELVEN; else if (race_str == "dwarven") result.race = MAKE_ITEM_DWARVEN; else if (race_str == "orcish") result.race = MAKE_ITEM_ORCISH; else if (race_str == "none" || race_str == "no_race") result.race = MAKE_ITEM_NO_RACE; else if (!race_str.empty()) { error = make_stringf("Bad race: %s", race_str.c_str()); return (result); } std::string unrand_str = strip_tag_prefix(s, "unrand:"); if (strip_tag(s, "good_item")) result.level = MAKE_GOOD_ITEM; else { int number = strip_number_tag(s, "level:"); if (number != TAG_UNFOUND) { if (number <= 0 && number != ISPEC_GOOD && number != ISPEC_SUPERB && number != ISPEC_DAMAGED && number != ISPEC_BAD) { error = make_stringf("Bad item level: %d", number); return (result); } result.level = number; } } if (strip_tag(s, "damaged")) { result.level = ISPEC_DAMAGED; } if (strip_tag(s, "cursed")) { result.level = ISPEC_BAD; // damaged + cursed, actually } if (strip_tag(s, "randart")) { result.level = ISPEC_RANDART; } if (strip_tag(s, "no_uniq")) result.allow_uniques = 0; if (strip_tag(s, "allow_uniq")) result.allow_uniques = 1; else { int uniq = strip_number_tag(s, "uniq:"); if (uniq != TAG_UNFOUND) { if (uniq <= 0) { error = make_stringf("Bad uniq level: %d", uniq); return (result); } result.allow_uniques = uniq; } } // XXX: This is nice-ish now, but could probably do with being improved. if (strip_tag(s, "randbook")) { result.props["make_book_theme_randart"] = true; // make_book_theme_randart requires the following properties: // disc: , disc2: // numspells: , slevels: // spell: , owner: // None of these are required, but if you don't intend on using any // of them, use "any fixed theme book" instead. short disc1 = 0; short disc2 = 0; std::string st_disc1 = strip_tag_prefix(s, "disc:"); if (!st_disc1.empty()) { disc1 = school_by_name(st_disc1); if (disc1 == SPTYP_NONE) { error = make_stringf("Bad spell school: %s", st_disc1.c_str()); return (result); } } std::string st_disc2 = strip_tag_prefix(s, "disc2:"); if (!st_disc2.empty()) { disc2 = school_by_name(st_disc2); if (disc2 == SPTYP_NONE) { error = make_stringf("Bad spell school: %s", st_disc2.c_str()); return (result); } } if (disc1 != 0 && disc2 != 0) { if (disciplines_conflict(disc1, disc2)) { error = make_stringf("Bad combination of spell schools: %s & %s.", st_disc1.c_str(), st_disc2.c_str()); return (result); } } if (disc1 == 0 && disc2 != 0) { // Don't fail, just quietly swap. Any errors in disc1's syntax will // have been caught above, anyway. disc1 = disc2; disc2 = 0; } short num_spells = strip_number_tag(s, "numspells:"); if (num_spells == TAG_UNFOUND) num_spells = -1; else if (num_spells <= 0 || num_spells > SPELLBOOK_SIZE) { error = make_stringf("Bad spellbook size: %d", num_spells); return (result); } short slevels = strip_number_tag(s, "slevels:"); if (slevels == TAG_UNFOUND) slevels = -1; else if (slevels == 0) { error = make_stringf("Bad slevels: %d.", slevels); return (result); } const std::string spell = replace_all_of(strip_tag_prefix(s, "spell:"), "_", " "); if (!spell.empty() && spell_by_name(spell) == SPELL_NO_SPELL) { error = make_stringf("Bad spell: %s", spell.c_str()); return (result); } const std::string owner = replace_all_of(strip_tag_prefix(s, "owner:"), "_", " "); result.props["randbook_disc1"] = disc1; result.props["randbook_disc2"] = disc2; result.props["randbook_num_spells"] = num_spells; result.props["randbook_slevels"] = slevels; result.props["randbook_spell"] = spell; result.props["randbook_owner"] = owner; result.base_type = OBJ_BOOKS; // This is changed in make_book_theme_randart. result.sub_type = BOOK_MINOR_MAGIC_I; result.plus = -1; return (result); } // Clean up after any tag brain damage. trim_string(s); if (!ego_str.empty()) error = "Can't set an ego for random items."; // Completely random? if (s == "random" || s == "any" || s == "%") return (result); if (s == "*" || s == "star_item") { result.level = ISPEC_GOOD; return (result); } else if (s == "|" || s == "superb_item") { result.level = ISPEC_SUPERB; return (result); } else if (s == "$" || s == "gold") { if (!ego_str.empty()) error = "Can't set an ego for gold."; result.base_type = OBJ_GOLD; result.sub_type = OBJ_RANDOM; return (result); } if (s == "nothing") { error.clear(); result.base_type = OBJ_UNASSIGNED; return (result); } error.clear(); // Check for "any objclass" if (s.find("any ") == 0) parse_random_by_class(s.substr(4), result); else if (s.find("random ") == 0) parse_random_by_class(s.substr(7), result); // Check for actual item names. else parse_raw_name(s, result); if (!error.empty()) return (result); if (!unrand_str.empty()) { result.ego = get_unrandart_num(unrand_str.c_str()); if (result.ego == SPWPN_NORMAL) { error = make_stringf("Unknown unrand art: %s", unrand_str.c_str()); return result; } result.ego = -result.ego; return result; } if (ego_str.empty()) return (result); if (result.base_type != OBJ_WEAPONS && result.base_type != OBJ_MISSILES && result.base_type != OBJ_ARMOUR) { error = "An ego can only be applied to a weapon, missile or " "armour."; return (result); } if (ego_str == "none") { result.ego = -1; return (result); } const int ego = str_to_ego(result, ego_str); if (ego == 0) { error = make_stringf("No such ego as: %s", ego_str.c_str()); return (result); } else if (ego == -1) { error = make_stringf("Ego '%s' is invalid for item '%s'.", ego_str.c_str(), s.c_str()); return (result); } else if (result.sub_type == OBJ_RANDOM) { // it will be assigned among appropriate ones later } else if (result.base_type == OBJ_WEAPONS && !is_weapon_brand_ok(result.sub_type, ego) || result.base_type == OBJ_ARMOUR && !is_armour_brand_ok(result.sub_type, ego) || result.base_type == OBJ_MISSILES && !is_missile_brand_ok(result.sub_type, ego)) { error = make_stringf("Ego '%s' is incompatible with item '%s'.", ego_str.c_str(), s.c_str()); return (result); } result.ego = ego; return (result); } void item_list::parse_random_by_class(std::string c, item_spec &spec) { trim_string(c); if (c == "?" || c.empty()) { error = make_stringf("Bad item class: '%s'", c.c_str()); return; } for (int type = OBJ_WEAPONS; type < NUM_OBJECT_CLASSES; ++type) { if (c == item_class_name(type, true)) { spec.base_type = static_cast(type); return; } } // Random manual? if (c == "manual") { spec.base_type = OBJ_BOOKS; spec.sub_type = BOOK_MANUAL; spec.plus = -1; return; } else if (c == "fixed theme book") { spec.base_type = OBJ_BOOKS; spec.sub_type = BOOK_RANDART_THEME; spec.plus = -1; return; } else if (c == "fixed level book") { spec.base_type = OBJ_BOOKS; spec.sub_type = BOOK_RANDART_LEVEL; spec.plus = -1; return; } error = make_stringf("Bad item class: '%s'", c.c_str()); } void item_list::parse_raw_name(std::string name, item_spec &spec) { trim_string(name); if (name.empty()) { error = make_stringf("Bad item name: '%s'", name.c_str()); return ; } item_def parsed = find_item_type(OBJ_UNASSIGNED, name); if (parsed.sub_type != OBJ_RANDOM) { spec.base_type = parsed.base_type; spec.sub_type = parsed.sub_type; spec.plus = parsed.plus; spec.plus2 = parsed.plus2; return; } error = make_stringf("Bad item name: '%s'", name.c_str()); } item_list::item_spec_slot item_list::parse_item_spec(std::string spec) { // lowercase(spec); item_spec_slot list; list.fix_slot = strip_tag(spec, "fix_slot"); std::vector specifiers = split_string( "/", spec ); for (unsigned i = 0; i < specifiers.size() && error.empty(); ++i) { list.ilist.push_back( parse_single_spec(specifiers[i]) ); } return (list); } ///////////////////////////////////////////////////////////////////////// // subst_spec subst_spec::subst_spec(std::string _k, bool _f, const glyph_replacements_t &g) : key(_k), count(-1), fix(_f), frozen_value(0), repl(g) { } subst_spec::subst_spec(int _count, bool dofix, const glyph_replacements_t &g) : key(""), count(_count), fix(dofix), frozen_value(0), repl(g) { } int subst_spec::value() { if (frozen_value) return (frozen_value); int cumulative = 0; int chosen = 0; for (int i = 0, size = repl.size(); i < size; ++i) if (x_chance_in_y(repl[i].second, cumulative += repl[i].second)) chosen = repl[i].first; if (fix) frozen_value = chosen; return (chosen); } ////////////////////////////////////////////////////////////////////////// // nsubst_spec nsubst_spec::nsubst_spec(std::string _key, const std::vector &_specs) : key(_key), specs(_specs) { } ////////////////////////////////////////////////////////////////////////// // colour_spec int colour_spec::get_colour() { if (fixed_colour != BLACK) return (fixed_colour); int chosen = BLACK; int cweight = 0; for (int i = 0, size = colours.size(); i < size; ++i) if (x_chance_in_y(colours[i].second, cweight += colours[i].second)) chosen = colours[i].first; if (fix) fixed_colour = chosen; return (chosen); } ////////////////////////////////////////////////////////////////////////// // fprop_spec unsigned long fprop_spec::get_property() { if (fixed_prop != FPROP_NONE) return (fixed_prop); unsigned long chosen = FPROP_NONE; int cweight = 0; for (int i = 0, size = fprops.size(); i < size; ++i) if (x_chance_in_y(fprops[i].second, cweight += fprops[i].second)) chosen = fprops[i].first; if (fix) fixed_prop = chosen; return (chosen); } ////////////////////////////////////////////////////////////////////////// // string_spec std::string string_spec::get_property() { if (!fixed_str.empty()) return fixed_str; std::string chosen = ""; int cweight = 0; for (int i = 0, size = strlist.size(); i < size; ++i) if (x_chance_in_y(strlist[i].second, cweight += strlist[i].second)) chosen = strlist[i].first; if (fix) fixed_str = chosen; return (chosen); } ////////////////////////////////////////////////////////////////////////// // map_marker_spec std::string map_marker_spec::apply_transform(map_lines &map) { std::vector positions = map.find_glyph(key); // Markers with no key are not an error. if (positions.empty()) return (""); for (int i = 0, size = positions.size(); i < size; ++i) { try { map_marker *mark = create_marker(); if (!mark) return make_stringf("Unable to parse marker from %s", marker.c_str()); mark->pos = positions[i]; map.add_marker(mark); } catch (const std::string &err) { return (err); } } return (""); } map_marker *map_marker_spec::create_marker() { return lua_fn.get() ? new map_lua_marker(*lua_fn.get()) : map_marker::parse_marker(marker); } ////////////////////////////////////////////////////////////////////////// // map_flags map_flags::map_flags() : flags_set(0), flags_unset(0) { } void map_flags::clear() { flags_set = flags_unset = 0; } typedef std::map flag_map; map_flags map_flags::parse(const std::string flag_list[], const std::string &s) throw(std::string) { map_flags mf; const std::vector segs = split_string("/", s); flag_map flag_vals; for (int i = 0; !flag_list[i].empty(); i++) flag_vals[flag_list[i]] = 1 << i; for (int i = 0, size = segs.size(); i < size; i++) { std::string flag = segs[i]; bool negate = false; if (flag[0] == '!') { flag = flag.substr(1); negate = true; } flag_map::const_iterator val = flag_vals.find(flag); if (val == flag_vals.end()) throw make_stringf("Unknown flag: '%s'", flag.c_str()); if (negate) mf.flags_unset |= val->second; else mf.flags_set |= val->second; } return mf; } ////////////////////////////////////////////////////////////////////////// // keyed_mapspec keyed_mapspec::keyed_mapspec() : key_glyph(-1), feat(), item(), mons() { } std::string keyed_mapspec::set_feat(const std::string &s, bool fix) { err.clear(); parse_features(s); feat.fix_slot = fix; // Fix this feature. if (fix) get_feat(); return (err); } void keyed_mapspec::copy_feat(const keyed_mapspec &spec) { feat = spec.feat; } void keyed_mapspec::parse_features(const std::string &s) { feat.feats.clear(); std::vector specs = split_string("/", s); for (int i = 0, size = specs.size(); i < size; ++i) { const std::string &spec = specs[i]; feature_spec_list feats = parse_feature(spec); if (!err.empty()) return; feat.feats.insert( feat.feats.end(), feats.begin(), feats.end() ); } } feature_spec keyed_mapspec::parse_trap(std::string s, int weight) { strip_tag(s, "trap"); const bool known = strip_tag(s, "known"); trim_string(s); lowercase(s); const int trap = str_to_trap(s); if (trap == -1) err = make_stringf("bad trap name: '%s'", s.c_str()); feature_spec fspec(known ? 1 : -1, weight); fspec.trap = trap; return (fspec); } feature_spec keyed_mapspec::parse_shop(std::string s, int weight) { strip_tag(s, "shop"); trim_string(s); lowercase(s); const int shop = str_to_shoptype(s); if (shop == -1) err = make_stringf("bad shop type: '%s'", s.c_str()); feature_spec fspec(-1, weight); fspec.shop = shop; return (fspec); } feature_spec_list keyed_mapspec::parse_feature(const std::string &str) { std::string s = str; int weight = find_weight(s); if (weight == TAG_UNFOUND || weight <= 0) weight = 10; trim_string(s); feature_spec_list list; if (s.length() == 1) { feature_spec fsp(-1, weight); fsp.glyph = s[0]; list.push_back( fsp ); return (list); } if (s.find("trap") != std::string::npos) { list.push_back( parse_trap(s, weight) ); return (list); } if (s.find("shop") != std::string::npos || s.find("store") != std::string::npos) { list.push_back( parse_shop(s, weight) ); return (list); } const dungeon_feature_type ftype = dungeon_feature_by_name(s); if (ftype == DNGN_UNSEEN) err = make_stringf("no features matching \"%s\"", str.c_str()); else list.push_back( feature_spec( ftype, weight ) ); return (list); } std::string keyed_mapspec::set_mons(const std::string &s, bool fix) { err.clear(); mons.clear(); std::vector segments = split_string(",", s); for (int i = 0, size = segments.size(); i < size; ++i) { const std::string error = mons.add_mons(segments[i], fix); if (!error.empty()) return (error); } return (""); } std::string keyed_mapspec::set_item(const std::string &s, bool fix) { err.clear(); item.clear(); std::vector segs = split_string(",", s); for (int i = 0, size = segs.size(); i < size; ++i) { err = item.add_item(segs[i], fix); if (!err.empty()) return (err); } return (err); } std::string keyed_mapspec::set_mask(const std::string &s, bool garbage) { UNUSED(garbage); err.clear(); try { static std::string flag_list[] = {"vault", "no_item_gen", "no_monster_gen", "no_pool_fixup", "no_secret_doors", "no_wall_fixup", "opaque", ""}; map_mask = map_flags::parse(flag_list, s); } catch (const std::string &error) { err = error; return (err); } // If not also a KFEAT... if (feat.feats.size() == 0) { feature_spec fsp(-1, 10); fsp.glyph = key_glyph; feat.feats.push_back(fsp); } return (err); } void keyed_mapspec::copy_mons(const keyed_mapspec &spec) { mons = spec.mons; } void keyed_mapspec::copy_item(const keyed_mapspec &spec) { item = spec.item; } void keyed_mapspec::copy_mask(const keyed_mapspec &spec) { map_mask = spec.map_mask; } feature_spec keyed_mapspec::get_feat() { return feat.get_feat('.'); } mons_list &keyed_mapspec::get_monsters() { return (mons); } item_list &keyed_mapspec::get_items() { return (item); } map_flags &keyed_mapspec::get_mask() { return (map_mask); } ////////////////////////////////////////////////////////////////////////// // feature_slot feature_slot::feature_slot() : feats(), fix_slot(false) { } feature_spec feature_slot::get_feat(int def_glyph) { int tweight = 0; feature_spec chosen_feat = feature_spec(DNGN_FLOOR); if (def_glyph != -1) { chosen_feat.feat = -1; chosen_feat.glyph = def_glyph; } for (int i = 0, size = feats.size(); i < size; ++i) { const feature_spec &feat = feats[i]; if (x_chance_in_y(feat.genweight, tweight += feat.genweight)) chosen_feat = feat; } if (fix_slot) { feats.clear(); feats.push_back( chosen_feat ); } return (chosen_feat); }