/* * File: luadgn.cc * Summary: Dungeon-builder Lua interface. * Created by: dshaligram on Sat Jun 23 20:02:09 2007 UTC * * Modified for Crawl Reference by $Author$ on $Date$ */ #include "AppHdr.h" REVISION("$Rev$"); #include #include #include #include #include "branch.h" #include "chardump.h" #include "clua.h" #include "cloud.h" #include "describe.h" #include "directn.h" #include "dungeon.h" #include "files.h" #include "hiscores.h" #include "initfile.h" #include "items.h" #include "luadgn.h" #include "mapdef.h" #include "mapmark.h" #include "maps.h" #include "misc.h" #include "mon-util.h" #include "monplace.h" #include "spl-util.h" #include "state.h" #include "stuff.h" #include "tags.h" #include "terrain.h" #include "view.h" #ifdef UNIX #include #include #endif #define MONSLIST_METATABLE "crawldgn.monster_list" #define ITEMLIST_METATABLE "crawldgn.item_list" static mons_list _lua_get_mlist(lua_State *ls, int ndx); static item_list _lua_get_ilist(lua_State *ls, int ndx); template static void _push_object_type(lua_State *ls, const char *meta, const T &data) { T **ptr = clua_new_userdata(ls, meta); *ptr = new T(data); } template static int dlua_gentable(lua_State *ls, const list &strings, lpush push) { lua_newtable(ls); for (int i = 0, size = strings.size(); i < size; ++i) { push(ls, strings[i]); lua_rawseti(ls, -2, i + 1); } return (1); } inline static void dlua_pushcxxstring(lua_State *ls, const std::string &s) { lua_pushstring(ls, s.c_str()); } int dlua_stringtable(lua_State *ls, const std::vector &s) { return dlua_gentable(ls, s, dlua_pushcxxstring); } static int dlua_compiled_chunk_writer(lua_State *ls, const void *p, size_t sz, void *ud) { std::ostringstream &out = *static_cast(ud); out.write((const char *) p, sz); return (0); } /////////////////////////////////////////////////////////////////////////// // dlua_chunk dlua_chunk::dlua_chunk(const std::string &_context) : file(), chunk(), compiled(), context(_context), first(-1), last(-1), error() { clear(); } // Initialises a chunk from the function on the top of stack. // This function must not be a closure, i.e. must not have any upvalues. dlua_chunk::dlua_chunk(lua_State *ls) : file(), chunk(), compiled(), context(), first(-1), last(-1), error() { clear(); lua_stack_cleaner cln(ls); std::ostringstream out; const int err = lua_dump(ls, dlua_compiled_chunk_writer, &out); if (err) { const char *e = lua_tostring(ls, -1); error = e? e : "Unknown error compiling chunk"; } compiled = out.str(); } dlua_chunk dlua_chunk::precompiled(const std::string &chunk) { dlua_chunk dchunk; dchunk.compiled = chunk; return (dchunk); } void dlua_chunk::write(writer& outf) const { if (empty()) { marshallByte(outf, CT_EMPTY); return; } if (!compiled.empty()) { marshallByte(outf, CT_COMPILED); marshallString4(outf, compiled); } else { marshallByte(outf, CT_SOURCE); marshallString4(outf, chunk); } marshallString4(outf, file); marshallLong(outf, first); } void dlua_chunk::read(reader& inf) { clear(); chunk_t type = static_cast(unmarshallByte(inf)); switch (type) { case CT_EMPTY: return; case CT_SOURCE: unmarshallString4(inf, chunk); break; case CT_COMPILED: unmarshallString4(inf, compiled); break; } unmarshallString4(inf, file); first = unmarshallLong(inf); } void dlua_chunk::clear() { file.clear(); chunk.clear(); first = last = -1; error.clear(); compiled.clear(); } void dlua_chunk::set_file(const std::string &s) { file = s; } void dlua_chunk::add(int line, const std::string &s) { if (first == -1) first = line; if (line != last && last != -1) while (last++ < line) chunk += '\n'; chunk += " "; chunk += s; last = line; } void dlua_chunk::set_chunk(const std::string &s) { chunk = s; } int dlua_chunk::check_op(CLua &interp, int err) { error = interp.error; return (err); } int dlua_chunk::load(CLua &interp) { if (!compiled.empty()) return check_op( interp, interp.loadbuffer(compiled.c_str(), compiled.length(), context.c_str()) ); if (empty()) { chunk.clear(); return (-1000); } int err = check_op( interp, interp.loadstring(chunk.c_str(), context.c_str()) ); if (err) return (err); std::ostringstream out; err = lua_dump(interp, dlua_compiled_chunk_writer, &out); if (err) { const char *e = lua_tostring(interp, -1); error = e? e : "Unknown error compiling chunk"; lua_pop(interp, 2); } compiled = out.str(); chunk.clear(); return (err); } int dlua_chunk::run(CLua &interp) { int err = load(interp); if (err) return (err); // callfn returns true on success, but we want to return 0 on success. return (check_op(interp, !interp.callfn(NULL, 0, 0))); } int dlua_chunk::load_call(CLua &interp, const char *fn) { int err = load(interp); if (err == -1000) return (0); if (err) return (err); return check_op(interp, !interp.callfn(fn, fn? 1 : 0, 0)); } std::string dlua_chunk::orig_error() const { rewrite_chunk_errors(error); return (error); } bool dlua_chunk::empty() const { return compiled.empty() && trimmed_string(chunk).empty(); } bool dlua_chunk::rewrite_chunk_errors(std::string &s) const { const std::string contextm = "[string \"" + context + "\"]:"; std::string::size_type dlwhere = s.find(contextm); if (dlwhere == std::string::npos) return (false); if (!dlwhere) { s = rewrite_chunk_prefix(s); return (true); } // Our chunk is mentioned, go back through and rewrite lines. std::vector lines = split_string("\n", s); std::string newmsg = lines[0]; bool wrote_prefix = false; for (int i = 2, size = lines.size() - 1; i < size; ++i) { const std::string &st = lines[i]; if (st.find(context) != std::string::npos) { if (!wrote_prefix) { newmsg = get_chunk_prefix(st) + ": " + newmsg; wrote_prefix = true; } else newmsg += "\n" + rewrite_chunk_prefix(st); } } s = newmsg; return (true); } std::string dlua_chunk::rewrite_chunk_prefix(const std::string &line, bool skip_body) const { std::string s = line; const std::string contextm = "[string \"" + context + "\"]:"; const std::string::size_type ps = s.find(contextm); if (ps == std::string::npos) return (s); const std::string::size_type lns = ps + contextm.length(); std::string::size_type pe = s.find(':', ps + contextm.length()); if (pe != std::string::npos) { const std::string line_num = s.substr(lns, pe - lns); const int lnum = atoi(line_num.c_str()); const std::string newlnum = make_stringf("%d", lnum + first - 1); s = s.substr(0, lns) + newlnum + s.substr(pe); pe = lns + newlnum.length(); } return s.substr(0, ps) + (file.empty()? context : file) + ":" + (skip_body? s.substr(lns, pe - lns) : s.substr(lns)); } std::string dlua_chunk::get_chunk_prefix(const std::string &sorig) const { return rewrite_chunk_prefix(sorig, true); } /////////////////////////////////////////////////////////////////////////// // Lua dungeon bindings (in the dgn table). #define MAP(ls, n, var) \ map_def *var = *(map_def **) luaL_checkudata(ls, n, MAP_METATABLE) #define DEVENT(ls, n, var) \ dgn_event *var = *(dgn_event **) luaL_checkudata(ls, n, DEVENT_METATABLE) #define MAPMARKER(ls, n, var) \ map_marker *var = *(map_marker **) luaL_checkudata(ls, n, MAPMARK_METATABLE) #define LUAFN(name) static int name(lua_State *ls) static dungeon_feature_type _get_lua_feature(lua_State *ls, int idx) { dungeon_feature_type feat = (dungeon_feature_type)0; if (lua_isnumber(ls, idx)) feat = (dungeon_feature_type)luaL_checkint(ls, idx); else if (lua_isstring(ls, idx)) feat = dungeon_feature_by_name(luaL_checkstring(ls, idx)); else luaL_argerror(ls, idx, "Feature must be a string or a feature index."); return feat; } static dungeon_feature_type _check_lua_feature(lua_State *ls, int idx) { const dungeon_feature_type f = _get_lua_feature(ls, idx); if (!f) luaL_argerror(ls, idx, "Invalid dungeon feature"); return (f); } static inline bool _lua_boolean(lua_State *ls, int ndx, bool defval) { return lua_isnone(ls, ndx)? defval : lua_toboolean(ls, ndx); } #define GETCOORD(c, p1, p2, boundfn) \ coord_def c; \ c.x = luaL_checkint(ls, p1); \ c.y = luaL_checkint(ls, p2); \ if (!boundfn(c)) \ luaL_error( \ ls, \ make_stringf("Point (%d,%d) is out of bounds", \ c.x, c.y).c_str()); \ else ; \ #define COORDS(c, p1, p2) \ GETCOORD(c, p1, p2, in_bounds) #define FEAT(f, pos) \ dungeon_feature_type f = _check_lua_feature(ls, pos) void dgn_reset_default_depth() { lc_default_depths.clear(); } std::string dgn_set_default_depth(const std::string &s) { std::vector frags = split_string(",", s); for (int i = 0, size = frags.size(); i < size; ++i) { try { lc_default_depths.push_back( level_range::parse(frags[i]) ); } catch (const std::string &error) { return (error); } } return (""); } static void dgn_add_depths(depth_ranges &drs, lua_State *ls, int s, int e) { for (int i = s; i <= e; ++i) { const char *depth = luaL_checkstring(ls, i); std::vector frags = split_string(",", depth); for (int j = 0, size = frags.size(); j < size; ++j) { try { drs.push_back( level_range::parse(frags[j]) ); } catch (const std::string &error) { luaL_error(ls, error.c_str()); } } } } static std::string dgn_depth_list_string(const depth_ranges &drs) { return (comma_separated_line(drs.begin(), drs.end(), ", ", ", ")); } static int dgn_depth_proc(lua_State *ls, depth_ranges &dr, int s) { if (lua_gettop(ls) < s) { PLUARET(string, dgn_depth_list_string(dr).c_str()); } if (lua_isnil(ls, s)) { dr.clear(); return (0); } dr.clear(); dgn_add_depths(dr, ls, s, lua_gettop(ls)); return (0); } static int dgn_default_depth(lua_State *ls) { return dgn_depth_proc(ls, lc_default_depths, 1); } static int dgn_depth(lua_State *ls) { MAP(ls, 1, map); return dgn_depth_proc(ls, map->depths, 2); } static int dgn_place(lua_State *ls) { MAP(ls, 1, map); if (lua_gettop(ls) > 1) { if (lua_isnil(ls, 2)) map->place.clear(); else { try { map->place = level_id::parse_level_id(luaL_checkstring(ls, 2)); } catch (const std::string &err) { luaL_error(ls, err.c_str()); } } } PLUARET(string, map->place.describe().c_str()); } static int dgn_tags(lua_State *ls) { MAP(ls, 1, map); if (lua_gettop(ls) > 1) { if (lua_isnil(ls, 2)) map->tags.clear(); else { const char *s = luaL_checkstring(ls, 2); map->tags += " " + trimmed_string(s) + " "; } } PLUARET(string, map->tags.c_str()); } static int dgn_tags_remove(lua_State *ls) { MAP(ls, 1, map); const int top = lua_gettop(ls); for (int i = 2; i <= top; ++i) { const std::string axee = luaL_checkstring(ls, i); const std::string::size_type pos = map->tags.find(axee); if (pos != std::string::npos) map->tags = map->tags.substr(0, pos) + map->tags.substr(pos + axee.length()); } PLUARET(string, map->tags.c_str()); } static const std::string level_flag_names[] = {"no_tele_control", "not_mappable", "no_magic_map", ""}; static int dgn_lflags(lua_State *ls) { MAP(ls, 1, map); try { map->level_flags = map_flags::parse(level_flag_names, luaL_checkstring(ls, 2)); } catch (const std::string &error) { luaL_argerror(ls, 2, error.c_str()); } return (0); } static int dgn_change_level_flags(lua_State *ls) { map_flags flags; try { flags = map_flags::parse(level_flag_names, luaL_checkstring(ls, 1)); } catch (const std::string &error) { luaL_argerror(ls, 2, error.c_str()); lua_pushboolean(ls, false); return (1); } bool silent = lua_toboolean(ls, 2); bool changed1 = set_level_flags(flags.flags_set, silent); bool changed2 = unset_level_flags(flags.flags_unset, silent); lua_pushboolean(ls, changed1 || changed2); return (1); } static const std::string branch_flag_names[] = {"no_tele_control", "not_mappable", "no_magic_map", ""}; static int dgn_bflags(lua_State *ls) { MAP(ls, 1, map); try { map->branch_flags = map_flags::parse(branch_flag_names, luaL_checkstring(ls, 2)); } catch (const std::string &error) { luaL_argerror(ls, 2, error.c_str()); } return (0); } static int dgn_change_branch_flags(lua_State *ls) { map_flags flags; try { flags = map_flags::parse(branch_flag_names, luaL_checkstring(ls, 1)); } catch (const std::string &error) { luaL_argerror(ls, 2, error.c_str()); lua_pushboolean(ls, false); return (1); } bool silent = lua_toboolean(ls, 2); bool changed1 = set_branch_flags(flags.flags_set, silent); bool changed2 = unset_branch_flags(flags.flags_unset, silent); lua_pushboolean(ls, changed1 || changed2); return (1); } static int dgn_set_random_mon_list(lua_State *ls) { // Don't complain if we're being called when the map is being loaded // and validated. if (you.level_type != LEVEL_PORTAL_VAULT && !(you.start_time == 0 && !you.entering_level && !Generating_Level)) { luaL_error(ls, "Can only be used in portal vaults."); return (0); } const int nargs = lua_gettop(ls); map_def *map = NULL; if (nargs > 2) { luaL_error(ls, "Too many arguments."); return (0); } else if (nargs == 0) { luaL_error(ls, "Too few arguments."); return (0); } else if (nargs == 2) { map_def **_map( clua_get_userdata(ls, MAP_METATABLE, 1)); map = *_map; } if (map) { if (map->orient != MAP_ENCOMPASS || map->place.is_valid() || !map->depths.empty()) { luaL_error(ls, "Can only be used in portal vaults."); return (0); } } int list_pos = (map != NULL) ? 2 : 1; mons_list mlist = _lua_get_mlist(ls, list_pos); if (mlist.size() == 0) { luaL_argerror(ls, list_pos, "Mon list is empty."); return (0); } if (mlist.size() > 1) { luaL_argerror(ls, list_pos, "Mon list must contain only one slot."); return (0); } const int num_mons = mlist.slot_size(0); if (num_mons == 0) { luaL_argerror(ls, list_pos, "Mon list is empty."); return (0); } std::vector mons; int num_lords = 0; for (int i = 0; i < num_mons; i++) { mons_spec mon = mlist.get_monster(0, i); // Pandemonium lords are pseudo-unique, so don't randomly generate // them. if (mon.mid == MONS_PANDEMONIUM_DEMON) { num_lords++; continue; } std::string name; if (mon.place.is_valid()) { if (mon.place.level_type == LEVEL_LABYRINTH || mon.place.level_type == LEVEL_PORTAL_VAULT) { std::string err; err = make_stringf("mon #%d: Can't use Lab or Portal as a " "monster place.", i + 1); luaL_argerror(ls, list_pos, err.c_str()); return(0); } name = mon.place.describe(); } else { if (mon.mid == RANDOM_MONSTER || mon.monbase == RANDOM_MONSTER) { std::string err; err = make_stringf("mon #%d: can't use random monster in " "list specifying random monsters", i + 1); luaL_argerror(ls, list_pos, err.c_str()); return(0); } if (mon.mid == -1) mon.mid = MONS_PROGRAM_BUG; name = mons_type_name(mon.mid, DESC_PLAIN); } mons.push_back(mon); if (mon.number != 0) mprf(MSGCH_ERROR, "dgn.set_random_mon_list() : number for %s " "being discarded.", name.c_str()); if (mon.band) mprf(MSGCH_ERROR, "dgn.set_random_mon_list() : band request for " "%s being ignored.", name.c_str()); if (mon.colour != BLACK) mprf(MSGCH_ERROR, "dgn.set_random_mon_list() : colour for " "%s being ignored.", name.c_str()); if (mon.items.size() > 0) mprf(MSGCH_ERROR, "dgn.set_random_mon_list() : items for " "%s being ignored.", name.c_str()); } // for (int i = 0; i < num_mons; i++) if (mons.size() == 0 && num_lords > 0) { luaL_argerror(ls, list_pos, "Mon list contains only pandemonium lords."); return (0); } if (map) map->random_mons = mons; else set_vault_mon_list(mons); return (0); } static int dgn_chance(lua_State *ls) { MAP(ls, 1, map); if (!lua_isnil(ls, 2) && !lua_isnil(ls, 3)) { const int chance_priority = luaL_checkint(ls, 2); const int chance = luaL_checkint(ls, 3); if (chance < 0 || chance > CHANCE_ROLL) luaL_argerror(ls, 2, make_stringf("Chance must be in the range [0,%d]", CHANCE_ROLL).c_str()); map->chance_priority = chance_priority; map->chance = chance; } PLUARET(number, map->chance); } static int dgn_weight(lua_State *ls) { MAP(ls, 1, map); if (!lua_isnil(ls, 2)) map->weight = luaL_checkint(ls, 2); PLUARET(number, map->weight); } static int dgn_orient(lua_State *ls) { MAP(ls, 1, map); if (lua_gettop(ls) > 1) { if (lua_isnil(ls, 2)) map->orient = MAP_NONE; else { const std::string orient = luaL_checkstring(ls, 2); bool found = false; // Note: Empty string is intentionally mapped to MAP_NONE! for (int i = MAP_NONE; i < MAP_NUM_SECTION_TYPES; ++i) { if (orient == map_section_name(i)) { map->orient = static_cast(i); found = true; break; } } if (!found) luaL_error(ls, ("Bad orient: " + orient).c_str()); } } PLUARET(string, map_section_name(map->orient)); } static int dgn_shuffle(lua_State *ls) { MAP(ls, 1, map); if (lua_gettop(ls) == 1) return dlua_stringtable(ls, map->get_shuffle_strings()); for (int i = 2, size = lua_gettop(ls); i <= size; ++i) { if (lua_isnil(ls, i)) map->map.clear_shuffles(); else { std::string err = map->map.add_shuffle(luaL_checkstring(ls, i)); if (!err.empty()) luaL_error(ls, err.c_str()); } } return (0); } static int dgn_shuffle_remove(lua_State *ls) { MAP(ls, 1, map); for (int i = 2, size = lua_gettop(ls); i <= size; ++i) map->map.remove_shuffle(luaL_checkstring(ls, i)); return (0); } static int dgn_map_add_transform( lua_State *ls, std::string (map_lines::*add)(const std::string &s), void (map_lines::*erase)()) { MAP(ls, 1, map); for (int i = 2, size = lua_gettop(ls); i <= size; ++i) { if (lua_isnil(ls, i)) (map->map.*erase)(); else { std::string err = (map->map.*add)(luaL_checkstring(ls, i)); if (!err.empty()) luaL_error(ls, err.c_str()); } } return (0); } static int dgn_subst(lua_State *ls) { return dgn_map_add_transform(ls, &map_lines::add_subst, &map_lines::clear_substs); } static int dgn_nsubst(lua_State *ls) { return dgn_map_add_transform(ls, &map_lines::add_nsubst, &map_lines::clear_nsubsts); } static int dgn_colour(lua_State *ls) { return dgn_map_add_transform(ls, &map_lines::add_colour, &map_lines::clear_colours); } static int dgn_subst_remove(lua_State *ls) { MAP(ls, 1, map); for (int i = 2, size = lua_gettop(ls); i <= size; ++i) map->map.remove_subst(luaL_checkstring(ls, i)); return (0); } static int dgn_map(lua_State *ls) { MAP(ls, 1, map); if (lua_gettop(ls) == 1) return dlua_stringtable(ls, map->map.get_lines()); if (lua_isnil(ls, 2)) { map->map.clear(); return (0); } // map(, x, y) = glyph at (x,y), subject to map being // resolved and normalised. if (lua_gettop(ls) == 3 && lua_isnumber(ls, 2) && lua_isnumber(ls, 3)) { const int gly = map->map.glyph(luaL_checkint(ls, 2), luaL_checkint(ls, 3)); char buf[2] = ""; buf[0] = gly; lua_pushstring(ls, buf); return (1); } if (lua_isstring(ls, 2)) { map->map.add_line(luaL_checkstring(ls, 2)); return (0); } std::vector &lines = map->map.get_lines(); int which_line = luaL_checkint(ls, 2); if (which_line < 0) which_line += (int) lines.size(); if (lua_gettop(ls) == 2) { if (which_line < 0 || which_line >= (int) lines.size()) { luaL_error(ls, lines.empty()? "Map is empty" : make_stringf("Line %d out of range (0-%u)", which_line, lines.size() - 1).c_str()); } PLUARET(string, lines[which_line].c_str()); } if (lua_isnil(ls, 3)) { if (which_line >= 0 && which_line < (int) lines.size()) { lines.erase(lines.begin() + which_line); PLUARET(boolean, true); } return (0); } const std::string newline = luaL_checkstring(ls, 3); if (which_line < 0) luaL_error(ls, make_stringf("Index %d out of range", which_line).c_str()); if (which_line < (int) lines.size()) { lines[which_line] = newline; return (0); } lines.reserve(which_line + 1); lines.resize(which_line + 1, ""); lines[which_line] = newline; return (0); } static int dgn_mons(lua_State *ls) { MAP(ls, 1, map); if (lua_gettop(ls) == 1) return (0); if (lua_isnil(ls, 2)) { map->mons.clear(); return (0); } if (lua_isstring(ls, 2)) { std::string err = map->mons.add_mons(luaL_checkstring(ls, 2)); if (!err.empty()) luaL_error(ls, err.c_str()); return (0); } const int index = luaL_checkint(ls, 2); std::string err = map->mons.set_mons(index, luaL_checkstring(ls, 3)); if (!err.empty()) luaL_error(ls, err.c_str()); return (0); } static int dgn_item(lua_State *ls) { MAP(ls, 1, map); if (lua_gettop(ls) == 1) return (0); if (lua_isnil(ls, 2)) { map->items.clear(); return (0); } if (lua_isstring(ls, 2)) { std::string err = map->items.add_item(luaL_checkstring(ls, 2)); if (!err.empty()) luaL_error(ls, err.c_str()); return (0); } const int index = luaL_checkint(ls, 2); std::string err = map->items.set_item(index, luaL_checkstring(ls, 3)); if (!err.empty()) luaL_error(ls, err.c_str()); return (0); } static int dgn_lua_marker(lua_State *ls) { MAP(ls, 1, map); if (lua_gettop(ls) != 3 || !lua_isstring(ls, 2) || (!lua_isfunction(ls, 3) && !lua_istable(ls, 3))) { luaL_error(ls, "Expected marker key and marker function/table."); } CLua &lvm(CLua::get_vm(ls)); std::string key = lua_tostring(ls, 2); lua_datum function(lvm, 3, false); const std::string err = map->map.add_lua_marker(key, function); if (!err.empty()) luaL_error(ls, err.c_str()); return (0); } static int dgn_marker(lua_State *ls) { MAP(ls, 1, map); if (lua_gettop(ls) == 1) return (0); if (lua_isnil(ls, 2)) { map->map.clear_markers(); return (0); } if (lua_isstring(ls, 2)) { std::string err = map->map.add_feature_marker(luaL_checkstring(ls, 2)); if (!err.empty()) luaL_error(ls, err.c_str()); } return (0); } static int dgn_kfeat(lua_State *ls) { MAP(ls, 1, map); std::string err = map->add_key_feat(luaL_checkstring(ls, 2)); if (!err.empty()) luaL_error(ls, err.c_str()); return (0); } static int dgn_kmons(lua_State *ls) { MAP(ls, 1, map); std::string err = map->add_key_mons(luaL_checkstring(ls, 2)); if (!err.empty()) luaL_error(ls, err.c_str()); return (0); } static int dgn_kitem(lua_State *ls) { MAP(ls, 1, map); std::string err = map->add_key_item(luaL_checkstring(ls, 2)); if (!err.empty()) luaL_error(ls, err.c_str()); return (0); } static int dgn_kmask(lua_State *ls) { MAP(ls, 1, map); std::string err = map->add_key_mask(luaL_checkstring(ls, 2)); if (!err.empty()) luaL_error(ls, err.c_str()); return (0); } static int dgn_map_size(lua_State *ls) { MAP(ls, 1, map); lua_pushnumber(ls, map->map.width()); lua_pushnumber(ls, map->map.height()); return (2); } static int dgn_name(lua_State *ls) { MAP(ls, 1, map); PLUARET(string, map->name.c_str()); } static int dgn_welcome(lua_State *ls) { MAP(ls, 1, map); map->welcome_messages.push_back(luaL_checkstring(ls, 2)); return (0); } static int dgn_grid(lua_State *ls) { GETCOORD(c, 1, 2, map_bounds); if (!lua_isnone(ls, 3)) { const dungeon_feature_type feat = _get_lua_feature(ls, 3); if (feat) grd(c) = feat; } PLUARET(number, grd(c)); } LUARET1(_dgn_is_wall, boolean, grid_is_wall(static_cast(luaL_checkint(ls, 1)))) static int dgn_max_bounds(lua_State *ls) { lua_pushnumber(ls, GXM); lua_pushnumber(ls, GYM); return (2); } typedef flood_find map_flood_finder; static int dgn_map_pathfind(lua_State *ls, int minargs, bool (map_flood_finder::*f)(const coord_def &)) { MAP(ls, 1, map); const int nargs = lua_gettop(ls); if (nargs < minargs) return luaL_error (ls, make_stringf("Not enough points to test connectedness " "(need at least %d)", minargs / 2).c_str()); map_def::map_feature_finder feat_finder(*map); map_def::map_bounds_check bounds_checker(*map); map_flood_finder finder(feat_finder, bounds_checker); for (int i = 4; i < nargs; i += 2) { const coord_def c(luaL_checkint(ls, i), luaL_checkint(ls, i + 1)); finder.add_point(c); } const coord_def pos(luaL_checkint(ls, 2), luaL_checkint(ls, 3)); PLUARET(boolean, (finder.*f)(pos)); } static int dgn_points_connected(lua_State *ls) { return dgn_map_pathfind(ls, 5, &map_flood_finder::points_connected_from); } static int dgn_any_point_connected(lua_State *ls) { return dgn_map_pathfind(ls, 5, &map_flood_finder::any_point_connected_from); } static int dgn_has_exit_from(lua_State *ls) { return dgn_map_pathfind(ls, 3, &map_flood_finder::has_exit_from); } static void dlua_push_coord(lua_State *ls, const coord_def &c) { lua_pushnumber(ls, c.x); lua_pushnumber(ls, c.y); } static int dgn_gly_point(lua_State *ls) { MAP(ls, 1, map); coord_def c = map->find_first_glyph(*luaL_checkstring(ls, 2)); if (c.x != -1 && c.y != -1) { dlua_push_coord(ls, c); return (2); } return (0); } static int dgn_gly_points(lua_State *ls) { MAP(ls, 1, map); std::vector cs = map->find_glyph(*luaL_checkstring(ls, 2)); for (int i = 0, size = cs.size(); i < size; ++i) dlua_push_coord(ls, cs[i]); return (cs.size() * 2); } static int dgn_original_map(lua_State *ls) { MAP(ls, 1, map); if (map->original) clua_push_map(ls, map->original); else lua_pushnil(ls); return (1); } static int dgn_load_des_file(lua_State *ls) { const std::string &file = luaL_checkstring(ls, 1); if (!file.empty()) read_map(file); return (0); } static int dgn_lfloorcol(lua_State *ls) { MAP(ls, 1, map); if (!lua_isnone(ls, 2)) { const char *s = luaL_checkstring(ls, 2); int colour = str_to_colour(s); if (colour < 0 || colour == BLACK) { std::string error; if (colour == BLACK) { error = "Can't set floor to black."; } else { error = "No such colour as '"; error += s; error += "'"; } luaL_argerror(ls, 2, error.c_str()); return (0); } map->floor_colour = (unsigned char) colour; } PLUARET(string, colour_to_str(map->floor_colour).c_str()); } static int dgn_lrockcol(lua_State *ls) { MAP(ls, 1, map); if (!lua_isnone(ls, 2)) { const char *s = luaL_checkstring(ls, 2); int colour = str_to_colour(s); if (colour < 0 || colour == BLACK) { std::string error; if (colour == BLACK) { error = "Can't set rock to black."; } else { error = "No such colour as '"; error += s; error += "'"; } luaL_argerror(ls, 2, error.c_str()); return (0); } map->rock_colour = (unsigned char) colour; } PLUARET(string, colour_to_str(map->rock_colour).c_str()); } static int dgn_get_floor_colour(lua_State *ls) { PLUARET(string, colour_to_str(env.floor_colour).c_str()); } static int dgn_get_rock_colour(lua_State *ls) { PLUARET(string, colour_to_str(env.rock_colour).c_str()); } static int _lua_colour(lua_State *ls, int ndx, int forbidden_colour = -1) { if (lua_isnumber(ls, ndx)) return lua_tointeger(ls, ndx); else if (const char *s = luaL_checkstring(ls, ndx)) { const int colour = str_to_colour(s); if (colour < 0 || colour == forbidden_colour) { std::string error; if (colour == forbidden_colour) error = std::string("Can't set floor to ") + s; else error = std::string("Unknown colour: '") + s + "'"; return luaL_argerror(ls, 1, error.c_str()); } return (colour); } return luaL_argerror(ls, ndx, "Expected colour name or number"); } static int dgn_change_floor_colour(lua_State *ls) { const int colour = _lua_colour(ls, 1, BLACK); const bool update_now = _lua_boolean(ls, 2, false); env.floor_colour = (unsigned char) colour; if (crawl_state.need_save && update_now) viewwindow(true, false); return (0); } static int dgn_change_rock_colour(lua_State *ls) { const int colour = _lua_colour(ls, 1, BLACK); const bool update_now = _lua_boolean(ls, 2, false); env.rock_colour = (unsigned char) colour; if (crawl_state.need_save && update_now) viewwindow(true, false); return (0); } static int dgn_colour_at(lua_State *ls) { COORDS(c, 1, 2); if (!lua_isnone(ls, 3)) env.grid_colours(c) = _lua_colour(ls, 3); PLUARET(string, colour_to_str(env.grid_colours(c)).c_str()); } const char *dngn_feature_names[] = { "unseen", "closed_door", "secret_door", "wax_wall", "metal_wall", "green_crystal_wall", "rock_wall", "stone_wall", "permarock_wall", "clear_rock_wall", "clear_stone_wall", "clear_permarock_wall", "orcish_idol", "", "", "", "", "", "", "", "", "granite_statue", "statue_reserved_1", "statue_reserved_2", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "lava", "deep_water", "", "", "shallow_water", "water_stuck", "floor", "floor_special", "floor_reserved", "exit_hell", "enter_hell", "open_door", "", "", "trap_mechanical", "trap_magical", "trap_natural", "undiscovered_trap", "", "enter_shop", "enter_labyrinth", "stone_stairs_down_i", "stone_stairs_down_ii", "stone_stairs_down_iii", "escape_hatch_down", "stone_stairs_up_i", "stone_stairs_up_ii", "stone_stairs_up_iii", "escape_hatch_up", "", "", "enter_dis", "enter_gehenna", "enter_cocytus", "enter_tartarus", "enter_abyss", "exit_abyss", "stone_arch", "enter_pandemonium", "exit_pandemonium", "transit_pandemonium", "", "", "", "builder_special_wall", "builder_special_floor", "", "", "", "enter_orcish_mines", "enter_hive", "enter_lair", "enter_slime_pits", "enter_vaults", "enter_crypt", "enter_hall_of_blades", "enter_zot", "enter_temple", "enter_snake_pit", "enter_elven_halls", "enter_tomb", "enter_swamp", "enter_shoals", "enter_reserved_2", "enter_reserved_3", "enter_reserved_4", "", "", "", "return_from_orcish_mines", "return_from_hive", "return_from_lair", "return_from_slime_pits", "return_from_vaults", "return_from_crypt", "return_from_hall_of_blades", "return_from_zot", "return_from_temple", "return_from_snake_pit", "return_from_elven_halls", "return_from_tomb", "return_from_swamp", "return_from_shoals", "return_reserved_2", "return_reserved_3", "return_reserved_4", "", "", "", "", "", "", "", "", "", "", "", "", "", "enter_portal_vault", "exit_portal_vault", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "altar_zin", "altar_shining_one", "altar_kikubaaqudgha", "altar_yredelemnul", "altar_xom", "altar_vehumet", "altar_okawaru", "altar_makhleb", "altar_sif_muna", "altar_trog", "altar_nemelex_xobeh", "altar_elyvilon", "altar_lugonu", "altar_beogh", "", "", "", "", "", "", "fountain_blue", "fountain_sparkling", "fountain_blood", "dry_fountain_blue", "dry_fountain_sparkling", "dry_fountain_blood", "permadry_fountain", "abandoned_shop" }; dungeon_feature_type dungeon_feature_by_name(const std::string &name) { COMPILE_CHECK(ARRAYSZ(dngn_feature_names) == NUM_REAL_FEATURES, c1); if (name.empty()) return (DNGN_UNSEEN); for (unsigned i = 0; i < ARRAYSZ(dngn_feature_names); ++i) if (dngn_feature_names[i] == name) return static_cast(i); return (DNGN_UNSEEN); } std::vector dungeon_feature_matches(const std::string &name) { std::vector matches; COMPILE_CHECK(ARRAYSZ(dngn_feature_names) == NUM_REAL_FEATURES, c1); if (name.empty()) return (matches); for (unsigned i = 0; i < ARRAYSZ(dngn_feature_names); ++i) if (strstr(dngn_feature_names[i], name.c_str())) matches.push_back(dngn_feature_names[i]); return (matches); } const char *dungeon_feature_name(dungeon_feature_type rfeat) { const unsigned feat = rfeat; if (feat >= ARRAYSZ(dngn_feature_names)) return (NULL); return dngn_feature_names[feat]; } static int dgn_feature_number(lua_State *ls) { const std::string &name = luaL_checkstring(ls, 1); PLUARET(number, dungeon_feature_by_name(name)); } static int dgn_feature_name(lua_State *ls) { const unsigned feat = luaL_checkint(ls, 1); PLUARET(string, dungeon_feature_name(static_cast(feat))); } static const char *dgn_event_type_names[] = { "none", "turn", "mons_move", "player_move", "leave_level", "entering_level", "entered_level", "player_los", "player_climb", "monster_dies", "item_pickup", "item_moved", "feat_change", }; static dgn_event_type dgn_event_type_by_name(const std::string &name) { for (unsigned i = 0; i < ARRAYSZ(dgn_event_type_names); ++i) if (dgn_event_type_names[i] == name) return static_cast(i? 1 << (i - 1) : 0); return (DET_NONE); } static const char *dgn_event_type_name(unsigned evmask) { if (evmask == 0) return (dgn_event_type_names[0]); for (unsigned i = 1; i < ARRAYSZ(dgn_event_type_names); ++i) if (evmask & (1 << (i - 1))) return (dgn_event_type_names[i]); return (dgn_event_type_names[0]); } static void dgn_push_event_type(lua_State *ls, int n) { if (lua_isstring(ls, n)) lua_pushnumber(ls, dgn_event_type_by_name(lua_tostring(ls, n))); else if (lua_isnumber(ls, n)) lua_pushstring(ls, dgn_event_type_name(luaL_checkint(ls, n))); else lua_pushnil(ls); } static int dgn_dgn_event(lua_State *ls) { const int start = lua_isuserdata(ls, 1)? 2 : 1; int retvals = 0; for (int i = start, nargs = lua_gettop(ls); i <= nargs; ++i) { dgn_push_event_type(ls, i); retvals++; } return (retvals); } static int dgn_register_listener(lua_State *ls) { unsigned mask = luaL_checkint(ls, 1); MAPMARKER(ls, 2, mark); map_lua_marker *listener = dynamic_cast(mark); coord_def pos; // Was a position supplied? if (lua_gettop(ls) == 4) { pos.x = luaL_checkint(ls, 3); pos.y = luaL_checkint(ls, 4); } dungeon_events.register_listener(mask, listener, pos); return (0); } static int dgn_remove_listener(lua_State *ls) { MAPMARKER(ls, 1, mark); map_lua_marker *listener = dynamic_cast(mark); coord_def pos; // Was a position supplied? if (lua_gettop(ls) == 3) { pos.x = luaL_checkint(ls, 2); pos.y = luaL_checkint(ls, 3); } dungeon_events.remove_listener(listener, pos); return (0); } static int dgn_remove_marker(lua_State *ls) { MAPMARKER(ls, 1, mark); env.markers.remove(mark); return (0); } static int dgn_num_matching_markers(lua_State *ls) { const char* key = luaL_checkstring(ls, 1); const char* val_ptr = lua_tostring(ls, 2); const char* val; if (val_ptr == NULL) val = ""; else val = val_ptr; std::vector markers = env.markers.get_all(key, val); PLUARET(number, markers.size()); } static int dgn_feature_desc(lua_State *ls) { const dungeon_feature_type feat = static_cast(luaL_checkint(ls, 1)); const description_level_type dtype = lua_isnumber(ls, 2)? static_cast(luaL_checkint(ls, 2)) : description_type_by_name(lua_tostring(ls, 2)); const bool need_stop = lua_isboolean(ls, 3)? lua_toboolean(ls, 3) : false; const std::string s = feature_description(feat, NUM_TRAPS, false, dtype, need_stop); lua_pushstring(ls, s.c_str()); return (1); } static int dgn_feature_desc_at(lua_State *ls) { const description_level_type dtype = lua_isnumber(ls, 3)? static_cast(luaL_checkint(ls, 3)) : description_type_by_name(lua_tostring(ls, 3)); const bool need_stop = lua_isboolean(ls, 4)? lua_toboolean(ls, 4) : false; const std::string s = feature_description(coord_def(luaL_checkint(ls, 1), luaL_checkint(ls, 2)), false, dtype, need_stop); lua_pushstring(ls, s.c_str()); return (1); } static int dgn_set_feature_desc_short(lua_State *ls) { const std::string base_name = luaL_checkstring(ls, 1); const std::string desc = luaL_checkstring(ls, 2); if (base_name.empty()) { luaL_argerror(ls, 1, "Base name can't be empty"); return (0); } set_feature_desc_short(base_name, desc); return (0); } static int dgn_set_feature_desc_long(lua_State *ls) { const std::string raw_name = luaL_checkstring(ls, 1); const std::string desc = luaL_checkstring(ls, 2); if (raw_name.empty()) { luaL_argerror(ls, 1, "Raw name can't be empty"); return (0); } set_feature_desc_long(raw_name, desc); return (0); } static int dgn_terrain_changed(lua_State *ls) { dungeon_feature_type type = DNGN_UNSEEN; if (lua_isnumber(ls, 3)) type = static_cast(luaL_checkint(ls, 3)); else if (lua_isstring(ls, 3)) type = dungeon_feature_by_name(lua_tostring(ls, 3)); const bool affect_player = lua_isboolean(ls, 4)? lua_toboolean(ls, 4) : true; const bool preserve_features = lua_isboolean(ls, 5)? lua_toboolean(ls, 5) : true; const bool preserve_items = lua_isboolean(ls, 6)? lua_toboolean(ls, 6) : true; dungeon_terrain_changed( coord_def( luaL_checkint(ls, 1), luaL_checkint(ls, 2) ), type, affect_player, preserve_features, preserve_items ); return (0); } static int dgn_item_from_index(lua_State *ls) { const int index = luaL_checkint(ls, 1); item_def *item = &mitm[index]; if (is_valid_item(*item)) lua_pushlightuserdata(ls, item); else lua_pushnil(ls); return (1); } static int dgn_mons_from_index(lua_State *ls) { const int index = luaL_checkint(ls, 1); monsters *mons = &menv[index]; if (mons->type != -1) push_monster(ls, mons); else lua_pushnil(ls); return (1); } static int dgn_mons_at(lua_State *ls) { COORDS(c, 1, 2); monsters *mon = monster_at(c); if (mon && mon->alive()) push_monster(ls, mon); else lua_pushnil(ls); return (1); } static int dgn_items_at(lua_State *ls) { COORDS(c, 1, 2); lua_push_items(ls, env.igrid(c)); return (1); } static int lua_dgn_set_lt_callback(lua_State *ls) { const char *level_type = luaL_checkstring(ls, 1); if (level_type == NULL || strlen(level_type) == 0) return (0); const char *callback_name = luaL_checkstring(ls, 2); if (callback_name == NULL || strlen(callback_name) == 0) return (0); dgn_set_lt_callback(level_type, callback_name); return (0); } static int dgn_fixup_stairs(lua_State *ls) { const dungeon_feature_type up_feat = dungeon_feature_by_name(luaL_checkstring(ls, 1)); const dungeon_feature_type down_feat = dungeon_feature_by_name(luaL_checkstring(ls, 2)); if (up_feat == DNGN_UNSEEN && down_feat == DNGN_UNSEEN) return(0); for (int y = 0; y < GYM; ++y) { for (int x = 0; x < GXM; ++x) { const dungeon_feature_type feat = grd[x][y]; if (grid_is_stone_stair(feat) || grid_is_escape_hatch(feat)) { dungeon_feature_type new_feat = DNGN_UNSEEN; if (grid_stair_direction(feat) == CMD_GO_DOWNSTAIRS) new_feat = down_feat; else new_feat = up_feat; if (new_feat != DNGN_UNSEEN) { grd[x][y] = new_feat; env.markers.add( new map_feature_marker( coord_def(x, y), new_feat)); } } } } return (0); } #ifdef USE_TILE static unsigned int _get_tile_idx(lua_State *ls, int arg) { if (!lua_isstring(ls, arg)) { luaL_argerror(ls, arg, "Expected string for tile name"); return 0; } const char *tile_name = luaL_checkstring(ls, arg); unsigned int idx; if (!tile_dngn_index(tile_name, idx)) { std::string error = "Couldn't find tile '"; error += tile_name; error += "'"; luaL_argerror(ls, arg, error.c_str()); return 0; } return idx; } #endif static int dgn_floor_halo(lua_State *ls) { std::string error = ""; const char *s1 = luaL_checkstring(ls, 1); const dungeon_feature_type target = dungeon_feature_by_name(s1); if (target == DNGN_UNSEEN) { error += "No such dungeon feature as '"; error += s1; error += "'. "; } const char *s2 = luaL_checkstring(ls, 2); short colour = str_to_colour(s2); if (colour == -1) { error += "No such colour as '"; error += s2; error += "'."; } else if (colour == BLACK) error += "Can't set floor colour to black."; if (!error.empty()) { luaL_argerror(ls, 2, error.c_str()); return(0); } for (int y = 0; y < GYM; ++y) for (int x = 0; x < GXM; ++x) { const dungeon_feature_type feat = grd[x][y]; if (feat == target) { for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { if (!map_bounds(x+i, y+j)) continue; const dungeon_feature_type feat2 = grd[x+i][y+j]; if (feat2 == DNGN_FLOOR || feat2 == DNGN_UNDISCOVERED_TRAP) { env.grid_colours[x+i][y+j] = colour; } } } } #ifdef USE_TILE unsigned int tile = _get_tile_idx(ls, 3); if (!tile) return (0); if (tile_dngn_count(tile) != 9) { error += "'"; error += luaL_checkstring(ls, 3); error += "' is not a valid halo tile. It has "; error += tile_dngn_count(tile); error += " variations, but needs exactly 9."; luaL_argerror(ls, 3, error.c_str()); return (0); } tile_floor_halo(target, tile); #endif return (0); } #define SQRT_2 1.41421356237309504880 static int dgn_random_walk(lua_State *ls) { const int x = luaL_checkint(ls, 1); const int y = luaL_checkint(ls, 2); const int dist = luaL_checkint(ls, 3); // Fourth param being true means that we can move past // statues. const dungeon_feature_type minmove = lua_isnil(ls, 4) ? DNGN_MINMOVE : DNGN_ORCISH_IDOL; if (!in_bounds(x, y)) { char buf[80]; sprintf(buf, "Point (%d,%d) isn't in bounds.", x, y); luaL_argerror(ls, 1, buf); return (0); } if (dist < 1) { luaL_argerror(ls, 3, "Distance must be positive."); return (0); } float dist_left = dist; // Allow movement to all 8 adjacent squares if distance is 1 // (needed since diagonal moves are distance sqrt(2)) if (dist == 1) dist_left = (float)SQRT_2; int moves_left = dist; coord_def pos(x, y); while (dist_left >= 1.0 && moves_left-- > 0) { int okay_dirs = 0; int dir = -1; for (int j = 0; j < 8; j++) { const coord_def new_pos = pos + Compass[j]; const float move_dist = (j % 2 == 0) ? 1.0 : SQRT_2; if (in_bounds(new_pos) && grd(new_pos) >= minmove && move_dist <= dist_left) { if (one_chance_in(++okay_dirs)) dir = j; } } if (okay_dirs == 0) break; if (one_chance_in(++okay_dirs)) continue; pos += Compass[dir]; dist_left -= (dir % 2 == 0) ? 1.0 : SQRT_2; } dlua_push_coord(ls, pos); return (2); } static cloud_type dgn_cloud_name_to_type(std::string name) { lowercase(name); if (name == "random") return (CLOUD_RANDOM); else if (name == "debugging") return (CLOUD_DEBUGGING); for (int i = CLOUD_NONE; i < CLOUD_RANDOM; i++) if (cloud_name(static_cast(i)) == name) return static_cast(i); return (CLOUD_NONE); } static kill_category dgn_kill_name_to_category(std::string name) { if (name.empty()) return KC_OTHER; lowercase(name); if (name == "you") return KC_YOU; else if (name == "friendly") return KC_FRIENDLY; else if (name == "other") return KC_OTHER; else return KC_NCATEGORIES; } static int lua_cloud_pow_min; static int lua_cloud_pow_max; static int lua_cloud_pow_rolls; static int make_a_lua_cloud(coord_def where, int garbage, int spread_rate, cloud_type ctype, kill_category whose, killer_type killer) { UNUSED( garbage ); const int pow = random_range(lua_cloud_pow_min, lua_cloud_pow_max, lua_cloud_pow_rolls); place_cloud( ctype, where, pow, whose, killer, spread_rate ); return 1; } static int dgn_apply_area_cloud(lua_State *ls) { const int x = luaL_checkint(ls, 1); const int y = luaL_checkint(ls, 2); lua_cloud_pow_min = luaL_checkint(ls, 3); lua_cloud_pow_max = luaL_checkint(ls, 4); lua_cloud_pow_rolls = luaL_checkint(ls, 5); const int size = luaL_checkint(ls, 6); const cloud_type ctype = dgn_cloud_name_to_type(luaL_checkstring(ls, 7)); const char* kname = lua_isstring(ls, 8) ? luaL_checkstring(ls, 8) : ""; const kill_category kc = dgn_kill_name_to_category(kname); const int spread_rate = lua_isnumber(ls, 9) ? luaL_checkint(ls, 9) : -1; if (!in_bounds(x, y)) { char buf[80]; sprintf(buf, "Point (%d,%d) isn't in bounds.", x, y); luaL_argerror(ls, 1, buf); return (0); } if (lua_cloud_pow_min < 0) { luaL_argerror(ls, 4, "pow_min must be non-negative"); return (0); } if (lua_cloud_pow_max < lua_cloud_pow_min) { luaL_argerror(ls, 5, "pow_max must not be less than pow_min"); return (0); } if (lua_cloud_pow_max == 0) { luaL_argerror(ls, 5, "pow_max must be positive"); return (0); } if (lua_cloud_pow_rolls <= 0) { luaL_argerror(ls, 6, "pow_rolls must be positive"); return (0); } if (size < 1) { luaL_argerror(ls, 4, "size must be positive."); return (0); } if (ctype == CLOUD_NONE) { std::string error = "Invalid cloud type '"; error += luaL_checkstring(ls, 7); error += "'"; luaL_argerror(ls, 7, error.c_str()); return (0); } if (kc == KC_NCATEGORIES) { std::string error = "Invalid kill category '"; error += kname; error += "'"; luaL_argerror(ls, 8, error.c_str()); return (0); } if (spread_rate < -1 || spread_rate > 100) { luaL_argerror(ls, 9, "spread_rate must be between -1 and 100," "inclusive"); return (0); } apply_area_cloud(make_a_lua_cloud, coord_def(x, y), 0, size, ctype, kc, cloud_struct::whose_to_killer(kc), spread_rate); return (0); } static void _clamp_to_bounds(int &x, int &y, bool edge_ok = false) { const int edge_offset = edge_ok ? 0 : 1; x = std::min(std::max(x, X_BOUND_1 + edge_offset), X_BOUND_2 - edge_offset); y = std::min(std::max(y, Y_BOUND_1 + edge_offset), Y_BOUND_2 - edge_offset); } static int dgn_fill_area(lua_State *ls) { int x1 = luaL_checkint(ls, 1); int y1 = luaL_checkint(ls, 2); int x2 = luaL_checkint(ls, 3); int y2 = luaL_checkint(ls, 4); dungeon_feature_type feat = _check_lua_feature(ls, 5); _clamp_to_bounds(x1, y1); _clamp_to_bounds(x2, y2); if (x2 < x1) std::swap(x1, x2); if (y2 < y1) std::swap(y1, y2); for (int y = y1; y <= y2; y++) for (int x = x1; x <= x2; x++) grd[x][y] = feat; return 0; } static int dgn_replace_area(lua_State *ls) { int x1 = luaL_checkint(ls, 1); int y1 = luaL_checkint(ls, 2); int x2 = luaL_checkint(ls, 3); int y2 = luaL_checkint(ls, 4); dungeon_feature_type search = _check_lua_feature(ls, 5); dungeon_feature_type replace = _check_lua_feature(ls, 6); // gracefully handle out of bound areas by truncating them. _clamp_to_bounds(x1, y1); _clamp_to_bounds(x2, y2); if (x2 < x1) std::swap(x1, x2); if (y2 < y1) std::swap(y1, y2); for (int y = y1; y <= y2; y++) for (int x = x1; x <= x2; x++) if (grd[x][y] == search) grd[x][y] = replace; return 0; } static int dgn_octa_room(lua_State *ls) { int x1 = luaL_checkint(ls, 1); int y1 = luaL_checkint(ls, 2); int x2 = luaL_checkint(ls, 3); int y2 = luaL_checkint(ls, 4); int oblique = luaL_checkint(ls, 5); dungeon_feature_type fill = _check_lua_feature(ls, 6); spec_room sr; sr.tl.x = x1; sr.br.x = x2; sr.tl.y = y1; sr.br.y = y2; octa_room(sr, oblique, fill); return 0; } static int dgn_make_pillars(lua_State *ls) { int center_x = luaL_checkint(ls, 1); int center_y = luaL_checkint(ls, 2); int num = luaL_checkint(ls, 3); int scale_x = luaL_checkint(ls, 4); int big_radius = luaL_checkint(ls, 5); int pillar_radius = luaL_checkint(ls, 6); dungeon_feature_type fill = _check_lua_feature(ls, 8); // [enne] The underscore is for DJGPP's brain damage. const float _PI = 3.14159265f; for (int n = 0; n < num; n++) { float angle = n * 2 * _PI / (float)num; int x = (int)std::floor(std::cos(angle) * big_radius * scale_x + 0.5f); int y = (int)std::floor(std::sin(angle) * big_radius + 0.5f); lua_pushvalue(ls, 7); lua_pushnumber(ls, center_x + x); lua_pushnumber(ls, center_y + y); lua_pushnumber(ls, pillar_radius); lua_pushnumber(ls, fill); lua_call(ls, 4, 0); } return 0; } static int dgn_make_square(lua_State *ls) { int center_x = luaL_checkint(ls, 1); int center_y = luaL_checkint(ls, 2); int radius = std::abs(luaL_checkint(ls, 3)); dungeon_feature_type fill = _check_lua_feature(ls, 4); for (int x = -radius; x <= radius; x++) for (int y = -radius; y <= radius; y++) grd[center_x + x][center_y + y] = fill; return 0; } static int dgn_make_rounded_square(lua_State *ls) { int center_x = luaL_checkint(ls, 1); int center_y = luaL_checkint(ls, 2); int radius = std::abs(luaL_checkint(ls, 3)); dungeon_feature_type fill = _check_lua_feature(ls, 4); for (int x = -radius; x <= radius; x++) for (int y = -radius; y <= radius; y++) if (std::abs(x) != radius || std::abs(y) != radius) grd[center_x + x][center_y + y] = fill; return 0; } static int dgn_make_circle(lua_State *ls) { int center_x = luaL_checkint(ls, 1); int center_y = luaL_checkint(ls, 2); int radius = std::abs(luaL_checkint(ls, 3)); dungeon_feature_type fill = _check_lua_feature(ls, 4); for (int x = -radius; x <= radius; x++) for (int y = -radius; y <= radius; y++) if (x * x + y * y < radius * radius) grd[center_x + x][center_y + y] = fill; return 0; } static int dgn_in_bounds(lua_State *ls) { int x = luaL_checkint(ls, 1); int y = luaL_checkint(ls, 2); lua_pushboolean(ls, in_bounds(x, y)); return 1; } static int dgn_replace_first(lua_State *ls) { int x = luaL_checkint(ls, 1); int y = luaL_checkint(ls, 2); int dx = luaL_checkint(ls, 3); int dy = luaL_checkint(ls, 4); dungeon_feature_type search = _check_lua_feature(ls, 5); dungeon_feature_type replace = _check_lua_feature(ls, 6); _clamp_to_bounds(x, y); bool found = false; while (in_bounds(x, y)) { if (grd[x][y] == search) { grd[x][y] = replace; found = true; break; } x += dx; y += dy; } lua_pushboolean(ls, found); return 1; } static int dgn_replace_random(lua_State *ls) { dungeon_feature_type search = _check_lua_feature(ls, 1); dungeon_feature_type replace = _check_lua_feature(ls, 2); int x, y; do { x = random2(GXM); y = random2(GYM); } while (grd[x][y] != search); grd[x][y] = replace; return 0; } static int dgn_spotty_level(lua_State *ls) { bool seeded = lua_toboolean(ls, 1); int iterations = luaL_checkint(ls, 2); bool boxy = lua_toboolean(ls, 3); spotty_level(seeded, iterations, boxy); return 0; } static int dgn_smear_feature(lua_State *ls) { int iterations = luaL_checkint(ls, 1); bool boxy = lua_toboolean(ls, 2); dungeon_feature_type feat = _check_lua_feature(ls, 3); int x1 = luaL_checkint(ls, 4); int y1 = luaL_checkint(ls, 5); int x2 = luaL_checkint(ls, 6); int y2 = luaL_checkint(ls, 7); _clamp_to_bounds(x1, y1, true); _clamp_to_bounds(x2, y2, true); smear_feature(iterations, boxy, feat, x1, y1, x2, y2); return 0; } static int dgn_count_feature_in_box(lua_State *ls) { int x1 = luaL_checkint(ls, 1); int y1 = luaL_checkint(ls, 2); int x2 = luaL_checkint(ls, 3); int y2 = luaL_checkint(ls, 4); dungeon_feature_type feat = _check_lua_feature(ls, 5); lua_pushnumber(ls, count_feature_in_box(x1, y1, x2, y2, feat)); return 1; } static int dgn_count_antifeature_in_box(lua_State *ls) { int x1 = luaL_checkint(ls, 1); int y1 = luaL_checkint(ls, 2); int x2 = luaL_checkint(ls, 3); int y2 = luaL_checkint(ls, 4); dungeon_feature_type feat = _check_lua_feature(ls, 5); lua_pushnumber(ls, count_antifeature_in_box(x1, y1, x2, y2, feat)); return 1; } static int dgn_count_neighbours(lua_State *ls) { int x = luaL_checkint(ls, 1); int y = luaL_checkint(ls, 2); dungeon_feature_type feat = _check_lua_feature(ls, 3); lua_pushnumber(ls, count_neighbours(x, y, feat)); return 1; } static int dgn_join_the_dots(lua_State *ls) { int from_x = luaL_checkint(ls, 1); int from_y = luaL_checkint(ls, 2); int to_x = luaL_checkint(ls, 3); int to_y = luaL_checkint(ls, 4); // TODO enne - push map masks to lua? unsigned map_mask = MMT_VAULT; bool early_exit = lua_toboolean(ls, 5); coord_def from(from_x, from_y); coord_def to(to_x, to_y); bool ret = join_the_dots(from, to, map_mask, early_exit); lua_pushboolean(ls, ret); return 1; } static int dgn_fill_disconnected_zones(lua_State *ls) { int from_x = luaL_checkint(ls, 1); int from_y = luaL_checkint(ls, 2); int to_x = luaL_checkint(ls, 3); int to_y = luaL_checkint(ls, 4); dungeon_feature_type feat = _check_lua_feature(ls, 5); process_disconnected_zones(from_x, from_y, to_x, to_y, true, feat); return 0; } static int _dgn_is_passable(lua_State *ls) { COORDS(c, 1, 2); lua_pushboolean(ls, is_travelsafe_square(c, false, true)); return (1); } static int dgn_register_feature_marker(lua_State *ls) { COORDS(c, 1, 2); FEAT(feat, 3); env.markers.add( new map_feature_marker(c, feat) ); return (0); } static int dgn_register_lua_marker(lua_State *ls) { COORDS(c, 1, 2); if (!lua_istable(ls, 3) && !lua_isfunction(ls, 3)) return luaL_argerror(ls, 3, "Expected marker table or function"); lua_datum table(CLua::get_vm(ls), 3, false); map_marker *marker = new map_lua_marker(table); marker->pos = c; env.markers.add(marker); return (0); } static int dgn_create_monster(lua_State *ls) { COORDS(c, 1, 2); mons_list mlist = _lua_get_mlist(ls, 3); for (int i = 0, size = mlist.size(); i < size; ++i) { mons_spec mspec = mlist.get_monster(i); const int mid = dgn_place_monster(mspec, you.your_level, c, false, false, false); if (mid != -1) { push_monster(ls, &menv[mid]); return (1); } } lua_pushnil(ls); return (1); } static int _dgn_monster_spec(lua_State *ls) { const mons_list mlist = _lua_get_mlist(ls, 1); _push_object_type(ls, MONSLIST_METATABLE, mlist); return (1); } static int _dgn_item_spec(lua_State *ls) { const item_list ilist = _lua_get_ilist(ls, 1); _push_object_type(ls, ITEMLIST_METATABLE, ilist); return (1); } LUARET1(_dgn_max_monsters, number, MAX_MONSTERS) static int dgn_create_item(lua_State *ls) { COORDS(c, 1, 2); item_list ilist = _lua_get_ilist(ls, 3); const int level = lua_isnumber(ls, 4) ? lua_tointeger(ls, 4) : you.your_level; dgn_place_multiple_items(ilist, c, level); link_items(); return (0); } static std::auto_ptr _dgn_map_safe_bounds_fn; static bool _lua_map_place_valid(const map_def &map, const coord_def &c, const coord_def &size) { #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "lua_map_place_invalid: (%d,%d) (%d,%d)", c.x, c.y, size.x, size.y); #endif lua_stack_cleaner clean(_dgn_map_safe_bounds_fn->lua); // Push the Lua function onto the stack. _dgn_map_safe_bounds_fn->push(); lua_State *ls = _dgn_map_safe_bounds_fn->lua; // Push map, pos.x, pos.y, size.x, size.y clua_push_map(ls, const_cast(&map)); clua_push_coord(ls, c); clua_push_coord(ls, size); const int err = lua_pcall(ls, 5, 1, 0); // Lua error invalidates place. if (err) { mprf(MSGCH_ERROR, "Lua error: %s", lua_tostring(ls, -1)); return (true); } return (lua_toboolean(ls, -1)); } LUAFN(dgn_with_map_bounds_fn) { CLua &vm(CLua::get_vm(ls)); if (lua_gettop(ls) != 2 || !lua_isfunction(ls, 1) || !lua_isfunction(ls, 2)) luaL_error(ls, "Expected map-bounds check fn and action fn."); _dgn_map_safe_bounds_fn.reset(new lua_datum(vm, 1, false)); int err = 0; { unwind_var mpc(map_place_valid, _lua_map_place_valid); // All set, call our friend, the second function. ASSERT(lua_isfunction(ls, -1)); // Copy the function since pcall will pop it off. lua_pushvalue(ls, -1); // Use pcall to catch the error here, else unwind_var won't // happen when lua_call does its longjmp. err = lua_pcall(ls, 0, 1, 0); _dgn_map_safe_bounds_fn.reset(NULL); } if (err) lua_error(ls); return (1); } // Accepts any number of point coordinates and a function, binds the // points as anchors that floating vaults must match and calls the // function, returning the return value of the function. LUAFN(dgn_with_map_anchors) { const int top = lua_gettop(ls); int err = 0; { unwind_var uanchor(map_anchor_points); map_anchor_points.clear(); int i; for (i = 1; i < top; i += 2) { if (lua_isnumber(ls, i) && lua_isnumber(ls, i + 1)) map_anchor_points.push_back( coord_def( lua_tointeger(ls, i), lua_tointeger(ls, i + 1) ) ); } ASSERT(lua_isfunction(ls, -1)); lua_pushvalue(ls, -1); err = lua_pcall(ls, 0, 1, 0); } if (err) lua_error(ls); return (1); } #define BRANCH(br, pos) \ const char *branch_name = luaL_checkstring(ls, pos); \ branch_type req_branch_type = str_to_branch(branch_name); \ if (req_branch_type == NUM_BRANCHES) \ luaL_error(ls, "Expected branch name"); \ Branch &br = branches[req_branch_type] #define BRANCHFN(name, type, expr) \ LUAFN(dgn_br_##name) { \ BRANCH(br, 1); \ PLUARET(type, expr); \ } BRANCHFN(floorcol, number, br.floor_colour) BRANCHFN(rockcol, number, br.rock_colour) BRANCHFN(has_shops, boolean, br.has_shops) BRANCHFN(has_uniques, boolean, br.has_uniques) BRANCHFN(parent_branch, string, br.parent_branch == NUM_BRANCHES ? "" : branches[br.parent_branch].abbrevname) #define LEVEL(lev, br, pos) \ const char *level_name = luaL_checkstring(ls, pos); \ level_area_type lev = str_to_level_area_type(level_name); \ if (lev == NUM_LEVEL_AREA_TYPES) \ luaL_error(ls, "Expected level name"); \ const char *branch_name = luaL_checkstring(ls, pos); \ branch_type br = str_to_branch(branch_name); \ if (lev == LEVEL_DUNGEON && br == NUM_BRANCHES) \ luaL_error(ls, "Expected branch name"); static void push_level_id(lua_State *ls, const level_id &lid) { // We're skipping the constructor; naughty, but level_id has no // virtual methods and no dynamically allocated memory. level_id *nlev = static_cast(lua_newuserdata(ls, sizeof(level_id))); *nlev = lid; } static level_id _lua_level_id(lua_State *ls, int ndx) { if (lua_isstring(ls, ndx)) { const char *s = lua_tostring(ls, 1); try { return level_id::parse_level_id(s); } catch (const std::string &err) { luaL_error(ls, err.c_str()); } } else if (lua_isuserdata(ls, ndx)) { const level_id *lid = static_cast(lua_touserdata(ls, ndx)); return (*lid); } luaL_argerror(ls, ndx, "Expected level_id"); // Never gets here. return level_id(); } LUAFN(dgn_level_id) { const int nargs = lua_gettop(ls); if (!nargs) push_level_id(ls, level_id::current()); else if (nargs == 1) push_level_id(ls, _lua_level_id(ls, 1)); return (1); } LUAFN(dgn_level_name) { const level_id lid(_lua_level_id(ls, 1)); lua_pushstring(ls, lid.describe().c_str()); return (1); } LUAFN(dgn_set_level_type_name) { if (you.level_type != LEVEL_PORTAL_VAULT) { luaL_error(ls, "Can only set level type name on portal vaults"); return(0); } if (!lua_isstring(ls, 1)) { luaL_argerror(ls, 1, "Expected string for level type name"); return(0); } you.level_type_name = luaL_checkstring(ls, 1); return(0); } LUAFN(dgn_set_level_type_name_abbrev) { if (you.level_type != LEVEL_PORTAL_VAULT) { luaL_error(ls, "Can only set level type name abbreviation on " "portal vaults"); return(0); } if (!lua_isstring(ls, 1)) { luaL_argerror(ls, 1, "Expected string for level type name " "abbreviation"); return(0); } you.level_type_name_abbrev = luaL_checkstring(ls, 1); return(0); } LUAFN(dgn_set_level_type_origin) { if (you.level_type != LEVEL_PORTAL_VAULT) { luaL_error(ls, "Can only set level type origin on portal vaults"); return(0); } if (!lua_isstring(ls, 1)) { luaL_argerror(ls, 1, "Expected string for level type origin"); return(0); } you.level_type_origin = luaL_checkstring(ls, 1); return(0); } static int _lua_push_map(lua_State *ls, const map_def *map) { if (map) clua_push_map(ls, const_cast(map)); else lua_pushnil(ls); return (1); } LUAFN(dgn_map_by_tag) { if (const char *tag = luaL_checkstring(ls, 1)) { const bool check_depth = _lua_boolean(ls, 3, true); return _lua_push_map(ls, random_map_for_tag(tag, check_depth)); } return (0); } LUAFN(dgn_map_in_depth) { const level_id lid = _lua_level_id(ls, 1); const bool mini = _lua_boolean(ls, 2, true); return _lua_push_map(ls, random_map_in_depth(lid, mini)); } LUAFN(dgn_map_by_place) { const level_id lid = _lua_level_id(ls, 1); return _lua_push_map(ls, random_map_for_place(lid)); } LUAFN(_dgn_place_map) { MAP(ls, 1, map); const bool clobber = _lua_boolean(ls, 2, false); const bool no_exits = _lua_boolean(ls, 3, false); coord_def where(-1, -1); if (lua_isnumber(ls, 4) && lua_isnumber(ls, 5)) { COORDS(c, 4, 5); where = c; } if (dgn_place_map(map, clobber, no_exits, where) && !Level_Vaults.empty()) lua_pushlightuserdata(ls, &Level_Vaults[Level_Vaults.size() - 1]); else lua_pushnil(ls); return (1); } LUAFN(_dgn_in_vault) { GETCOORD(c, 1, 2, map_bounds); const int mask = lua_isnone(ls, 3) ? MMT_VAULT : lua_tointeger(ls, 3); lua_pushboolean(ls, dgn_Map_Mask(c) & mask); return (1); } LUAFN(_dgn_find_marker_prop) { const char *prop = luaL_checkstring(ls, 1); const std::string value( lua_gettop(ls) >= 2 ? luaL_checkstring(ls, 2) : ""); const coord_def place = find_marker_prop(prop, value); if (map_bounds(place)) clua_push_coord(ls, place); else { lua_pushnil(ls); lua_pushnil(ls); } return (2); } extern spec_room lua_special_room_spec; extern int lua_special_room_level; LUAFN(dgn_get_special_room_info) { if (!lua_special_room_spec.created || !in_bounds(lua_special_room_spec.tl) || lua_special_room_level == -1) { return (0); } lua_pushnumber(ls, lua_special_room_level); dlua_push_coord(ls, lua_special_room_spec.tl); dlua_push_coord(ls, lua_special_room_spec.br); return (5); } LUAFN(_dgn_resolve_map) { if (lua_isnil(ls, 1)) { lua_pushnil(ls); return (1); } MAP(ls, 1, map); const bool check_collisions = _lua_boolean(ls, 2, true); // Save the vault_placement into Temp_Vaults because the map_def // will need to be alive through to the end of dungeon gen. Temp_Vaults.push_back(vault_placement()); vault_placement &place(Temp_Vaults[Temp_Vaults.size() - 1]); if (vault_main(place, map, check_collisions) != MAP_NONE) { clua_push_map(ls, &place.map); lua_pushlightuserdata(ls, &place); } else { lua_pushnil(ls); lua_pushnil(ls); } return (2); } LUAFN(_dgn_reuse_map) { if (!lua_isuserdata(ls, 1)) luaL_argerror(ls, 1, "Expected vault_placement"); vault_placement &vp( *static_cast(lua_touserdata(ls, 1))); COORDS(place, 2, 3); const bool flip_horiz = _lua_boolean(ls, 4, false); const bool flip_vert = _lua_boolean(ls, 5, false); // 1 for clockwise, -1 for anticlockwise, 0 for no rotation. const int rotate_dir = lua_isnone(ls, 6) ? 0 : luaL_checkint(ls, 6); const bool register_place = _lua_boolean(ls, 7, true); const bool register_vault = register_place && _lua_boolean(ls, 8, false); if (flip_horiz) vp.map.hmirror(); if (flip_vert) vp.map.vmirror(); if (rotate_dir) vp.map.rotate(rotate_dir == 1); vp.size = vp.map.map.size(); // draw_at changes vault_placement. vp.draw_at(place); if (register_place) dgn_register_place(vp, register_vault); return (0); } LUAFN(dgn_lev_floortile) { #ifdef USE_TILE LEVEL(lev, br, 1); tile_flavour flv; tile_default_flv(lev, br, flv); const char *tile_name = tile_dngn_name(flv.floor); PLUARET(string, tile_name); #else PLUARET(string, "invalid"); #endif } LUAFN(dgn_lev_rocktile) { #ifdef USE_TILE LEVEL(lev, br, 1); tile_flavour flv; tile_default_flv(lev, br, flv); const char *tile_name = tile_dngn_name(flv.wall); PLUARET(string, tile_name); #else PLUARET(string, "invalid"); #endif } LUAFN(dgn_lrocktile) { MAP(ls, 1, map); #ifdef USE_TILE unsigned short tile = _get_tile_idx(ls, 2); map->rock_tile = tile; const char *tile_name = tile_dngn_name(tile); PLUARET(string, tile_name); #else UNUSED(map); PLUARET(string, "invalid"); #endif } LUAFN(dgn_lfloortile) { MAP(ls, 1, map); #ifdef USE_TILE unsigned short tile = _get_tile_idx(ls, 2); map->floor_tile = tile; const char *tile_name = tile_dngn_name(tile); PLUARET(string, tile_name); #else UNUSED(map); PLUARET(string, "invalid"); #endif } LUAFN(dgn_change_rock_tile) { #ifdef USE_TILE unsigned short tile = _get_tile_idx(ls, 1); if (tile) env.tile_default.wall = tile; const char *tile_name = tile_dngn_name(tile); PLUARET(string, tile_name); #else PLUARET(string, "invalid"); #endif } LUAFN(dgn_change_floor_tile) { #ifdef USE_TILE unsigned short tile = _get_tile_idx(ls, 1); if (tile) env.tile_default.floor = tile; const char *tile_name = tile_dngn_name(tile); PLUARET(string, tile_name); #else PLUARET(string, "invalid"); #endif } LUAFN(dgn_ftile) { #ifdef USE_TILE return dgn_map_add_transform(ls, &map_lines::add_floortile, &map_lines::clear_floortiles); #else return 0; #endif } LUAFN(dgn_rtile) { #ifdef USE_TILE return dgn_map_add_transform(ls, &map_lines::add_rocktile, &map_lines::clear_rocktiles); #else return 0; #endif } LUAFN(dgn_debug_dump_map) { const int pos = lua_isuserdata(ls, 1) ? 2 : 1; if (lua_isstring(ls, pos)) dump_map(lua_tostring(ls, pos), true); return (0); } static const struct luaL_reg dgn_lib[] = { { "default_depth", dgn_default_depth }, { "name", dgn_name }, { "depth", dgn_depth }, { "place", dgn_place }, { "tags", dgn_tags }, { "tags_remove", dgn_tags_remove }, { "lflags", dgn_lflags }, { "bflags", dgn_bflags }, { "chance", dgn_chance }, { "weight", dgn_weight }, { "welcome", dgn_welcome }, { "weight", dgn_weight }, { "orient", dgn_orient }, { "shuffle", dgn_shuffle }, { "shuffle_remove", dgn_shuffle_remove }, { "subst", dgn_subst }, { "nsubst", dgn_nsubst }, { "colour", dgn_colour }, { "lfloorcol", dgn_lfloorcol}, { "lrockcol", dgn_lrockcol}, { "subst_remove", dgn_subst_remove }, { "map", dgn_map }, { "mons", dgn_mons }, { "item", dgn_item }, { "marker", dgn_marker }, { "lua_marker", dgn_lua_marker }, { "kfeat", dgn_kfeat }, { "kitem", dgn_kitem }, { "kmons", dgn_kmons }, { "kmask", dgn_kmask }, { "mapsize", dgn_map_size }, { "grid", dgn_grid }, { "is_wall", _dgn_is_wall }, { "max_bounds", dgn_max_bounds }, { "colour_at", dgn_colour_at }, { "terrain_changed", dgn_terrain_changed }, { "points_connected", dgn_points_connected }, { "any_point_connected", dgn_any_point_connected }, { "has_exit_from", dgn_has_exit_from }, { "gly_point", dgn_gly_point }, { "gly_points", dgn_gly_points }, { "original_map", dgn_original_map }, { "load_des_file", dgn_load_des_file }, { "feature_number", dgn_feature_number }, { "feature_name", dgn_feature_name }, { "dgn_event_type", dgn_dgn_event }, { "register_listener", dgn_register_listener }, { "remove_listener", dgn_remove_listener }, { "remove_marker", dgn_remove_marker }, { "num_matching_markers", dgn_num_matching_markers }, { "feature_desc", dgn_feature_desc }, { "feature_desc_at", dgn_feature_desc_at }, { "set_feature_desc_short", dgn_set_feature_desc_short }, { "set_feature_desc_long", dgn_set_feature_desc_long }, { "item_from_index", dgn_item_from_index }, { "mons_from_index", dgn_mons_from_index }, { "mons_at", dgn_mons_at }, { "items_at", dgn_items_at }, { "change_level_flags", dgn_change_level_flags }, { "change_branch_flags", dgn_change_branch_flags }, { "set_random_mon_list", dgn_set_random_mon_list }, { "get_floor_colour", dgn_get_floor_colour }, { "get_rock_colour", dgn_get_rock_colour }, { "change_floor_colour", dgn_change_floor_colour }, { "change_rock_colour", dgn_change_rock_colour }, { "set_lt_callback", lua_dgn_set_lt_callback }, { "fixup_stairs", dgn_fixup_stairs }, { "floor_halo", dgn_floor_halo }, { "random_walk", dgn_random_walk }, { "apply_area_cloud", dgn_apply_area_cloud }, // building routines { "fill_area", dgn_fill_area }, { "replace_area", dgn_replace_area }, { "octa_room", dgn_octa_room }, { "make_pillars", dgn_make_pillars }, { "make_square", dgn_make_square }, { "make_rounded_square", dgn_make_rounded_square }, { "make_circle", dgn_make_circle }, { "in_bounds", dgn_in_bounds }, { "replace_first", dgn_replace_first }, { "replace_random", dgn_replace_random }, { "spotty_level", dgn_spotty_level }, { "smear_feature", dgn_smear_feature }, { "count_feature_in_box", dgn_count_feature_in_box }, { "count_antifeature_in_box", dgn_count_antifeature_in_box }, { "count_neighbours", dgn_count_neighbours }, { "join_the_dots", dgn_join_the_dots }, { "fill_disconnected_zones", dgn_fill_disconnected_zones }, { "is_passable", _dgn_is_passable }, { "register_feature_marker", dgn_register_feature_marker }, { "register_lua_marker", dgn_register_lua_marker }, { "create_monster", dgn_create_monster }, { "create_item", dgn_create_item }, { "monster_spec", _dgn_monster_spec }, { "item_spec", _dgn_item_spec }, { "max_monsters", _dgn_max_monsters }, { "with_map_bounds_fn", dgn_with_map_bounds_fn }, { "with_map_anchors", dgn_with_map_anchors }, { "br_floorcol", dgn_br_floorcol }, { "br_rockcol", dgn_br_rockcol }, { "br_has_shops", dgn_br_has_shops }, { "br_has_uniques", dgn_br_has_uniques }, { "br_parent_branch", dgn_br_parent_branch }, { "level_id", dgn_level_id }, { "level_name", dgn_level_name }, { "set_level_type_name", dgn_set_level_type_name }, { "set_level_type_name_abbrev", dgn_set_level_type_name_abbrev }, { "set_level_type_origin", dgn_set_level_type_origin }, { "map_by_tag", dgn_map_by_tag }, { "map_in_depth", dgn_map_in_depth }, { "map_by_place", dgn_map_by_place }, { "place_map", _dgn_place_map }, { "reuse_map", _dgn_reuse_map }, { "resolve_map", _dgn_resolve_map }, { "in_vault", _dgn_in_vault }, { "find_marker_prop", _dgn_find_marker_prop }, { "get_special_room_info", dgn_get_special_room_info }, { "lrocktile", dgn_lrocktile }, { "lfloortile", dgn_lfloortile }, { "rtile", dgn_rtile }, { "ftile", dgn_ftile }, { "change_rock_tile", dgn_change_rock_tile }, { "change_floor_tile", dgn_change_floor_tile }, { "lev_floortile", dgn_lev_floortile }, { "lev_rocktile", dgn_lev_rocktile }, { "debug_dump_map", dgn_debug_dump_map }, { NULL, NULL } }; LUAFN(_crawl_args) { return dlua_stringtable(ls, SysEnv.cmd_args); } LUAFN(_crawl_milestone) { #ifdef DGL_MILESTONES mark_milestone(luaL_checkstring(ls, 1), luaL_checkstring(ls, 2)); #endif return (0); } #ifdef UNIX LUAFN(_crawl_millis) { struct timeval tv; struct timezone tz; const int error = gettimeofday(&tv, &tz); if (error) luaL_error(ls, make_stringf("Failed to get time: %s", strerror(error)).c_str()); lua_pushnumber(ls, tv.tv_sec * 1000 + tv.tv_usec / 1000); return (1); } #endif static const struct luaL_reg crawl_lib[] = { { "args", _crawl_args }, { "mark_milestone", _crawl_milestone }, #ifdef UNIX { "millis", _crawl_millis }, #endif { NULL, NULL } }; static int file_marshall(lua_State *ls) { if (lua_gettop(ls) != 2) luaL_error(ls, "Need two arguments: tag header and value"); writer &th(*static_cast( lua_touserdata(ls, 1) )); if (lua_isnumber(ls, 2)) marshallLong(th, luaL_checklong(ls, 2)); else if (lua_isboolean(ls, 2)) marshallByte(th, lua_toboolean(ls, 2)); else if (lua_isstring(ls, 2)) marshallString(th, lua_tostring(ls, 2)); else if (lua_isfunction(ls, 2)) { dlua_chunk chunk(ls); marshallString(th, chunk.compiled_chunk()); } return (0); } static int file_unmarshall_boolean(lua_State *ls) { if (lua_gettop(ls) != 1) luaL_error(ls, "Need reader as one argument"); reader &th(*static_cast( lua_touserdata(ls, 1) )); lua_pushboolean(ls, unmarshallByte(th)); return (1); } static int file_unmarshall_number(lua_State *ls) { if (lua_gettop(ls) != 1) luaL_error(ls, "Need reader as one argument"); reader &th(*static_cast( lua_touserdata(ls, 1) )); lua_pushnumber(ls, unmarshallLong(th)); return (1); } static int file_unmarshall_string(lua_State *ls) { if (lua_gettop(ls) != 1) luaL_error(ls, "Need reader as one argument"); reader &th(*static_cast( lua_touserdata(ls, 1) )); lua_pushstring(ls, unmarshallString(th).c_str()); return (1); } static int file_unmarshall_fn(lua_State *ls) { if (lua_gettop(ls) != 1) luaL_error(ls, "Need reader as one argument"); reader &th(*static_cast( lua_touserdata(ls, 1) )); const std::string s(unmarshallString(th, LUA_CHUNK_MAX_SIZE)); dlua_chunk chunk = dlua_chunk::precompiled(s); if (chunk.load(dlua)) lua_pushnil(ls); return (1); } enum lua_persist_type { LPT_NONE, LPT_NUMBER, LPT_STRING, LPT_FUNCTION, LPT_NIL, LPT_BOOLEAN }; static int file_marshall_meta(lua_State *ls) { if (lua_gettop(ls) != 2) luaL_error(ls, "Need two arguments: tag header and value"); writer &th(*static_cast( lua_touserdata(ls, 1) )); lua_persist_type ptype = LPT_NONE; if (lua_isnumber(ls, 2)) ptype = LPT_NUMBER; else if (lua_isboolean(ls, 2)) ptype = LPT_BOOLEAN; else if (lua_isstring(ls, 2)) ptype = LPT_STRING; else if (lua_isfunction(ls, 2)) ptype = LPT_FUNCTION; else if (lua_isnil(ls, 2)) ptype = LPT_NIL; else luaL_error(ls, make_stringf("Cannot marshall %s", lua_typename(ls, lua_type(ls, 2))).c_str()); marshallByte(th, ptype); if (ptype != LPT_NIL) file_marshall(ls); return (0); } static int file_unmarshall_meta(lua_State *ls) { reader &th(*static_cast( lua_touserdata(ls, 1) )); const lua_persist_type ptype = static_cast(unmarshallByte(th)); switch (ptype) { case LPT_BOOLEAN: return file_unmarshall_boolean(ls); case LPT_NUMBER: return file_unmarshall_number(ls); case LPT_STRING: return file_unmarshall_string(ls); case LPT_FUNCTION: return file_unmarshall_fn(ls); case LPT_NIL: lua_pushnil(ls); return (1); default: luaL_error(ls, "Unexpected type signature."); } // Never get here. return (0); } static const struct luaL_reg file_lib[] = { { "marshall", file_marshall }, { "marshall_meta", file_marshall_meta }, { "unmarshall_meta", file_unmarshall_meta }, { "unmarshall_number", file_unmarshall_number }, { "unmarshall_string", file_unmarshall_string }, { "unmarshall_fn", file_unmarshall_fn }, { NULL, NULL } }; LUARET1(you_can_hear_pos, boolean, player_can_hear(coord_def(luaL_checkint(ls,1), luaL_checkint(ls, 2)))) LUARET1(you_x_pos, number, you.pos().x) LUARET1(you_y_pos, number, you.pos().y) LUARET2(you_pos, number, you.pos().x, you.pos().y) static const struct luaL_reg you_lib[] = { { "hear_pos", you_can_hear_pos }, { "x_pos", you_x_pos }, { "y_pos", you_y_pos }, { "pos", you_pos }, { NULL, NULL } }; static int dgnevent_type(lua_State *ls) { DEVENT(ls, 1, dev); PLUARET(number, dev->type); } static int dgnevent_place(lua_State *ls) { DEVENT(ls, 1, dev); lua_pushnumber(ls, dev->place.x); lua_pushnumber(ls, dev->place.y); return (2); } static int dgnevent_dest(lua_State *ls) { DEVENT(ls, 1, dev); lua_pushnumber(ls, dev->dest.x); lua_pushnumber(ls, dev->dest.y); return (2); } static int dgnevent_ticks(lua_State *ls) { DEVENT(ls, 1, dev); PLUARET(number, dev->elapsed_ticks); } static int dgnevent_arg1(lua_State *ls) { DEVENT(ls, 1, dev); PLUARET(number, dev->arg1); } static int dgnevent_arg2(lua_State *ls) { DEVENT(ls, 1, dev); PLUARET(number, dev->arg2); } static const struct luaL_reg dgnevent_lib[] = { { "type", dgnevent_type }, { "pos", dgnevent_place }, { "dest", dgnevent_dest }, { "ticks", dgnevent_ticks }, { "arg1", dgnevent_arg1 }, { "arg2", dgnevent_arg2 }, { NULL, NULL } }; static void luaopen_setmeta(lua_State *ls, const char *global, const luaL_reg *lua_lib, const char *meta) { luaL_newmetatable(ls, meta); lua_setglobal(ls, global); luaL_openlib(ls, global, lua_lib, 0); // Do .__index = lua_pushstring(ls, "__index"); lua_pushvalue(ls, -2); lua_settable(ls, -3); } static void luaopen_dgnevent(lua_State *ls) { luaopen_setmeta(ls, "dgnevent", dgnevent_lib, DEVENT_METATABLE); } static int mapmarker_pos(lua_State *ls) { MAPMARKER(ls, 1, mark); lua_pushnumber(ls, mark->pos.x); lua_pushnumber(ls, mark->pos.y); return (2); } static int mapmarker_move(lua_State *ls) { MAPMARKER(ls, 1, mark); const coord_def dest( luaL_checkint(ls, 2), luaL_checkint(ls, 3) ); env.markers.move_marker(mark, dest); return (0); } static mons_list _lua_get_mlist(lua_State *ls, int ndx) { if (lua_isstring(ls, ndx)) { const char *spec = lua_tostring(ls, ndx); mons_list mlist; const std::string err = mlist.add_mons(spec); if (!err.empty()) luaL_error(ls, err.c_str()); return (mlist); } else { mons_list **mlist( clua_get_userdata(ls, MONSLIST_METATABLE, ndx)); if (mlist) return (**mlist); luaL_argerror(ls, ndx, "Expected monster list object or string"); return mons_list(); } } static item_list _lua_get_ilist(lua_State *ls, int ndx) { if (lua_isstring(ls, ndx)) { const char *spec = lua_tostring(ls, ndx); item_list ilist; const std::string err = ilist.add_item(spec); if (!err.empty()) luaL_error(ls, err.c_str()); return (ilist); } else { item_list **ilist( clua_get_userdata(ls, ITEMLIST_METATABLE, ndx)); if (ilist) return (**ilist); luaL_argerror(ls, ndx, "Expected item list object or string"); return item_list(); } } static void _register_mapdef_tables(lua_State *ls) { clua_register_metatable(ls, MONSLIST_METATABLE, NULL, lua_object_gc); clua_register_metatable(ls, ITEMLIST_METATABLE, NULL, lua_object_gc); } static const struct luaL_reg mapmarker_lib[] = { { "pos", mapmarker_pos }, { "move", mapmarker_move }, { NULL, NULL } }; static void luaopen_mapmarker(lua_State *ls) { luaopen_setmeta(ls, "mapmarker", mapmarker_lib, MAPMARK_METATABLE); } void init_dungeon_lua() { lua_stack_cleaner clean(dlua); luaL_openlib(dlua, "dgn", dgn_lib, 0); // Add additional function to the Crawl module. luaL_openlib(dlua, "crawl", crawl_lib, 0); luaL_openlib(dlua, "file", file_lib, 0); luaL_openlib(dlua, "you", you_lib, 0); dlua.execfile("clua/dungeon.lua", true, true); dlua.execfile("clua/luamark.lua", true, true); lua_getglobal(dlua, "dgn_run_map"); luaopen_debug(dlua); luaL_newmetatable(dlua, MAP_METATABLE); luaopen_dgnevent(dlua); luaopen_mapmarker(dlua); _register_mapdef_tables(dlua); } // Can be called from within a debugger to look at the current Lua // call stack. (Borrowed from ToME 3) void print_dlua_stack(void) { struct lua_Debug dbg; int i = 0; lua_State *L = dlua.state(); fprintf(stderr, EOL); while (lua_getstack(L, i++, &dbg) == 1) { lua_getinfo(L, "lnuS", &dbg); char* file = strrchr(dbg.short_src, '/'); if (file == NULL) file = dbg.short_src; else file++; fprintf(stderr, "%s, function %s, line %d" EOL, file, dbg.name, dbg.currentline); } fprintf(stderr, EOL); }