/* * File: l_dgnbld.cc * Summary: Building routines (library "dgn"). */ #include "AppHdr.h" #include #include "cluautil.h" #include "coord.h" #include "dungeon.h" #include "l_libs.h" #include "mapdef.h" #include "random.h" static const char *traversable_glyphs = ".+=w@{}()[]<>BC^~TUVY$%*|Odefghijk0123456789"; static const char *exit_glyphs = "{}()[]<>@"; // Return the integer stored in the table (on the stack) with the key name. // If the key doesn't exist or the value is the wrong type, return defval. static int _table_int(lua_State *ls, int idx, const char *name, int defval) { if (!lua_istable(ls, idx)) return defval; lua_pushstring(ls, name); lua_gettable(ls, idx < 0 ? idx - 1 : idx); bool nil = lua_isnil(ls, idx); bool valid = lua_isnumber(ls, idx); if (!nil && !valid) luaL_error(ls, "'%s' in table, but not an int.", name); int ret = (!nil && valid ? luaL_checkint(ls, idx) : defval); lua_pop(ls, 1); return (ret); } // Return the character stored in the table (on the stack) with the key name. // If the key doesn't exist or the value is the wrong type, return defval. static char _table_char(lua_State *ls, int idx, const char *name, char defval) { if (!lua_istable(ls, idx)) return defval; lua_pushstring(ls, name); lua_gettable(ls, idx < 0 ? idx - 1 : idx); bool nil = lua_isnil(ls, idx); bool valid = lua_isstring(ls, idx); if (!nil && !valid) luaL_error(ls, "'%s' in table, but not a string.", name); char ret = defval; if (!nil && valid) { const char *str = lua_tostring(ls, idx); if (str[0] && !str[1]) ret = str[0]; else luaL_error(ls, "'%s' has more than one character.", name); } lua_pop(ls, 1); return (ret); } // Return the string stored in the table (on the stack) with the key name. // If the key doesn't exist or the value is the wrong type, return defval. static const char* _table_str(lua_State *ls, int idx, const char *name, const char *defval) { if (!lua_istable(ls, idx)) return defval; lua_pushstring(ls, name); lua_gettable(ls, idx < 0 ? idx - 1 : idx); bool nil = lua_isnil(ls, idx); bool valid = lua_isstring(ls, idx); if (!nil && !valid) luaL_error(ls, "'%s' in table, but not a string.", name); const char *ret = (!nil && valid ? lua_tostring(ls, idx) : defval); lua_pop(ls, 1); return (ret); } // Return the boolean stored in the table (on the stack) with the key name. // If the key doesn't exist or the value is the wrong type, return defval. static bool _table_bool(lua_State *ls, int idx, const char *name, bool defval) { if (!lua_istable(ls, idx)) return defval; lua_pushstring(ls, name); lua_gettable(ls, idx < 0 ? idx - 1 : idx); bool nil = lua_isnil(ls, idx); bool valid = lua_isboolean(ls, idx); if (!nil && !valid) luaL_error(ls, "'%s' in table, but not a bool.", name); bool ret = (!nil && valid ? lua_toboolean(ls, idx) : defval); lua_pop(ls, 1); return (ret); } // These macros all assume the table is on the top of the lua stack. #define TABLE_INT(ls, val, def) int val = _table_int(ls, -1, #val, def); #define TABLE_CHAR(ls, val, def) char val = _table_char(ls, -1, #val, def); #define TABLE_STR(ls, val, def) const char *val = _table_str(ls, -1, #val, def); #define TABLE_BOOL(ls, val, def) bool val = _table_bool(ls, -1, #val, def); // Read a set of box coords (x1, y1, x2, y2) from the table. // Return true if coords are valid. static bool _coords(lua_State *ls, map_lines &lines, int &x1, int &y1, int &x2, int &y2, int border = 0) { const int idx = -1; x1 = _table_int(ls, idx, "x1", 1); y1 = _table_int(ls, idx, "y1", 1); x2 = _table_int(ls, idx, "x2", lines.width()); y2 = _table_int(ls, idx, "y2", lines.height()); if (x2 < x1) std::swap(x1, x2); if (y2 < y1) std::swap(y1, y2); x1--; y1--; x2--; y2--; return (x1 + border <= x2 - border && y1 + border <= y2 - border); } // Check if a given coordiante is valid for lines. static bool _valid_coord(lua_State *ls, map_lines &lines, int x, int y) { if (x < 0 || x >= lines.width()) { luaL_error(ls, "Invalid x coordinate: %d", x); return false; } if (y < 0 || y >= lines.height()) { luaL_error(ls, "Invalid y coordinate: %d", y); return false; } return true; } LUAFN(dgn_count_feature_in_box) { LINES(ls, 1, lines); int x1, y1, x2, y2; if (!_coords(ls, lines, x1, y1, x2, y2)) return (0); TABLE_STR(ls, feat, ""); coord_def tl(x1, y1); coord_def br(x2, y2); PLUARET(number, lines.count_feature_in_box(tl, br, feat)); } LUAFN(dgn_count_antifeature_in_box) { LINES(ls, 1, lines); int x1, y1, x2, y2; if (!_coords(ls, lines, x1, y1, x2, y2)) return (0); TABLE_STR(ls, feat, ""); coord_def tl(x1, y1); coord_def br(x2, y2); int sum = (br.x - tl.x + 1) * (br.y - tl.y + 1); PLUARET(number, sum - lines.count_feature_in_box(tl, br, feat)); } LUAFN(dgn_count_neighbors) { LINES(ls, 1, lines); TABLE_STR(ls, feat, ""); TABLE_INT(ls, x, -1); TABLE_INT(ls, y, -1); if (!_valid_coord(ls, lines, --x, --y)) return (0); coord_def tl(x-1, y-1); coord_def br(x+1, y+1); PLUARET(number, lines.count_feature_in_box(tl, br, feat)); } LUAFN(dgn_extend_map) { LINES(ls, 1, lines); TABLE_INT(ls, height, 1); TABLE_INT(ls, width, 1); TABLE_CHAR(ls, fill, 'x'); lines.extend(width, height, fill); return (0); } LUAFN(dgn_fill_area) { LINES(ls, 1, lines); int x1, y1, x2, y2; if (!_coords(ls, lines, x1, y1, x2, y2)) return (0); TABLE_CHAR(ls, fill, 'x'); for (int y = y1; y <= y2; ++y) for (int x = x1; x <= x2; ++x) { lines(x, y) = fill; } return (0); } LUAFN(dgn_fill_disconnected) { LINES(ls, 1, lines); int x1, y1, x2, y2; if (!_coords(ls, lines, x1, y1, x2, y2)) return (0); TABLE_CHAR(ls, fill, 'x'); TABLE_STR(ls, passable, traversable_glyphs); TABLE_STR(ls, wanted, exit_glyphs); coord_def tl(x1, y1); coord_def br(x2, y2); travel_distance_grid_t tpd; memset(tpd, 0, sizeof(tpd)); int nzones = 0; for (rectangle_iterator ri(tl, br); ri; ++ri) { const coord_def c = *ri; if (tpd[c.x][c.y] || passable && !strchr(passable, lines(c))) continue; if (lines.fill_zone(tpd, c, tl, br, ++nzones, wanted, passable)) continue; // If wanted wasn't found, fill every passable square that // we just found with the 'fill' glyph. for (rectangle_iterator f(tl, br); f; ++f) { const coord_def fc = *f; if (tpd[fc.x][fc.y] == nzones) lines(fc) = fill; } } return (0); } LUAFN(dgn_height) { LINES(ls, 1, lines); PLUARET(number, lines.height()); } LUAFN(dgn_join_the_dots) { LINES(ls, 1, lines); TABLE_INT(ls, x1, -1); TABLE_INT(ls, y1, -1); TABLE_INT(ls, x2, -1); TABLE_INT(ls, y2, -1); TABLE_STR(ls, passable, traversable_glyphs); TABLE_CHAR(ls, fill, '.'); if (!_valid_coord(ls, lines, --x1, --y1)) return (0); if (!_valid_coord(ls, lines, --x2, --y2)) return (0); coord_def from(x1, y1); coord_def to(x2, y2); if (from == to) return (0); coord_def at = from; do { char glyph = lines(at); if (!strchr(passable, glyph)) lines(at) = fill; if (at == to) break; if (at.x < to.x) { at.x++; continue; } if (at.x > to.x) { at.x--; continue; } if (at.y > to.y) { at.y--; continue; } if (at.y < to.y) { at.y++; continue; } } while (true); return (0); } LUAFN(dgn_make_circle) { LINES(ls, 1, lines); TABLE_INT(ls, x, -1); TABLE_INT(ls, y, -1); TABLE_INT(ls, radius, 1); TABLE_CHAR(ls, fill, 'x'); if (!_valid_coord(ls, lines, --x, --y)) return (0); for (int ry = -radius; ry <= radius; ++ry) for (int rx = -radius; rx <= radius; ++rx) if (rx * rx + ry * ry < radius * radius) lines(x + rx, y + ry) = fill; return (0); } LUAFN(dgn_make_diamond) { LINES(ls, 1, lines); TABLE_INT(ls, x, -1); TABLE_INT(ls, y, -1); TABLE_INT(ls, radius, 1); TABLE_CHAR(ls, fill, 'x'); if (!_valid_coord(ls, lines, --x, --y)) return (0); for (int ry = -radius; ry <= radius; ++ry) for (int rx = -radius; rx <= radius; ++rx) if (std::abs(rx) + std::abs(ry) <= radius) lines(x + rx, y + ry) = fill; return (0); } LUAFN(dgn_make_rounded_square) { LINES(ls, 1, lines); TABLE_INT(ls, x, -1); TABLE_INT(ls, y, -1); TABLE_INT(ls, radius, 1); TABLE_CHAR(ls, fill, 'x'); if (!_valid_coord(ls, lines, --x, --y)) return (0); for (int ry = -radius; ry <= radius; ++ry) for (int rx = -radius; rx <= radius; ++rx) if (std::abs(rx) != radius || std::abs(ry) != radius) lines(x + rx, y + ry) = fill; return (0); } LUAFN(dgn_make_square) { LINES(ls, 1, lines); TABLE_INT(ls, x, -1); TABLE_INT(ls, y, -1); TABLE_INT(ls, radius, 1); TABLE_CHAR(ls, fill, 'x'); if (!_valid_coord(ls, lines, --x, --y)) return (0); for (int ry = -radius; ry <= radius; ++ry) for (int rx = -radius; rx <= radius; ++rx) lines(x + rx, y + ry) = fill; return (0); } // Return a metatable for a point on the map_lines grid. LUAFN(dgn_mapgrd_table) { MAP(ls, 1, map); map_def **mapref = clua_new_userdata(ls, MAPGRD_METATABLE); *mapref = map; return (1); } LUAFN(dgn_octa_room) { LINES(ls, 1, lines); int default_oblique = std::min(lines.width(), lines.height()) / 2 - 1; TABLE_INT(ls, oblique, default_oblique); TABLE_CHAR(ls, outside, 'x'); TABLE_CHAR(ls, inside, '.'); TABLE_STR(ls, replace, ""); int x1, y1, x2, y2; if (!_coords(ls, lines, x1, y1, x2, y2)) return (0); coord_def tl(x1, y1); coord_def br(x2, y2); for (rectangle_iterator ri(tl, br); ri; ++ri) { const coord_def mc = *ri; char glyph = lines(mc); if (replace[0] && !strchr(replace, glyph)) continue; int ob = 0; ob += std::max(oblique + tl.x - mc.x, 0); ob += std::max(oblique + mc.x - br.x, 0); bool is_inside = (mc.y >= tl.y + ob && mc.y <= br.y - ob); lines(mc) = is_inside ? inside : outside; } return (0); } LUAFN(dgn_replace_area) { LINES(ls, 1, lines); TABLE_STR(ls, find, '\0'); TABLE_CHAR(ls, replace, '\0'); int x1, y1, x2, y2; if (!_coords(ls, lines, x1, y1, x2, y2)) return (0); for (int y = y1; y <= y2; ++y) for (int x = x1; x <= x2; ++x) if (strchr(find, lines(x, y))) lines(x, y) = replace; return (0); } LUAFN(dgn_replace_first) { LINES(ls, 1, lines); TABLE_INT(ls, x, 0); TABLE_INT(ls, y, 0); TABLE_INT(ls, xdir, 2); TABLE_INT(ls, ydir, 2); TABLE_CHAR(ls, find, '\0'); TABLE_CHAR(ls, replace, '\0'); TABLE_BOOL(ls, required, false); if (!_valid_coord(ls, lines, --x, --y)) return (0); if (xdir < -1 || xdir > 1) { return (luaL_error(ls, "Invalid xdir: %d", xdir)); } if (ydir < -1 || ydir > 1) { return (luaL_error(ls, "Invalid ydir: %d", ydir)); } while (lines.in_bounds(coord_def(x, y))) { if (lines(x, y) == find) { lines(x, y) = replace; return (0); } x += xdir; y += ydir; } if (required) return (luaL_error(ls, "Could not find feature '%c' to replace", find)); return (0); } LUAFN(dgn_replace_random) { LINES(ls, 1, lines); TABLE_CHAR(ls, find, '\0'); TABLE_CHAR(ls, replace, '\0'); TABLE_BOOL(ls, required, false); int x1, y1, x2, y2; if (!_coords(ls, lines, x1, y1, x2, y2)) return (0); int count = (x2 - x1) * (y2 - y1); if (!count) { if (required) luaL_error(ls, "%s", "No elements to replace"); return (0); } std::vector loc; loc.reserve(count); for (int y = y1; y <= y2; ++y) for (int x = x1; x <= x2; ++x) if (lines(x, y) == find) loc.push_back(coord_def(x, y)); if (!loc.size()) { if (required) return (luaL_error(ls, "Could not find '%c'", find)); } int idx = random2(loc.size()); lines(loc[idx]) = replace; return (0); } LUAFN(dgn_smear_map) { LINES(ls, 1, lines); TABLE_INT(ls, iterations, 1); TABLE_CHAR(ls, smear, 'x'); TABLE_STR(ls, onto, "."); TABLE_BOOL(ls, boxy, false); const int border = 1; int x1, y1, x2, y2; if (!_coords(ls, lines, x1, y1, x2, y2, border)) return (0); const int max_test_per_iteration = 10; int sanity = 0; int max_sanity = iterations * max_test_per_iteration; for (int i = 0; i < iterations; i++) { bool diagonals, straights; coord_def mc; do { do { if (sanity++ > max_sanity) return (0); mc.x = random_range(x1+border, y2-border); mc.y = random_range(y1+border, y2-border); } while (onto[0] && !strchr(onto, lines(mc))); // Is there a "smear" feature along the diagonal from mc? diagonals = lines(mc.x+1, mc.y+1) == smear || lines(mc.x-1, mc.y+1) == smear || lines(mc.x-1, mc.y-1) == smear || lines(mc.x+1, mc.y-1) == smear; // Is there a "smear" feature up, down, left, or right from mc? straights = lines(mc.x+1, mc.y) == smear || lines(mc.x-1, mc.y) == smear || lines(mc.x, mc.y+1) == smear || lines(mc.x, mc.y-1) == smear; } while (!straights && (boxy || !diagonals)); lines(mc) = smear; } return (0); } LUAFN(dgn_spotty_map) { LINES(ls, 1, lines); TABLE_STR(ls, replace, "x"); TABLE_CHAR(ls, fill, '.'); TABLE_BOOL(ls, boxy, true); TABLE_INT(ls, iterations, random2(boxy ? 750 : 1500)); const int border = 4; int x1, y1, x2, y2; if (!_coords(ls, lines, x1, y1, x2, y2, border)) return (0); const int max_test_per_iteration = 10; int sanity = 0; int max_sanity = iterations * max_test_per_iteration; for (int i = 0; i < iterations; i++) { int x, y; do { if (sanity++ > max_sanity) return (0); x = random_range(x1 + border, x2 - border); y = random_range(y1 + border, y2 - border); } while (strchr(replace, lines(x, y)) && strchr(replace, lines(x-1, y)) && strchr(replace, lines(x+1, y)) && strchr(replace, lines(x, y-1)) && strchr(replace, lines(x, y+1)) && strchr(replace, lines(x-2, y)) && strchr(replace, lines(x+2, y)) && strchr(replace, lines(x, y-2)) && strchr(replace, lines(x, y+2))); if (strchr(replace, lines(x, y))) lines(x, y) = fill; if (strchr(replace, lines(x, y-1))) lines(x, y-1) = fill; if (strchr(replace, lines(x, y+1))) lines(x, y+1) = fill; if (strchr(replace, lines(x-1, y))) lines(x-1, y) = fill; if (strchr(replace, lines(x+1, y))) lines(x+1, y) = fill; if (boxy) { if (strchr(replace, lines(x-1, y-1))) lines(x-1, y-1) = fill; if (strchr(replace, lines(x+1, y+1))) lines(x+1, y+1) = fill; if (strchr(replace, lines(x-1, y+1))) lines(x-1, y+1) = fill; if (strchr(replace, lines(x+1, y-1))) lines(x+1, y-1) = fill; } } return (0); } static int dgn_width(lua_State *ls) { LINES(ls, 1, lines); PLUARET(number, lines.width()); } const struct luaL_reg dgn_build_dlib[] = { { "count_feature_in_box", &dgn_count_feature_in_box }, { "count_antifeature_in_box", &dgn_count_antifeature_in_box }, { "count_neighbors", &dgn_count_neighbors }, { "extend_map", &dgn_extend_map }, { "fill_area", &dgn_fill_area }, { "fill_disconnected", &dgn_fill_disconnected }, { "height", dgn_height }, { "join_the_dots", &dgn_join_the_dots }, { "make_circle", &dgn_make_circle }, { "make_diamond", &dgn_make_diamond }, { "make_rounded_square", &dgn_make_rounded_square }, { "make_square", &dgn_make_square }, { "mapgrd_table", dgn_mapgrd_table }, { "octa_room", &dgn_octa_room }, { "replace_area", &dgn_replace_area }, { "replace_first", &dgn_replace_first }, { "replace_random", &dgn_replace_random }, { "smear_map", &dgn_smear_map }, { "spotty_map", &dgn_spotty_map }, { "width", dgn_width }, { NULL, NULL } };