dfhack/library/LuaApi.cpp

3828 lines
103 KiB
C++

/*
https://github.com/peterix/dfhack
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#include "Internal.h"
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include "MemAccess.h"
#include "Core.h"
#include "Error.h"
#include "VersionInfo.h"
// must be last due to MS stupidity
#include "DataDefs.h"
#include "DataIdentity.h"
#include "DataFuncs.h"
#include "DFHackVersion.h"
#include "PluginManager.h"
#include "md5wrapper.h"
#include "modules/Buildings.h"
#include "modules/Burrows.h"
#include "modules/Constructions.h"
#include "modules/Designations.h"
#include "modules/Filesystem.h"
#include "modules/Gui.h"
#include "modules/Items.h"
#include "modules/Job.h"
#include "modules/Kitchen.h"
#include "modules/MapCache.h"
#include "modules/Maps.h"
#include "modules/Materials.h"
#include "modules/Military.h"
#include "modules/Random.h"
#include "modules/Screen.h"
#include "modules/Textures.h"
#include "modules/Translation.h"
#include "modules/Units.h"
#include "modules/World.h"
#include "LuaWrapper.h"
#include "LuaTools.h"
#include "MiscUtils.h"
#include "df/activity_entry.h"
#include "df/activity_event.h"
#include "df/enabler.h"
#include "df/job.h"
#include "df/job_item.h"
#include "df/building.h"
#include "df/unit.h"
#include "df/item.h"
#include "df/material.h"
#include "df/viewscreen.h"
#include "df/identity.h"
#include "df/nemesis_record.h"
#include "df/historical_figure.h"
#include "df/histfig_entity_link_positionst.h"
#include "df/plant_raw.h"
#include "df/creature_raw.h"
#include "df/inorganic_raw.h"
#include "df/dfhack_material_category.h"
#include "df/job_material_category.h"
#include "df/burrow.h"
#include "df/building_cagest.h"
#include "df/building_civzonest.h"
#include "df/region_map_entry.h"
#include "df/flow_info.h"
#include "df/unit_misc_trait.h"
#include "df/proj_itemst.h"
#include "df/itemdef.h"
#include "df/feature_init.h"
#include "df/plant.h"
#include "df/specific_ref.h"
#include "df/specific_ref_type.h"
#include "df/vermin.h"
#include "df/announcement_infost.h"
#include "df/report_zoom_type.h"
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
using namespace DFHack;
using namespace DFHack::LuaWrapper;
using Screen::Pen;
using Random::MersenneRNG;
using Random::PerlinNoise1D;
using Random::PerlinNoise2D;
using Random::PerlinNoise3D;
void dfhack_printerr(lua_State *S, const std::string &str);
static df::coord2d CheckCoordXY(lua_State *state, int base, bool vararg = false)
{
df::coord2d p;
if (vararg && lua_gettop(state) <= base)
Lua::CheckDFAssign(state, &p, base);
else
{
p = df::coord2d(
luaL_checkint(state, base),
luaL_checkint(state, base+1)
);
}
return p;
}
static df::coord CheckCoordXYZ(lua_State *state, int base, bool vararg = false)
{
df::coord p;
if (vararg && lua_gettop(state) <= base)
Lua::CheckDFAssign(state, &p, base);
else
{
p = df::coord(
luaL_checkint(state, base),
luaL_checkint(state, base+1),
luaL_checkint(state, base+2)
);
}
return p;
}
template<class T>
static bool get_int_field(lua_State *L, T *pf, int idx, const char *name, int defval)
{
lua_getfield(L, idx, name);
bool nil = lua_isnil(L, -1);
if (nil) *pf = T(defval);
else if (lua_isnumber(L, -1)) *pf = T(lua_tointeger(L, -1));
else luaL_error(L, "Field %s is not a number.", name);
lua_pop(L, 1);
return !nil;
}
template<class T>
static bool get_int_or_closure_field(lua_State *L, T *pf, int idx, const char *name, int defval)
{
lua_getfield(L, idx, name);
bool nil = lua_isnil(L, -1);
if (nil) {
*pf = T(defval);
} else if (lua_isnumber(L, -1)) {
*pf = T(lua_tointeger(L, -1));
} else if (lua_isfunction(L, -1)) {
lua_call(L, 0, 1);
*pf = T(lua_tointeger(L, -1));
lua_pop(L, 1);
} else {
luaL_error(L, "Field %s is not a number or closure function.", name);
}
lua_pop(L, 1);
return !nil;
}
static bool get_char_field(lua_State *L, char *pf, int idx, const char *name, char defval)
{
lua_getfield(L, idx, name);
if (lua_type(L, -1) == LUA_TSTRING)
{
*pf = lua_tostring(L, -1)[0];
lua_pop(L, 1);
return true;
}
else
{
lua_pop(L, 1);
return get_int_field(L, pf, idx, name, defval);
}
}
static bool get_bool_field(lua_State *L, bool *pf, int idx, const char *name, bool defval) {
lua_getfield(L, idx, name);
bool nil = lua_isnil(L, -1);
if (nil)
*pf = defval;
else
*pf = lua_toboolean(L, -1);
lua_pop(L, 1);
return !nil;
}
static void decode_pen(lua_State *L, Pen &pen, int idx)
{
idx = lua_absindex(L, idx);
get_char_field(L, &pen.ch, idx, "ch", 0);
get_int_field(L, &pen.fg, idx, "fg", 7);
get_int_field(L, &pen.bg, idx, "bg", 0);
lua_getfield(L, idx, "bold");
if (lua_isnil(L, -1))
{
pen.bold = (pen.fg & 8) != 0;
pen.fg &= 7;
}
else pen.bold = lua_toboolean(L, -1);
lua_pop(L, 1);
get_int_or_closure_field(L, &pen.tile, idx, "tile", 0);
bool tcolor = get_int_field(L, &pen.tile_fg, idx, "tile_fg", 7);
tcolor = get_int_field(L, &pen.tile_bg, idx, "tile_bg", 0) || tcolor;
if (tcolor)
pen.tile_mode = Pen::TileColor;
else
{
lua_getfield(L, idx, "tile_color");
pen.tile_mode = (lua_toboolean(L, -1) ? Pen::CharColor : Pen::AsIs);
lua_pop(L, 1);
}
get_bool_field(L, &pen.keep_lower, idx, "keep_lower", false);
get_bool_field(L, &pen.write_to_lower, idx, "write_to_lower", false);
get_bool_field(L, &pen.top_of_text, idx, "top_of_text", false);
get_bool_field(L, &pen.bottom_of_text, idx, "bottom_of_text", false);
}
/**************************************************
* Per-world persistent configuration storage API *
**************************************************/
static PersistentDataItem persistent_by_struct(lua_State *state, int idx)
{
lua_getfield(state, idx, "entry_id");
int id = lua_tointeger(state, -1);
lua_pop(state, 1);
PersistentDataItem ref = World::GetPersistentData(id);
if (ref.isValid())
{
lua_getfield(state, idx, "key");
const char *str = lua_tostring(state, -1);
if (!str || str != ref.key())
luaL_argerror(state, idx, "inconsistent id and key");
lua_pop(state, 1);
}
return ref;
}
static int read_persistent(lua_State *state, PersistentDataItem ref, bool create)
{
if (!ref.isValid())
{
lua_pushnil(state);
lua_pushstring(state, "entry not found");
return 2;
}
if (create)
lua_createtable(state, 0, 4);
lua_pushvalue(state, lua_upvalueindex(1));
lua_setmetatable(state, -2);
lua_pushinteger(state, ref.entry_id());
lua_setfield(state, -2, "entry_id");
lua_pushstring(state, ref.key().c_str());
lua_setfield(state, -2, "key");
lua_pushstring(state, ref.val().c_str());
lua_setfield(state, -2, "value");
lua_createtable(state, PersistentDataItem::NumInts, 0);
for (int i = 0; i < PersistentDataItem::NumInts; i++)
{
lua_pushinteger(state, ref.ival(i));
lua_rawseti(state, -2, i+1);
}
lua_setfield(state, -2, "ints");
return 1;
}
static PersistentDataItem get_persistent(lua_State *state)
{
luaL_checkany(state, 1);
if (lua_istable(state, 1))
{
Lua::StackUnwinder frame(state);
if (!lua_getmetatable(state, 1) ||
!lua_rawequal(state, -1, lua_upvalueindex(1)))
luaL_argerror(state, 1, "invalid table type");
return persistent_by_struct(state, 1);
}
else
{
const char *str = luaL_checkstring(state, 1);
return World::GetPersistentData(str);
}
}
static int dfhack_persistent_get(lua_State *state)
{
CoreSuspender suspend;
auto ref = get_persistent(state);
return read_persistent(state, ref, !lua_istable(state, 1));
}
static int dfhack_persistent_delete(lua_State *state)
{
CoreSuspender suspend;
auto ref = get_persistent(state);
bool ok = World::DeletePersistentData(ref);
lua_pushboolean(state, ok);
return 1;
}
static int dfhack_persistent_get_all(lua_State *state)
{
CoreSuspender suspend;
const char *str = luaL_checkstring(state, 1);
bool prefix = (lua_gettop(state)>=2 ? lua_toboolean(state,2) : false);
std::vector<PersistentDataItem> data;
World::GetPersistentData(&data, str, prefix);
if (data.empty())
{
lua_pushnil(state);
}
else
{
lua_createtable(state, data.size(), 0);
for (size_t i = 0; i < data.size(); ++i)
{
read_persistent(state, data[i], true);
lua_rawseti(state, -2, i+1);
}
}
return 1;
}
static int dfhack_persistent_save(lua_State *state)
{
CoreSuspender suspend;
lua_settop(state, 2);
luaL_checktype(state, 1, LUA_TTABLE);
bool add = lua_toboolean(state, 2);
lua_getfield(state, 1, "key");
const char *str = lua_tostring(state, -1);
if (!str)
luaL_argerror(state, 1, "no key field");
lua_settop(state, 1);
// Retrieve existing or create a new entry
PersistentDataItem ref;
bool added = false;
if (add)
{
ref = World::AddPersistentData(str);
added = true;
}
else if (lua_getmetatable(state, 1))
{
if (!lua_rawequal(state, -1, lua_upvalueindex(1)))
return luaL_argerror(state, 1, "invalid table type");
lua_pop(state, 1);
ref = persistent_by_struct(state, 1);
}
else
{
ref = World::GetPersistentData(str);
}
// Auto-add if not found
if (!ref.isValid())
{
ref = World::AddPersistentData(str);
if (!ref.isValid())
luaL_error(state, "cannot create persistent entry");
added = true;
}
// Copy data from lua to C++ memory
lua_getfield(state, 1, "value");
if (const char *str = lua_tostring(state, -1))
ref.val() = str;
lua_pop(state, 1);
lua_getfield(state, 1, "ints");
if (lua_istable(state, -1))
{
for (int i = 0; i < PersistentDataItem::NumInts; i++)
{
lua_rawgeti(state, -1, i+1);
if (lua_isnumber(state, -1))
ref.ival(i) = lua_tointeger(state, -1);
lua_pop(state, 1);
}
}
lua_pop(state, 1);
// Reinitialize lua from C++ and return
read_persistent(state, ref, false);
lua_pushboolean(state, added);
return 2;
}
static int dfhack_persistent_getTilemask(lua_State *state)
{
CoreSuspender suspend;
lua_settop(state, 3);
auto ref = get_persistent(state);
auto block = Lua::CheckDFObject<df::map_block>(state, 2);
bool create = lua_toboolean(state, 3);
Lua::PushDFObject(state, World::getPersistentTilemask(ref, block, create));
return 1;
}
static int dfhack_persistent_deleteTilemask(lua_State *state)
{
CoreSuspender suspend;
lua_settop(state, 2);
auto ref = get_persistent(state);
auto block = Lua::CheckDFObject<df::map_block>(state, 2);
lua_pushboolean(state, World::deletePersistentTilemask(ref, block));
return 1;
}
static const luaL_Reg dfhack_persistent_funcs[] = {
{ "get", dfhack_persistent_get },
{ "delete", dfhack_persistent_delete },
{ "get_all", dfhack_persistent_get_all },
{ "save", dfhack_persistent_save },
{ "getTilemask", dfhack_persistent_getTilemask },
{ "deleteTilemask", dfhack_persistent_deleteTilemask },
{ NULL, NULL }
};
static void OpenPersistent(lua_State *state)
{
luaL_getsubtable(state, lua_gettop(state), "persistent");
lua_dup(state);
luaL_setfuncs(state, dfhack_persistent_funcs, 1);
lua_dup(state);
lua_setfield(state, -2, "__index");
lua_pop(state, 1);
}
/************************
* Material info lookup *
************************/
static int DFHACK_MATINFO_TOKEN = 0;
void Lua::Push(lua_State *state, const MaterialInfo &info)
{
if (!info.isValid())
{
lua_pushnil(state);
return;
}
lua_newtable(state);
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_MATINFO_TOKEN);
lua_setmetatable(state, -2);
lua_pushinteger(state, info.type);
lua_setfield(state, -2, "type");
lua_pushinteger(state, info.index);
lua_setfield(state, -2, "index");
#define SETOBJ(name) { \
Lua::PushDFObject(state, info.name); \
lua_setfield(state, -2, #name); \
}
SETOBJ(material);
if (info.plant) SETOBJ(plant);
if (info.creature) SETOBJ(creature);
if (info.inorganic) SETOBJ(inorganic);
if (info.figure) SETOBJ(figure);
#undef SETOBJ
if (info.mode != MaterialInfo::Builtin)
{
lua_pushinteger(state, info.subtype);
lua_setfield(state, -2, "subtype");
}
const char *id = "builtin";
switch (info.mode)
{
case MaterialInfo::Plant: id = "plant"; break;
case MaterialInfo::Creature: id = "creature"; break;
case MaterialInfo::Inorganic: id = "inorganic"; break;
default: break;
}
lua_pushstring(state, id);
lua_setfield(state, -2, "mode");
}
static int dfhack_matinfo_find(lua_State *state)
{
MaterialInfo info;
int argc = lua_gettop(state);
if (argc == 1)
info.find(luaL_checkstring(state, 1));
else
{
std::vector<std::string> tokens;
for (int i = 1; i <= argc; i++)
tokens.push_back(luaL_checkstring(state, i));
info.find(tokens);
}
Lua::Push(state, info);
return 1;
}
static bool decode_matinfo(lua_State *state, MaterialInfo *info, bool numpair = false)
{
int curtop = lua_gettop(state);
luaL_checkany(state, 1);
if (!lua_isnumber(state, 1))
{
if (lua_isnil(state, 1))
return false;
if (lua_getmetatable(state, 1))
{
if (lua_rawequal(state, -1, lua_upvalueindex(1)))
{
lua_getfield(state, 1, "type");
lua_getfield(state, 1, "index");
goto int_pair;
}
lua_pop(state, 1);
}
if (lua_isuserdata(state, 1))
{
if (auto item = Lua::GetDFObject<df::item>(state, 1))
return info->decode(item);
if (auto mvec = Lua::GetDFObject<df::material_vec_ref>(state, 1))
return info->decode(*mvec, luaL_checkint(state, 2));
}
lua_getfield(state, 1, "mat_type");
lua_getfield(state, 1, "mat_index");
goto int_pair;
}
else
{
if (!numpair)
luaL_argerror(state, 1, "material info object expected");
if (curtop < 2)
lua_settop(state, 2);
}
int_pair:
{
int ok;
int type = lua_tointegerx(state, -2, &ok);
if (!ok)
luaL_argerror(state, 1, "material id is not a number");
int index = lua_tointegerx(state, -1, &ok);
if (!ok)
index = -1;
lua_settop(state, curtop);
return info->decode(type, index);
}
}
static int dfhack_matinfo_decode(lua_State *state)
{
MaterialInfo info;
decode_matinfo(state, &info, true);
Lua::Push(state, info);
return 1;
}
static int dfhack_matinfo_getToken(lua_State *state)
{
MaterialInfo info;
decode_matinfo(state, &info, true);
auto str = info.getToken();
lua_pushstring(state, str.c_str());
return 1;
}
static int dfhack_matinfo_toString(lua_State *state)
{
MaterialInfo info;
decode_matinfo(state, &info);
lua_settop(state, 3);
auto str = info.toString(luaL_optint(state, 2, 10015), lua_toboolean(state, 3));
lua_pushstring(state, str.c_str());
return 1;
}
static int dfhack_matinfo_getCraftClass(lua_State *state)
{
MaterialInfo info;
if (decode_matinfo(state, &info, true))
lua_pushinteger(state, info.getCraftClass());
else
lua_pushnil(state);
return 1;
}
static int dfhack_matinfo_matches(lua_State *state)
{
MaterialInfo info;
if (!decode_matinfo(state, &info))
luaL_argerror(state, 1, "material info object expected");
luaL_checkany(state, 2);
if (lua_isuserdata(state, 2))
{
if (auto mc = Lua::GetDFObject<df::job_material_category>(state, 2))
lua_pushboolean(state, info.matches(*mc));
else if (auto mc = Lua::GetDFObject<df::dfhack_material_category>(state, 2))
lua_pushboolean(state, info.matches(*mc));
else if (auto mc = Lua::GetDFObject<df::job_item>(state, 2))
lua_pushboolean(state, info.matches(*mc));
else
luaL_argerror(state, 2, "material category object expected");
}
else if (lua_istable(state, 2))
{
df::dfhack_material_category tmp;
Lua::CheckDFAssign(state, &tmp, 2, false);
lua_pushboolean(state, info.matches(tmp));
}
else
luaL_argerror(state, 2, "material category object expected");
return 1;
}
static const luaL_Reg dfhack_matinfo_funcs[] = {
{ "find", dfhack_matinfo_find },
{ "decode", dfhack_matinfo_decode },
{ "getToken", dfhack_matinfo_getToken },
{ "toString", dfhack_matinfo_toString },
{ "getCraftClass", dfhack_matinfo_getCraftClass },
{ "matches", dfhack_matinfo_matches },
{ NULL, NULL }
};
static void OpenMatinfo(lua_State *state)
{
luaL_getsubtable(state, lua_gettop(state), "matinfo");
lua_dup(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_MATINFO_TOKEN);
lua_dup(state);
luaL_setfuncs(state, dfhack_matinfo_funcs, 1);
lua_dup(state);
lua_setfield(state, -2, "__index");
lua_pop(state, 1);
}
/**************
* Pen object *
**************/
static int DFHACK_PEN_TOKEN = 0;
void Lua::Push(lua_State *L, const Screen::Pen &info)
{
if (!info.valid())
{
lua_pushnil(L);
return;
}
new (L) Pen(info);
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN);
lua_setmetatable(L, -2);
}
static Pen *check_pen_native(lua_State *L, int index)
{
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN);
if (!lua_getmetatable(L, index) || !lua_rawequal(L, -1, -2))
luaL_argerror(L, index, "not a pen object");
lua_pop(L, 2);
return (Pen*)lua_touserdata(L, index);
}
void Lua::CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil, bool allow_color)
{
index = lua_absindex(L, index);
luaL_checkany(L, index);
if (lua_isnil(L, index))
{
if (!allow_nil)
luaL_argerror(L, index, "nil pen not allowed");
*pen = Pen(0,0,0,-1);
}
else if (lua_isuserdata(L, index))
{
*pen = *check_pen_native(L, index);
}
else if (allow_color && lua_isnumber(L, index))
{
*pen = Pen(0, lua_tointeger(L, index)&15, 0);
}
else
{
luaL_checktype(L, index, LUA_TTABLE);
decode_pen(L, *pen, index);
}
}
static int adjust_pen(lua_State *L, bool no_copy)
{
lua_settop(L, 4);
Pen pen;
int iidx = 1;
Lua::CheckPen(L, &pen, 1, true, true);
if (!lua_isnil(L, 2) || !lua_isnil(L, 3) || !lua_isnil(L, 4))
{
if (lua_isnumber(L, 2) || lua_isnil(L, 2))
{
if (!pen.valid())
pen = Pen();
iidx = -1;
pen.fg = luaL_optint(L, 2, pen.fg) & 15;
pen.bg = luaL_optint(L, 3, pen.bg);
if (!lua_isnil(L, 4))
pen.bold = lua_toboolean(L, 4);
else if (!lua_isnil(L, 2))
{
pen.bold = !!(pen.fg & 8);
pen.fg &= 7;
}
}
else
{
iidx = 2;
Lua::CheckPen(L, &pen, 2, false, false);
}
}
if (no_copy && iidx > 0 && lua_isuserdata(L, iidx))
lua_pushvalue(L, iidx);
else
Lua::Push(L, pen);
return 1;
}
static int dfhack_pen_parse(lua_State *L)
{
return adjust_pen(L, true);
}
static int dfhack_pen_make(lua_State *L)
{
return adjust_pen(L, false);
}
static void make_pen_table(lua_State *L, Pen &pen)
{
if (!pen.valid())
luaL_error(L, "invalid pen state");
else
{
lua_newtable(L);
lua_pushinteger(L, (unsigned char)pen.ch); lua_setfield(L, -2, "ch");
lua_pushinteger(L, pen.fg); lua_setfield(L, -2, "fg");
lua_pushinteger(L, pen.bg); lua_setfield(L, -2, "bg");
lua_pushboolean(L, pen.bold); lua_setfield(L, -2, "bold");
if (pen.tile)
{
lua_pushinteger(L, pen.tile); lua_setfield(L, -2, "tile");
}
switch (pen.tile_mode) {
case Pen::CharColor:
lua_pushboolean(L, true); lua_setfield(L, -2, "tile_color");
break;
case Pen::TileColor:
lua_pushinteger(L, pen.tile_fg); lua_setfield(L, -2, "tile_fg");
lua_pushinteger(L, pen.tile_bg); lua_setfield(L, -2, "tile_bg");
break;
default:
lua_pushboolean(L, false); lua_setfield(L, -2, "tile_color");
break;
}
if (pen.keep_lower) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "keep_lower");
}
if (pen.write_to_lower) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "write_to_lower");
}
if (pen.top_of_text) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "top_of_text");
}
if (pen.bottom_of_text) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "bottom_of_text");
}
}
}
static void get_pen_mirror(lua_State *L, int idx)
{
lua_getuservalue(L, idx);
if (lua_isnil(L, -1))
{
lua_pop(L, 1);
Pen pen;
Lua::CheckPen(L, &pen, idx, false, false);
make_pen_table(L, pen);
lua_dup(L);
lua_setuservalue(L, idx);
}
}
static int dfhack_pen_index(lua_State *L)
{
lua_settop(L, 2);
luaL_checktype(L, 1, LUA_TUSERDATA);
// check metatable
if (!lua_getmetatable(L, 1))
luaL_argerror(L, 1, "must be a pen");
lua_pushvalue(L, 2);
lua_rawget(L, -2);
if (!lua_isnil(L, -1))
return 1;
// otherwise read from the mirror table, creating it if necessary
lua_settop(L, 2);
get_pen_mirror(L, 1);
lua_pushvalue(L, 2);
lua_rawget(L, -2);
return 1;
}
static int pen_pnext(lua_State *L)
{
lua_settop(L, 2); /* create a 2nd argument if there isn't one */
if (lua_next(L, lua_upvalueindex(1)))
return 2;
lua_pushnil(L);
return 1;
}
static int dfhack_pen_pairs(lua_State *L)
{
luaL_checktype(L, 1, LUA_TUSERDATA);
get_pen_mirror(L, 1);
lua_pushcclosure(L, pen_pnext, 1);
lua_pushnil(L);
lua_pushnil(L);
return 3;
}
const char *const pen_fields[] = {
"ch", "fg", "bold", "bg", "tile", "tile_color", "tile_fg", "tile_bg", NULL
};
static int dfhack_pen_newindex(lua_State *L)
{
lua_settop(L, 3);
luaL_checktype(L, 1, LUA_TUSERDATA);
int id = luaL_checkoption(L, 2, NULL, pen_fields);
int arg = 0;
Pen &pen = *check_pen_native(L, 1);
bool wipe_tile = false, wipe_tc = false;
switch (id) {
case 0:
if (lua_type(L, 3) != LUA_TNUMBER)
arg = (unsigned char)*luaL_checkstring(L, 3);
else
arg = luaL_checkint(L, 3);
pen.ch = arg;
lua_pushinteger(L, (unsigned char)pen.ch);
break;
case 1:
pen.fg = luaL_checkint(L, 3) & 15;
lua_pushinteger(L, pen.fg);
break;
case 2:
pen.bold = lua_toboolean(L, 3);
lua_pushboolean(L, pen.bold);
break;
case 3:
pen.bg = luaL_checkint(L, 3) & 15;
lua_pushinteger(L, pen.bg);
break;
case 4:
arg = lua_isnil(L, 3) ? 0 : luaL_checkint(L, 3);
if (arg < 0)
luaL_argerror(L, 3, "invalid tile index");
pen.tile = arg;
if (pen.tile)
lua_pushinteger(L, pen.tile);
else
lua_pushnil(L);
break;
case 5:
wipe_tile = (pen.tile_mode == Pen::TileColor);
pen.tile_mode = lua_toboolean(L, 3) ? Pen::CharColor : Pen::AsIs;
lua_pushboolean(L, pen.tile_mode == Pen::CharColor);
break;
case 6:
if (pen.tile_mode != Pen::TileColor) { wipe_tc = true; pen.tile_bg = 0; }
pen.tile_fg = luaL_checkint(L, 3) & 15;
pen.tile_mode = Pen::TileColor;
lua_pushinteger(L, pen.tile_fg);
break;
case 7:
if (pen.tile_mode != Pen::TileColor) { wipe_tc = true; pen.tile_fg = 7; }
pen.tile_bg = luaL_checkint(L, 3) & 15;
pen.tile_mode = Pen::TileColor;
lua_pushinteger(L, pen.tile_bg);
break;
}
lua_getuservalue(L, 1);
if (!lua_isnil(L, -1))
{
lua_remove(L, 3);
lua_insert(L, 2);
lua_rawset(L, 2);
if (wipe_tc) {
lua_pushnil(L); lua_setfield(L, 2, "tile_color");
lua_pushinteger(L, pen.tile_fg); lua_setfield(L, 2, "tile_fg");
lua_pushinteger(L, pen.tile_bg); lua_setfield(L, 2, "tile_bg");
}
if (wipe_tile) {
lua_pushnil(L); lua_setfield(L, 2, "tile_fg");
lua_pushnil(L); lua_setfield(L, 2, "tile_bg");
}
}
return 0;
}
static const luaL_Reg dfhack_pen_funcs[] = {
{ "parse", dfhack_pen_parse },
{ "make", dfhack_pen_make },
{ "__index", dfhack_pen_index },
{ "__pairs", dfhack_pen_pairs },
{ "__newindex", dfhack_pen_newindex },
{ NULL, NULL }
};
static void OpenPen(lua_State *state)
{
luaL_getsubtable(state, lua_gettop(state), "pen");
lua_dup(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN);
luaL_setfuncs(state, dfhack_pen_funcs, 0);
lua_pop(state, 1);
}
/******************
* PenArray object *
******************/
static int DFHACK_PENARRAY_TOKEN = 0;
using Screen::PenArray;
static PenArray *check_penarray_native(lua_State *L, int index)
{
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PENARRAY_TOKEN);
if (!lua_getmetatable(L, index) || !lua_rawequal(L, -1, -2))
luaL_argerror(L, index, "not a penarray object");
lua_pop(L, 2);
return (PenArray*)lua_touserdata(L, index);
}
static int dfhack_penarray_new(lua_State *L)
{
int bufwidth = luaL_checkint(L, 1);
int bufheight = luaL_checkint(L, 2);
void *buf = lua_newuserdata(L, sizeof(PenArray) + (sizeof(Pen) * bufwidth * bufheight));
new (buf) PenArray(bufwidth, bufheight, buf);
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PENARRAY_TOKEN);
lua_setmetatable(L, -2);
return 1;
}
static int dfhack_penarray_clear(lua_State *L)
{
PenArray *parr = check_penarray_native(L, 1);
parr->clear();
return 0;
}
static int dfhack_penarray_get_dims(lua_State *L)
{
PenArray *parr = check_penarray_native(L, 1);
lua_pushinteger(L, parr->get_dimx());
lua_pushinteger(L, parr->get_dimy());
return 2;
}
static int dfhack_penarray_get_tile(lua_State *L)
{
PenArray *parr = check_penarray_native(L, 1);
unsigned int x = luaL_checkint(L, 2);
unsigned int y = luaL_checkint(L, 3);
if (x < parr->get_dimx() && y < parr->get_dimy())
{
Pen pen = parr->get_tile(x, y);
Lua::Push(L, pen);
}
else
{
lua_pushnil(L);
}
return 1;
}
static int dfhack_penarray_set_tile(lua_State *L)
{
PenArray *parr = check_penarray_native(L, 1);
unsigned int x = luaL_checkint(L, 2);
unsigned int y = luaL_checkint(L, 3);
Pen pen;
Lua::CheckPen(L, &pen, 4);
parr->set_tile(x, y, pen);
return 0;
}
static int dfhack_penarray_draw(lua_State *L)
{
PenArray *parr = check_penarray_native(L, 1);
unsigned int x = (unsigned int)luaL_checkint(L, 2);
unsigned int y = (unsigned int)luaL_checkint(L, 3);
unsigned int w = (unsigned int)luaL_checkint(L, 4);
unsigned int h = (unsigned int)luaL_checkint(L, 5);
unsigned int bufx = (unsigned int)luaL_optint(L, 6, 0);
unsigned int bufy = (unsigned int)luaL_optint(L, 7, 0);
parr->draw(x, y, w, h, bufx, bufy);
return 0;
}
static const luaL_Reg dfhack_penarray_funcs[] = {
{ "new", dfhack_penarray_new },
{ "clear", dfhack_penarray_clear },
{ "get_dims", dfhack_penarray_get_dims },
{ "get_tile", dfhack_penarray_get_tile },
{ "set_tile", dfhack_penarray_set_tile },
{ "draw", dfhack_penarray_draw },
{ NULL, NULL }
};
static void OpenPenArray(lua_State *state)
{
luaL_getsubtable(state, lua_gettop(state), "penarray");
lua_dup(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_PENARRAY_TOKEN);
luaL_setfuncs(state, dfhack_penarray_funcs, 0);
lua_pop(state, 1);
}
/********************
* Random generator *
********************/
static int DFHACK_RANDOM_TOKEN = 0;
static MersenneRNG *check_random_native(lua_State *L, int index)
{
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_RANDOM_TOKEN);
if (!lua_getmetatable(L, index) || !lua_rawequal(L, -1, -2))
luaL_argerror(L, index, "not a random generator object");
lua_pop(L, 2);
return (MersenneRNG*)lua_touserdata(L, index);
}
static int dfhack_random_init(lua_State *L)
{
lua_settop(L, 3);
MersenneRNG *prng = check_random_native(L, 1);
if (lua_isnil(L, 2))
prng->init();
else
{
std::vector<uint32_t> data;
int tcnt = luaL_optint(L, 3, 1);
if (lua_isnumber(L, 2))
data.push_back(lua_tounsigned(L, 2));
else if (lua_istable(L, 2))
{
int cnt = lua_rawlen(L, 2);
if (cnt <= 0)
luaL_argerror(L, 2, "empty list in dfhack.random.init");
for (int i = 1; i <= cnt; i++)
{
lua_rawgeti(L, 2, i);
if (!lua_isnumber(L, -1))
luaL_argerror(L, 2, "not a number in dfhack.random.init argument");
data.push_back(lua_tounsigned(L, -1));
lua_pop(L, 1);
}
}
else
luaL_argerror(L, 2, "dfhack.random.init argument not number or table");
prng->init(data.data(), data.size(), tcnt);
}
lua_settop(L, 1);
return 1;
}
static int dfhack_random_new(lua_State *L)
{
new (L) MersenneRNG();
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_RANDOM_TOKEN);
lua_setmetatable(L, -2);
lua_insert(L, 1);
return dfhack_random_init(L);
}
static int dfhack_random_random(lua_State *L)
{
MersenneRNG *prng = check_random_native(L, 1);
lua_settop(L, 2);
if (lua_gettop(L) < 2 || lua_isnil(L, 2))
lua_pushunsigned(L, prng->random());
else
lua_pushunsigned(L, prng->random(luaL_optunsigned(L, 2, 0)));
return 1;
}
static int dfhack_random_drandom(lua_State *L)
{
lua_pushnumber(L, check_random_native(L, 1)->drandom());
return 1;
}
static int dfhack_random_drandom0(lua_State *L)
{
lua_pushnumber(L, check_random_native(L, 1)->drandom0());
return 1;
}
static int dfhack_random_drandom1(lua_State *L)
{
lua_pushnumber(L, check_random_native(L, 1)->drandom1());
return 1;
}
static int dfhack_random_unitrandom(lua_State *L)
{
lua_pushnumber(L, check_random_native(L, 1)->unitrandom());
return 1;
}
static int dfhack_random_unitvector(lua_State *L)
{
MersenneRNG *prng = check_random_native(L, 1);
int size = luaL_optint(L, 2, 3);
if (size <= 0 || size > 32)
luaL_argerror(L, 2, "vector size must be positive");
luaL_checkstack(L, size, "not enough stack in dfhack.random.unitvector");
std::vector<double> buf(size);
prng->unitvector(buf.data(), size);
for (int i = 0; i < size; i++)
lua_pushnumber(L, buf[i]);
return size;
}
static int eval_perlin_1(lua_State *L)
{
auto &gen = *(PerlinNoise1D<float>*)lua_touserdata(L, lua_upvalueindex(1));
lua_pushnumber(L, gen(luaL_checknumber(L, 1)));
return 1;
}
static int eval_perlin_2(lua_State *L)
{
auto &gen = *(PerlinNoise2D<float>*)lua_touserdata(L, lua_upvalueindex(1));
lua_pushnumber(L, gen(luaL_checknumber(L, 1), luaL_checknumber(L, 2)));
return 1;
}
static int eval_perlin_3(lua_State *L)
{
auto &gen = *(PerlinNoise3D<float>*)lua_touserdata(L, lua_upvalueindex(1));
lua_pushnumber(L, gen(luaL_checknumber(L, 1), luaL_checknumber(L, 2), luaL_checknumber(L, 3)));
return 1;
}
static int dfhack_random_perlin(lua_State *L)
{
MersenneRNG *prng = check_random_native(L, 1);
int size = luaL_optint(L, 2, 3);
switch (size)
{
case 1: {
auto pdata = new (L) PerlinNoise1D<float>();
pdata->init(*prng);
lua_pushcclosure(L, eval_perlin_1, 1);
break;
}
case 2: {
auto pdata = new (L) PerlinNoise2D<float>();
pdata->init(*prng);
lua_pushcclosure(L, eval_perlin_2, 1);
break;
}
case 3: {
auto pdata = new (L) PerlinNoise3D<float>();
pdata->init(*prng);
lua_pushcclosure(L, eval_perlin_3, 1);
break;
}
default:
luaL_argerror(L, 2, "perlin noise dimension must be 1, 2 or 3");
}
return 1;
}
static const luaL_Reg dfhack_random_funcs[] = {
{ "new", dfhack_random_new },
{ "init", dfhack_random_init },
{ "random", dfhack_random_random },
{ "drandom", dfhack_random_drandom },
{ "drandom0", dfhack_random_drandom0 },
{ "drandom1", dfhack_random_drandom1 },
{ "unitrandom", dfhack_random_unitrandom },
{ "unitvector", dfhack_random_unitvector },
{ "perlin", dfhack_random_perlin },
{ NULL, NULL }
};
static void OpenRandom(lua_State *state)
{
luaL_getsubtable(state, lua_gettop(state), "random");
lua_dup(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_RANDOM_TOKEN);
luaL_setfuncs(state, dfhack_random_funcs, 0);
lua_pop(state, 1);
}
/*********************************
* Commandline history repository *
**********************************/
static std::map<std::string, CommandHistory> commandHistories;
static CommandHistory * ensureCommandHistory(std::string id,
std::string src_file) {
if (!commandHistories.count(id)) {
commandHistories[id].load(src_file.c_str());
}
return &commandHistories[id];
}
static int getCommandHistory(lua_State *state)
{
std::string id = luaL_checkstring(state, 1);
std::string src_file = luaL_checkstring(state, 2);
std::vector<std::string> entries;
ensureCommandHistory(id, src_file)->getEntries(entries);
Lua::PushVector(state, entries);
return 1;
}
static void addCommandToHistory(std::string id, std::string src_file,
std::string command) {
CommandHistory *history = ensureCommandHistory(id, src_file);
history->add(command);
history->save(src_file.c_str());
}
/************************
* Wrappers for C++ API *
************************/
static void OpenModule(lua_State *state, const char *mname,
const LuaWrapper::FunctionReg *reg, const luaL_Reg *reg2 = NULL)
{
luaL_getsubtable(state, lua_gettop(state), mname);
LuaWrapper::SetFunctionWrappers(state, reg);
if (reg2)
luaL_setfuncs(state, reg2, 0);
lua_pop(state, 1);
}
static void OpenModule(lua_State *state, const char *mname, const luaL_Reg *reg2)
{
luaL_getsubtable(state, lua_gettop(state), mname);
luaL_setfuncs(state, reg2, 0);
lua_pop(state, 1);
}
#define WRAPM(module, function) { #function, df::wrap_function(module::function,true) }
#define WRAP(function) { #function, df::wrap_function(function,true) }
#define WRAPN(name, function) { #name, df::wrap_function(function,true) }
#define CWRAP(name, function) { #name, &Lua::CallWithCatchWrapper<function> }
/***** DFHack module *****/
static std::string getOSType()
{
switch (Core::getInstance().vinfo->getOS())
{
case OS_WINDOWS:
return "windows";
case OS_LINUX:
return "linux";
case OS_APPLE:
return "darwin";
default:
return "unknown";
}
}
static int getArchitecture()
{
return sizeof(void*) * 8;
}
static std::string getArchitectureName()
{
return getArchitecture() == 64 ? "x86_64" : "x86";
}
static std::string getDFVersion() { return Core::getInstance().vinfo->getVersion(); }
static uint32_t getTickCount() { return Core::getInstance().p->getTickCount(); }
static std::string getDFPath() { return Core::getInstance().p->getPath(); }
static std::string getHackPath() { return Core::getInstance().getHackPath(); }
static bool isWorldLoaded() { return Core::getInstance().isWorldLoaded(); }
static bool isMapLoaded() { return Core::getInstance().isMapLoaded(); }
static std::string df2utf(std::string s) { return DF2UTF(s); }
static std::string utf2df(std::string s) { return UTF2DF(s); }
static std::string df2console(color_ostream &out, std::string s) { return DF2CONSOLE(out, s); }
static std::string toSearchNormalized(std::string s) { return to_search_normalized(s); }
#define WRAP_VERSION_FUNC(name, function) WRAPN(name, DFHack::Version::function)
static const LuaWrapper::FunctionReg dfhack_module[] = {
WRAP(getOSType),
WRAP(getArchitecture),
WRAP(getArchitectureName),
WRAP(getDFVersion),
WRAP(getDFPath),
WRAP(getTickCount),
WRAP(getHackPath),
WRAP(isWorldLoaded),
WRAP(isMapLoaded),
WRAPM(Translation, TranslateName),
WRAP(df2utf),
WRAP(utf2df),
WRAP(df2console),
WRAP(toSearchNormalized),
WRAP_VERSION_FUNC(getDFHackVersion, dfhack_version),
WRAP_VERSION_FUNC(getDFHackRelease, dfhack_release),
WRAP_VERSION_FUNC(getDFHackBuildID, dfhack_build_id),
WRAP_VERSION_FUNC(getCompiledDFVersion, df_version),
WRAP_VERSION_FUNC(getGitDescription, git_description),
WRAP_VERSION_FUNC(getGitCommit, git_commit),
WRAP_VERSION_FUNC(getGitXmlCommit, git_xml_commit),
WRAP_VERSION_FUNC(getGitXmlExpectedCommit, git_xml_expected_commit),
WRAP_VERSION_FUNC(gitXmlMatch, git_xml_match),
WRAP_VERSION_FUNC(isRelease, is_release),
WRAP_VERSION_FUNC(isPrerelease, is_prerelease),
WRAP(addCommandToHistory),
{ NULL, NULL }
};
static const luaL_Reg dfhack_funcs[] = {
{ "getCommandHistory", getCommandHistory },
{ NULL, NULL }
};
/***** Gui module *****/
static int gui_getDwarfmodeViewDims(lua_State *state)
{
auto dims = Gui::getDwarfmodeViewDims();
lua_newtable(state);
Lua::TableInsert(state, "map_x1", dims.map_x1);
Lua::TableInsert(state, "map_x2", dims.map_x2);
Lua::TableInsert(state, "map_y1", dims.map_y1);
Lua::TableInsert(state, "map_y2", dims.map_y2);
return 1;
}
static int gui_getMousePos(lua_State *L)
{
bool allow_out_of_bounds = lua_toboolean(L, 1);
df::coord pos = Gui::getMousePos(allow_out_of_bounds);
if ((allow_out_of_bounds && pos.z >= 0) || pos.isValid())
Lua::Push(L, pos);
else
lua_pushnil(L);
return 1;
}
static const LuaWrapper::FunctionReg dfhack_gui_module[] = {
WRAPM(Gui, getCurViewscreen),
WRAPM(Gui, getDFViewscreen),
WRAPM(Gui, getSelectedWorkshopJob),
WRAPM(Gui, getSelectedJob),
WRAPM(Gui, getSelectedUnit),
WRAPM(Gui, getSelectedItem),
WRAPM(Gui, getSelectedBuilding),
WRAPM(Gui, getSelectedCivZone),
WRAPM(Gui, getSelectedStockpile),
WRAPM(Gui, getSelectedPlant),
WRAPM(Gui, getAnyUnit),
WRAPM(Gui, getAnyItem),
WRAPM(Gui, getAnyBuilding),
WRAPM(Gui, getAnyCivZone),
WRAPM(Gui, getAnyStockpile),
WRAPM(Gui, getAnyPlant),
WRAPM(Gui, writeToGamelog),
WRAPM(Gui, makeAnnouncement),
WRAPM(Gui, addCombatReport),
WRAPM(Gui, addCombatReportAuto),
WRAPM(Gui, showAnnouncement),
WRAPM(Gui, showZoomAnnouncement),
WRAPM(Gui, showPopupAnnouncement),
WRAPM(Gui, showAutoAnnouncement),
WRAPM(Gui, resetDwarfmodeView),
WRAPM(Gui, refreshSidebar),
WRAPM(Gui, inRenameBuilding),
WRAPM(Gui, getDepthAt),
WRAPM(Gui, matchFocusString),
{ NULL, NULL }
};
static int gui_getFocusStrings(lua_State *state) {
df::viewscreen *r = Lua::GetDFObject<df::viewscreen>(state, 1);
std::vector<std::string> focusStrings = Gui::getFocusStrings(r);
Lua::PushVector(state, focusStrings);
return 1;
}
static int gui_getCurFocus(lua_State *state) {
bool skip_dismissed = lua_toboolean(state, 1);
std::vector<std::string> cur_focus = Gui::getCurFocus(skip_dismissed);
Lua::PushVector(state, cur_focus);
return 1;
}
static int gui_autoDFAnnouncement(lua_State *state)
{
bool rv;
df::announcement_infost *r = Lua::GetDFObject<df::announcement_infost>(state, 1);
if (r)
{
std::string message = luaL_checkstring(state, 2);
rv = Gui::autoDFAnnouncement(*r, message);
}
else
{
df::coord pos;
int color = 0; // initialize these to prevent warning
bool bright = false, is_sparring = false;
df::unit *unit1 = NULL, *unit2 = NULL;
auto type = (df::announcement_type)lua_tointeger(state, 1);
Lua::CheckDFAssign(state, &pos, 2);
std::string message = luaL_checkstring(state, 3);
switch (lua_gettop(state))
{
default:
case 8:
is_sparring = lua_toboolean(state, 8);
case 7:
unit2 = Lua::CheckDFObject<df::unit>(state, 7);
case 6:
unit1 = Lua::CheckDFObject<df::unit>(state, 6);
case 5:
bright = lua_toboolean(state, 5);
case 4:
color = lua_tointeger(state, 4);
case 3:
break;
}
switch (lua_gettop(state))
{ // Use the defaults in Gui.h
default:
case 8:
rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1, unit2, is_sparring);
break;
case 7:
rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1, unit2);
break;
case 6:
rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1);
break;
case 5:
rv = Gui::autoDFAnnouncement(type, pos, message, color, bright);
break;
case 4:
rv = Gui::autoDFAnnouncement(type, pos, message, color);
break;
case 3:
rv = Gui::autoDFAnnouncement(type, pos, message);
}
}
lua_pushboolean(state, rv);
return 1;
}
static int gui_pauseRecenter(lua_State *state)
{
bool rv;
df::coord p;
switch (lua_gettop(state))
{
default:
case 4:
rv = Gui::pauseRecenter(CheckCoordXYZ(state, 1, false), lua_toboolean(state, 4));
break;
case 3:
rv = Gui::pauseRecenter(CheckCoordXYZ(state, 1, false));
break;
case 2:
Lua::CheckDFAssign(state, &p, 1);
rv = Gui::pauseRecenter(p, lua_toboolean(state, 2));
break;
case 1:
Lua::CheckDFAssign(state, &p, 1);
rv = Gui::pauseRecenter(p);
}
lua_pushboolean(state, rv);
return 1;
}
static int gui_revealInDwarfmodeMap(lua_State *state)
{
bool rv;
df::coord p;
switch (lua_gettop(state))
{
default:
case 5:
rv = Gui::revealInDwarfmodeMap(CheckCoordXYZ(state, 1, false), lua_toboolean(state, 4), lua_toboolean(state, 5));
break;
case 4:
rv = Gui::revealInDwarfmodeMap(CheckCoordXYZ(state, 1, false), lua_toboolean(state, 4));
break;
case 3:
if (lua_isboolean(state, 3)) {
Lua::CheckDFAssign(state, &p, 1);
rv = Gui::revealInDwarfmodeMap(p, lua_toboolean(state, 2), lua_toboolean(state, 3));
}
else
rv = Gui::revealInDwarfmodeMap(CheckCoordXYZ(state, 1, false));
break;
case 2:
Lua::CheckDFAssign(state, &p, 1);
rv = Gui::revealInDwarfmodeMap(p, lua_toboolean(state, 2));
break;
case 1:
Lua::CheckDFAssign(state, &p, 1);
rv = Gui::revealInDwarfmodeMap(p);
}
lua_pushboolean(state, rv);
return 1;
}
static const luaL_Reg dfhack_gui_funcs[] = {
{ "autoDFAnnouncement", gui_autoDFAnnouncement },
{ "getDwarfmodeViewDims", gui_getDwarfmodeViewDims },
{ "pauseRecenter", gui_pauseRecenter },
{ "revealInDwarfmodeMap", gui_revealInDwarfmodeMap },
{ "getMousePos", gui_getMousePos },
{ "getFocusStrings", gui_getFocusStrings },
{ "getCurFocus", gui_getCurFocus },
{ NULL, NULL }
};
/***** Job module *****/
static bool jobEqual(const df::job *job1, const df::job *job2)
{
CHECK_NULL_POINTER(job1);
CHECK_NULL_POINTER(job2);
return *job1 == *job2;
}
static bool jobItemEqual(const df::job_item *job1, const df::job_item *job2)
{
CHECK_NULL_POINTER(job1);
CHECK_NULL_POINTER(job2);
return *job1 == *job2;
}
static const LuaWrapper::FunctionReg dfhack_job_module[] = {
WRAPM(Job,attachJobItem),
WRAPM(Job,cloneJobStruct),
WRAPM(Job,printItemDetails),
WRAPM(Job,printJobDetails),
WRAPM(Job,getGeneralRef),
WRAPM(Job,getSpecificRef),
WRAPM(Job,getHolder),
WRAPM(Job,getWorker),
WRAPM(Job,setJobCooldown),
WRAPM(Job,removeWorker),
WRAPM(Job,checkBuildingsNow),
WRAPM(Job,checkDesignationsNow),
WRAPM(Job,isSuitableItem),
WRAPM(Job,isSuitableMaterial),
WRAPM(Job,getName),
WRAPM(Job,linkIntoWorld),
WRAPM(Job,removePostings),
WRAPM(Job,disconnectJobItem),
WRAPM(Job,disconnectJobGeneralRef),
WRAPM(Job,removeJob),
WRAPN(is_equal, jobEqual),
WRAPN(is_item_equal, jobItemEqual),
{ NULL, NULL }
};
static int job_listNewlyCreated(lua_State *state)
{
int nxid = luaL_checkint(state, 1);
lua_settop(state, 1);
std::vector<df::job*> pvec;
if (Job::listNewlyCreated(&pvec, &nxid))
{
lua_pushinteger(state, nxid);
Lua::PushVector(state, pvec);
return 2;
}
else
return 1;
}
static const luaL_Reg dfhack_job_funcs[] = {
{ "listNewlyCreated", job_listNewlyCreated },
{ NULL, NULL }
};
/***** Textures module *****/
static int textures_loadTileset(lua_State *state)
{
std::string file = luaL_checkstring(state, 1);
auto tile_w = luaL_checkint(state, 2);
auto tile_h = luaL_checkint(state, 3);
bool reserved = lua_isboolean(state, 4) ? lua_toboolean(state, 4) : false;
auto handles = Textures::loadTileset(file, tile_w, tile_h, reserved);
Lua::PushVector(state, handles);
return 1;
}
static int textures_getTexposByHandle(lua_State *state)
{
auto handle = luaL_checkunsigned(state, 1);
auto texpos = Textures::getTexposByHandle(handle);
if (texpos == -1) {
lua_pushnil(state);
} else {
Lua::Push(state, texpos);
}
return 1;
}
static int textures_deleteHandle(lua_State *state)
{
if (lua_isinteger(state,1)) {
auto handle = luaL_checkunsigned(state, 1);
Textures::deleteHandle(handle);
} else if (lua_istable(state,1)) {
std::vector<TexposHandle> handles;
Lua::GetVector(state, handles);
for (auto& handle: handles) {
Textures::deleteHandle(handle);
}
}
return 0;
}
static int textures_createTile(lua_State *state)
{
std::vector<uint32_t> pixels;
Lua::GetVector(state, pixels);
auto tile_w = luaL_checkint(state, 2);
auto tile_h = luaL_checkint(state, 3);
bool reserved = lua_isboolean(state, 4) ? lua_toboolean(state, 4) : false;
auto handle = Textures::createTile(pixels, tile_w, tile_h, reserved);
Lua::Push(state, handle);
return 1;
}
static int textures_createTileset(lua_State *state)
{
std::vector<uint32_t> pixels;
Lua::GetVector(state, pixels);
auto texture_w = luaL_checkint(state, 2);
auto texture_h = luaL_checkint(state, 3);
auto tile_w = luaL_checkint(state, 4);
auto tile_h = luaL_checkint(state, 5);
bool reserved = lua_isboolean(state, 6) ? lua_toboolean(state, 6) : false;
auto handles = Textures::createTileset(pixels, texture_w, texture_h, tile_w, tile_h, reserved);
Lua::PushVector(state, handles);
return 1;
}
static const luaL_Reg dfhack_textures_funcs[] = {
{ "loadTileset", textures_loadTileset },
{ "getTexposByHandle", textures_getTexposByHandle },
{ "deleteHandle", textures_deleteHandle },
{ "createTile", textures_createTile },
{ "createTileset", textures_createTileset },
{ NULL, NULL }
};
/***** Units module *****/
static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, isUnitInBox),
WRAPM(Units, isActive),
WRAPM(Units, isVisible),
WRAPM(Units, isCitizen),
WRAPM(Units, isFortControlled),
WRAPM(Units, isOwnCiv),
WRAPM(Units, isOwnGroup),
WRAPM(Units, isOwnRace),
WRAPM(Units, isAlive),
WRAPM(Units, isDead),
WRAPM(Units, isKilled),
WRAPM(Units, isSane),
WRAPM(Units, isCrazed),
WRAPM(Units, isGhost),
WRAPM(Units, isHidden),
WRAPM(Units, isHidingCurse),
WRAPM(Units, isMale),
WRAPM(Units, isFemale),
WRAPM(Units, isBaby),
WRAPM(Units, isChild),
WRAPM(Units, isAdult),
WRAPM(Units, isGay),
WRAPM(Units, isNaked),
WRAPM(Units, isVisiting),
WRAPM(Units, isTrainableHunting),
WRAPM(Units, isTrainableWar),
WRAPM(Units, isTrained),
WRAPM(Units, isHunter),
WRAPM(Units, isWar),
WRAPM(Units, isTame),
WRAPM(Units, isTamable),
WRAPM(Units, isDomesticated),
WRAPM(Units, isMarkedForTraining),
WRAPM(Units, isMarkedForTaming),
WRAPM(Units, isMarkedForWarTraining),
WRAPM(Units, isMarkedForHuntTraining),
WRAPM(Units, isMarkedForSlaughter),
WRAPM(Units, isMarkedForGelding),
WRAPM(Units, isGeldable),
WRAPM(Units, isGelded),
WRAPM(Units, isEggLayer),
WRAPM(Units, isEggLayerRace),
WRAPM(Units, isGrazer),
WRAPM(Units, isMilkable),
WRAPM(Units, isForest),
WRAPM(Units, isMischievous),
WRAPM(Units, isAvailableForAdoption),
WRAPM(Units, isPet),
WRAPM(Units, hasExtravision),
WRAPM(Units, isOpposedToLife),
WRAPM(Units, isBloodsucker),
WRAPM(Units, isDwarf),
WRAPM(Units, isAnimal),
WRAPM(Units, isMerchant),
WRAPM(Units, isDiplomat),
WRAPM(Units, isVisitor),
WRAPM(Units, isInvader),
WRAPM(Units, isUndead),
WRAPM(Units, isNightCreature),
WRAPM(Units, isSemiMegabeast),
WRAPM(Units, isMegabeast),
WRAPM(Units, isTitan),
WRAPM(Units, isDemon),
WRAPM(Units, isDanger),
WRAPM(Units, isGreatDanger),
WRAPM(Units, teleport),
WRAPM(Units, getGeneralRef),
WRAPM(Units, getSpecificRef),
WRAPM(Units, getContainer),
WRAPM(Units, setNickname),
WRAPM(Units, getVisibleName),
WRAPM(Units, getIdentity),
WRAPM(Units, getNemesis),
WRAPM(Units, getPhysicalAttrValue),
WRAPM(Units, getMentalAttrValue),
WRAPM(Units, casteFlagSet),
WRAPM(Units, getMiscTrait),
WRAPM(Units, getAge),
WRAPM(Units, getKillCount),
WRAPM(Units, getNominalSkill),
WRAPM(Units, getEffectiveSkill),
WRAPM(Units, getExperience),
WRAPM(Units, isValidLabor),
WRAPM(Units, setLaborValidity),
WRAPM(Units, computeMovementSpeed),
WRAPM(Units, computeSlowdownFactor),
WRAPM(Units, getProfessionName),
WRAPM(Units, getCasteProfessionName),
WRAPM(Units, getProfessionColor),
WRAPM(Units, getCasteProfessionColor),
WRAPM(Units, getGoalType),
WRAPM(Units, getGoalName),
WRAPM(Units, isGoalAchieved),
WRAPM(Units, getPhysicalDescription),
WRAPM(Units, getRaceName),
WRAPM(Units, getRaceNamePlural),
WRAPM(Units, getRaceNameById),
WRAPM(Units, getRaceBabyName),
WRAPM(Units, getRaceBabyNameById),
WRAPM(Units, getRaceChildName),
WRAPM(Units, getRaceChildNameById),
WRAPM(Units, getReadableName),
WRAPM(Units, getMainSocialActivity),
WRAPM(Units, getMainSocialEvent),
WRAPM(Units, getStressCategory),
WRAPM(Units, getStressCategoryRaw),
WRAPM(Units, subtractActionTimers),
WRAPM(Units, subtractGroupActionTimers),
WRAPM(Units, multiplyActionTimers),
WRAPM(Units, multiplyGroupActionTimers),
WRAPM(Units, setActionTimers),
WRAPM(Units, setGroupActionTimers),
WRAPM(Units, getUnitByNobleRole),
{ NULL, NULL }
};
static int units_getPosition(lua_State *state)
{
return Lua::PushPosXYZ(state, Units::getPosition(Lua::CheckDFObject<df::unit>(state,1)));
}
static int units_getOuterContainerRef(lua_State *state)
{
auto ref = Units::getOuterContainerRef(Lua::CheckDFObject<df::unit>(state, 1));
lua_newtable(state);
Lua::TableInsert(state, "type", ref.type);
switch (ref.type)
{
case specific_ref_type::UNIT:
Lua::TableInsert(state, "object", ref.data.unit);
break;
case specific_ref_type::ITEM_GENERAL:
Lua::TableInsert(state, "object", (df::item*)ref.data.object);
break;
case specific_ref_type::VERMIN_EVENT:
Lua::TableInsert(state, "object", ref.data.vermin);
break;
default:
Lua::TableInsert(state, "object", NULL);
}
return 1;
}
static int units_getNoblePositions(lua_State *state)
{
std::vector<Units::NoblePosition> np;
if (Units::getNoblePositions(&np, Lua::CheckDFObject<df::unit>(state,1)))
Lua::PushVector(state, np);
else
lua_pushnil(state);
return 1;
}
static int units_getUnitsInBox(lua_State *state)
{
std::vector<df::unit*> units;
int x1 = luaL_checkint(state, 1);
int y1 = luaL_checkint(state, 2);
int z1 = luaL_checkint(state, 3);
int x2 = luaL_checkint(state, 4);
int y2 = luaL_checkint(state, 5);
int z2 = luaL_checkint(state, 6);
bool ok = Units::getUnitsInBox(units, x1, y1, z1, x2, y2, z2);
if (ok && !lua_isnone(state, 7))
{
luaL_checktype(state, 7, LUA_TFUNCTION);
units.erase(std::remove_if(units.begin(), units.end(), [&state](df::unit *unit) -> bool {
// todo: merging this filter into the base function would be welcomed by plugins
// (it would also be faster, and less obfuscated than this [ie. erase(remove_if)])
lua_dup(state); // copy function
Lua::PushDFObject(state, unit);
lua_call(state, 1, 1);
bool ret = lua_toboolean(state, -1);
lua_pop(state, 1); // remove return value
return !ret;
}), units.end());
}
Lua::PushVector(state, units);
lua_pushboolean(state, ok);
return 2;
}
static int units_getCitizens(lua_State *L) {
bool ignore_sanity = lua_toboolean(L, -1); // defaults to false
std::vector<df::unit *> citizens;
if (Units::getCitizens(citizens, ignore_sanity)) {
Lua::PushVector(L, citizens);
return 1;
}
return 0;
}
static int units_getUnitsByNobleRole(lua_State *L) {
std::string role_name = luaL_checkstring(L, -1);
std::vector<df::unit *> units;
Units::getUnitsByNobleRole(units, role_name);
Lua::PushVector(L, units);
return 1;
}
static int units_getStressCutoffs(lua_State *L)
{
lua_newtable(L);
for (size_t i = 0; i < Units::stress_cutoffs.size(); i++)
Lua::TableInsert(L, i, Units::stress_cutoffs[i]);
return 1;
}
static const luaL_Reg dfhack_units_funcs[] = {
{ "getPosition", units_getPosition },
{ "getOuterContainerRef", units_getOuterContainerRef },
{ "getNoblePositions", units_getNoblePositions },
{ "getUnitsInBox", units_getUnitsInBox },
{ "getCitizens", units_getCitizens },
{ "getUnitsByNobleRole", units_getUnitsByNobleRole},
{ "getStressCutoffs", units_getStressCutoffs },
{ NULL, NULL }
};
/***** Military Module *****/
static const LuaWrapper::FunctionReg dfhack_military_module[] = {
WRAPM(Military, makeSquad),
WRAPM(Military, updateRoomAssignments),
WRAPM(Military, getSquadName),
{ NULL, NULL }
};
/***** Items module *****/
static bool items_moveToGround(df::item *item, df::coord pos)
{
MapExtras::MapCache mc;
return Items::moveToGround(mc, item, pos);
}
static bool items_moveToContainer(df::item *item, df::item *container)
{
MapExtras::MapCache mc;
return Items::moveToContainer(mc, item, container);
}
static bool items_moveToInventory
(df::item *item, df::unit *unit, df::unit_inventory_item::T_mode mode, int body_part)
{
MapExtras::MapCache mc;
return Items::moveToInventory(mc, item, unit, mode, body_part);
}
static bool items_remove(df::item *item, bool no_uncat)
{
MapExtras::MapCache mc;
return Items::remove(mc, item, no_uncat);
}
static df::proj_itemst *items_makeProjectile(df::item *item)
{
MapExtras::MapCache mc;
return Items::makeProjectile(mc, item);
}
static int16_t items_findType(std::string token)
{
DFHack::ItemTypeInfo result;
result.find(token);
return result.type;
}
static int32_t items_findSubtype(std::string token)
{
DFHack::ItemTypeInfo result;
result.find(token);
return result.subtype;
}
static const LuaWrapper::FunctionReg dfhack_items_module[] = {
WRAPM(Items, getGeneralRef),
WRAPM(Items, getSpecificRef),
WRAPM(Items, getOwner),
WRAPM(Items, setOwner),
WRAPM(Items, getContainer),
WRAPM(Items, getHolderBuilding),
WRAPM(Items, getHolderUnit),
WRAPM(Items, getBookTitle),
WRAPM(Items, getDescription),
WRAPM(Items, isCasteMaterial),
WRAPM(Items, getSubtypeCount),
WRAPM(Items, getSubtypeDef),
WRAPM(Items, getItemBaseValue),
WRAPM(Items, getValue),
WRAPM(Items, isRequestedTradeGood),
WRAPM(Items, createItem),
WRAPM(Items, checkMandates),
WRAPM(Items, canTrade),
WRAPM(Items, canTradeWithContents),
WRAPM(Items, canTradeAnyWithContents),
WRAPM(Items, markForTrade),
WRAPM(Items, isRouteVehicle),
WRAPM(Items, isSquadEquipment),
WRAPM(Items, getCapacity),
WRAPN(moveToGround, items_moveToGround),
WRAPN(moveToContainer, items_moveToContainer),
WRAPN(moveToInventory, items_moveToInventory),
WRAPN(makeProjectile, items_makeProjectile),
WRAPN(remove, items_remove),
WRAPN(findType, items_findType),
WRAPN(findSubtype, items_findSubtype),
{ NULL, NULL }
};
static int items_getPosition(lua_State *state)
{
return Lua::PushPosXYZ(state, Items::getPosition(Lua::CheckDFObject<df::item>(state,1)));
}
static int items_getOuterContainerRef(lua_State *state)
{
auto ref = Items::getOuterContainerRef(Lua::CheckDFObject<df::item>(state, 1));
lua_newtable(state);
Lua::TableInsert(state, "type", ref.type);
switch (ref.type)
{
case specific_ref_type::UNIT:
Lua::TableInsert(state, "object", ref.data.unit);
break;
case specific_ref_type::ITEM_GENERAL:
Lua::TableInsert(state, "object", (df::item*)ref.data.object);
break;
case specific_ref_type::VERMIN_EVENT:
Lua::TableInsert(state, "object", ref.data.vermin);
break;
default:
Lua::TableInsert(state, "object", NULL);
}
return 1;
}
static int items_getContainedItems(lua_State *state)
{
std::vector<df::item*> pvec;
Items::getContainedItems(Lua::CheckDFObject<df::item>(state,1),&pvec);
Lua::PushVector(state, pvec);
return 1;
}
static int items_moveToBuilding(lua_State *state)
{
MapExtras::MapCache mc;
auto item = Lua::CheckDFObject<df::item>(state, 1);
auto building = Lua::CheckDFObject<df::building_actual>(state, 2);
int use_mode = luaL_optint(state, 3, 0);
bool force_in_building = lua_toboolean(state, 4);
lua_pushboolean(state, Items::moveToBuilding(mc, item, building, use_mode, force_in_building));
return 1;
}
static const luaL_Reg dfhack_items_funcs[] = {
{ "getPosition", items_getPosition },
{ "getOuterContainerRef", items_getOuterContainerRef },
{ "getContainedItems", items_getContainedItems },
{ "moveToBuilding", items_moveToBuilding },
{ NULL, NULL }
};
/***** Maps module *****/
static bool hasTileAssignment(df::tile_bitmask *bm) {
return bm && bm->has_assignments();
}
static bool getTileAssignment(df::tile_bitmask *bm, int x, int y) {
return bm && bm->getassignment(x,y);
}
static void setTileAssignment(df::tile_bitmask *bm, int x, int y, bool val) {
CHECK_NULL_POINTER(bm);
bm->setassignment(x,y,val);
}
static void resetTileAssignment(df::tile_bitmask *bm, bool val) {
CHECK_NULL_POINTER(bm);
if (val) bm->set_all();
else bm->clear();
}
static const LuaWrapper::FunctionReg dfhack_maps_module[] = {
WRAPN(getBlock, (df::map_block* (*)(int32_t,int32_t,int32_t))Maps::getBlock),
WRAPM(Maps, enableBlockUpdates),
WRAPM(Maps, getGlobalInitFeature),
WRAPM(Maps, getLocalInitFeature),
WRAPM(Maps, getWalkableGroup),
WRAPM(Maps, canWalkBetween),
WRAPM(Maps, spawnFlow),
WRAPN(hasTileAssignment, hasTileAssignment),
WRAPN(getTileAssignment, getTileAssignment),
WRAPN(setTileAssignment, setTileAssignment),
WRAPN(resetTileAssignment, resetTileAssignment),
{ NULL, NULL }
};
static int maps_isValidTilePos(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
lua_pushboolean(L, Maps::isValidTilePos(pos));
return 1;
}
static int maps_isTileVisible(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
lua_pushboolean(L, Maps::isTileVisible(pos));
return 1;
}
static int maps_getTileBlock(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
Lua::PushDFObject(L, Maps::getTileBlock(pos));
return 1;
}
static int maps_ensureTileBlock(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
Lua::PushDFObject(L, Maps::ensureTileBlock(pos));
return 1;
}
static int maps_getTileType(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
auto ptype = Maps::getTileType(pos);
if (ptype)
lua_pushinteger(L, *ptype);
else
lua_pushnil(L);
return 1;
}
static int maps_getTileFlags(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
Lua::PushDFObject(L, Maps::getTileDesignation(pos));
Lua::PushDFObject(L, Maps::getTileOccupancy(pos));
return 2;
}
static int maps_getRegionBiome(lua_State *L)
{
auto pos = CheckCoordXY(L, 1, true);
Lua::PushDFObject(L, Maps::getRegionBiome(pos));
return 1;
}
static int maps_getTileBiomeRgn(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
return Lua::PushPosXY(L, Maps::getTileBiomeRgn(pos));
}
static int maps_getPlantAtTile(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
Lua::PushDFObject(L, Maps::getPlantAtTile(pos));
return 1;
}
static int maps_getBiomeType(lua_State *L)
{
auto pos = CheckCoordXY(L, 1, true);
lua_pushinteger(L, Maps::getBiomeType(pos.x, pos.y));
return 1;
}
static const luaL_Reg dfhack_maps_funcs[] = {
{ "isValidTilePos", maps_isValidTilePos },
{ "isTileVisible", maps_isTileVisible },
{ "getTileBlock", maps_getTileBlock },
{ "ensureTileBlock", maps_ensureTileBlock },
{ "getTileType", maps_getTileType },
{ "getTileFlags", maps_getTileFlags },
{ "getRegionBiome", maps_getRegionBiome },
{ "getTileBiomeRgn", maps_getTileBiomeRgn },
{ "getPlantAtTile", maps_getPlantAtTile },
{ "getBiomeType", maps_getBiomeType },
{ NULL, NULL }
};
/****** World module ******/
static const LuaWrapper::FunctionReg dfhack_world_module[] = {
WRAPM(World, ReadPauseState),
WRAPM(World, SetPauseState),
WRAPM(World, ReadCurrentTick),
WRAPM(World, ReadCurrentYear),
WRAPM(World, ReadCurrentMonth),
WRAPM(World, ReadCurrentDay),
WRAPM(World, ReadCurrentWeather),
WRAPM(World, SetCurrentWeather),
WRAPM(World, ReadWorldFolder),
{ NULL, NULL }
};
#define WORLD_GAMEMODE_WRAPPER(func) \
static int world_gamemode_##func(lua_State *L) \
{ \
int gametype = luaL_optint(L, 1, -1); \
lua_pushboolean(L, World::func((df::game_type)gametype)); \
return 1;\
}
#define WORLD_GAMEMODE_FUNC(func) \
{#func, world_gamemode_##func}
WORLD_GAMEMODE_WRAPPER(isFortressMode);
WORLD_GAMEMODE_WRAPPER(isAdventureMode);
WORLD_GAMEMODE_WRAPPER(isArena);
WORLD_GAMEMODE_WRAPPER(isLegends);
static const luaL_Reg dfhack_world_funcs[] = {
WORLD_GAMEMODE_FUNC(isFortressMode),
WORLD_GAMEMODE_FUNC(isAdventureMode),
WORLD_GAMEMODE_FUNC(isArena),
WORLD_GAMEMODE_FUNC(isLegends),
{ NULL, NULL }
};
/***** Burrows module *****/
static bool burrows_isAssignedBlockTile(df::burrow *burrow, df::map_block *block, int x, int y)
{
return Burrows::isAssignedBlockTile(burrow, block, df::coord2d(x,y));
}
static bool burrows_setAssignedBlockTile(df::burrow *burrow, df::map_block *block, int x, int y, bool enable)
{
return Burrows::setAssignedBlockTile(burrow, block, df::coord2d(x,y), enable);
}
static const LuaWrapper::FunctionReg dfhack_burrows_module[] = {
WRAPM(Burrows, findByName),
WRAPM(Burrows, clearUnits),
WRAPM(Burrows, isAssignedUnit),
WRAPM(Burrows, setAssignedUnit),
WRAPM(Burrows, clearTiles),
WRAPN(isAssignedBlockTile, burrows_isAssignedBlockTile),
WRAPN(setAssignedBlockTile, burrows_setAssignedBlockTile),
WRAPM(Burrows, isAssignedTile),
WRAPM(Burrows, setAssignedTile),
{ NULL, NULL }
};
static int burrows_listBlocks(lua_State *state)
{
std::vector<df::map_block*> pvec;
Burrows::listBlocks(&pvec, Lua::CheckDFObject<df::burrow>(state,1));
Lua::PushVector(state, pvec);
return 1;
}
static const luaL_Reg dfhack_burrows_funcs[] = {
{ "listBlocks", burrows_listBlocks },
{ NULL, NULL }
};
/***** Buildings module *****/
static bool buildings_containsTile(df::building *bld, int x, int y) {
return Buildings::containsTile(bld, df::coord2d(x,y));
}
static const LuaWrapper::FunctionReg dfhack_buildings_module[] = {
WRAPM(Buildings, getGeneralRef),
WRAPM(Buildings, getSpecificRef),
WRAPM(Buildings, setOwner),
WRAPM(Buildings, allocInstance),
WRAPM(Buildings, checkFreeTiles),
WRAPM(Buildings, countExtentTiles),
WRAPN(containsTile, buildings_containsTile),
WRAPM(Buildings, hasSupport),
WRAPM(Buildings, constructAbstract),
WRAPM(Buildings, constructWithItems),
WRAPM(Buildings, constructWithFilters),
WRAPM(Buildings, deconstruct),
WRAPM(Buildings, notifyCivzoneModified),
WRAPM(Buildings, markedForRemoval),
WRAPM(Buildings, getRoomDescription),
WRAPM(Buildings, isActivityZone),
WRAPM(Buildings, isPenPasture),
WRAPM(Buildings, isPitPond),
WRAPM(Buildings, isActive),
WRAPM(Buildings, completeBuild),
{ NULL, NULL }
};
static int buildings_findAtTile(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
Lua::PushDFObject(L, Buildings::findAtTile(pos));
return 1;
}
static int buildings_findCivzonesAt(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
std::vector<df::building_civzonest*> pvec;
if (Buildings::findCivzonesAt(&pvec, pos))
Lua::PushVector(L, pvec);
else
lua_pushnil(L);
return 1;
}
static int buildings_findPenPitAt(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
Lua::PushDFObject(L, Buildings::findPenPitAt(pos));
return 1;
}
static int buildings_getCorrectSize(lua_State *state)
{
df::coord2d size(luaL_optint(state, 1, 1), luaL_optint(state, 2, 1));
auto t = (df::building_type)luaL_optint(state, 3, -1);
int st = luaL_optint(state, 4, -1);
int cu = luaL_optint(state, 5, -1);
int d = luaL_optint(state, 6, 0);
df::coord2d center;
bool flexible = Buildings::getCorrectSize(size, center, t, st, cu, d);
lua_pushboolean(state, flexible);
lua_pushinteger(state, size.x);
lua_pushinteger(state, size.y);
lua_pushinteger(state, center.x);
lua_pushinteger(state, center.y);
return 5;
}
namespace {
int buildings_setSize(lua_State *state)
{
auto bld = Lua::CheckDFObject<df::building>(state, 1);
df::coord2d size(luaL_optint(state, 2, 1), luaL_optint(state, 3, 1));
int dir = luaL_optint(state, 4, 0);
bool ok = Buildings::setSize(bld, size, dir);
lua_pushboolean(state, ok);
if (ok)
{
auto size = Buildings::getSize(bld).second;
int area = size.x*size.y;
lua_pushinteger(state, size.x);
lua_pushinteger(state, size.y);
lua_pushinteger(state, area);
lua_pushinteger(state, Buildings::countExtentTiles(&bld->room, area));
return 5;
}
else
return 1;
}
}
static int buildings_getStockpileContents(lua_State *state)
{
std::vector<df::item*> pvec;
Buildings::getStockpileContents(Lua::CheckDFObject<df::building_stockpilest>(state,1),&pvec);
Lua::PushVector(state, pvec);
return 1;
}
static int buildings_getCageOccupants(lua_State *state)
{
std::vector<df::unit*> units;
Buildings::getCageOccupants(Lua::CheckDFObject<df::building_cagest>(state, 1), units);
Lua::PushVector(state, units);
return 1;
}
static const luaL_Reg dfhack_buildings_funcs[] = {
{ "findAtTile", buildings_findAtTile },
{ "findCivzonesAt", buildings_findCivzonesAt },
{ "getCorrectSize", buildings_getCorrectSize },
CWRAP(setSize, buildings_setSize),
CWRAP(getStockpileContents, buildings_getStockpileContents),
{ "findPenPitAt", buildings_findPenPitAt },
CWRAP(getCageOccupants, buildings_getCageOccupants),
{ NULL, NULL }
};
/***** Constructions module *****/
static const LuaWrapper::FunctionReg dfhack_constructions_module[] = {
WRAPM(Constructions, designateNew),
WRAPM(Constructions, insert),
{ NULL, NULL }
};
static int constructions_designateRemove(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
bool imm = false;
lua_pushboolean(L, Constructions::designateRemove(pos, &imm));
lua_pushboolean(L, imm);
return 2;
}
static int constructions_findAtTile(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
Lua::PushDFObject(L, Constructions::findAtTile(pos));
return 1;
}
static const luaL_Reg dfhack_constructions_funcs[] = {
{ "designateRemove", constructions_designateRemove },
{ "findAtTile", constructions_findAtTile },
{ NULL, NULL }
};
/***** Screen module *****/
static const LuaWrapper::FunctionReg dfhack_screen_module[] = {
WRAPM(Screen, inGraphicsMode),
WRAPM(Screen, clear),
WRAPM(Screen, invalidate),
WRAPM(Screen, getKeyDisplay),
{ NULL, NULL }
};
static int screen_getMousePos(lua_State *L)
{
return Lua::PushPosXY(L, Screen::getMousePos());
}
static int screen_getMousePixels(lua_State *L)
{
return Lua::PushPosXY(L, Screen::getMousePixels());
}
static int screen_getWindowSize(lua_State *L)
{
return Lua::PushPosXY(L, Screen::getWindowSize());
}
static int screen_paintTile(lua_State *L)
{
Pen pen;
Lua::CheckPen(L, &pen, 1);
int x = luaL_checkint(L, 2);
int y = luaL_checkint(L, 3);
if (lua_gettop(L) >= 4 && !lua_isnil(L, 4))
{
if (lua_type(L, 4) == LUA_TSTRING)
pen.ch = lua_tostring(L, 4)[0];
else
pen.ch = luaL_checkint(L, 4);
}
if (lua_gettop(L) >= 5 && !lua_isnil(L, 5))
pen.tile = luaL_checkint(L, 5);
bool map = lua_toboolean(L, 6);
lua_pushboolean(L, Screen::paintTile(pen, x, y, map));
return 1;
}
static int screen_readTile(lua_State *L)
{
int x = luaL_checkint(L, 1);
int y = luaL_checkint(L, 2);
bool map = lua_toboolean(L, 3);
Pen pen = Screen::readTile(x, y, map);
Lua::Push(L, pen);
return 1;
}
static int screen_paintString(lua_State *L)
{
Pen pen;
Lua::CheckPen(L, &pen, 1);
int x = luaL_checkint(L, 2);
int y = luaL_checkint(L, 3);
const char *text = luaL_checkstring(L, 4);
bool map = lua_toboolean(L, 5);
lua_pushboolean(L, Screen::paintString(pen, x, y, text, map));
return 1;
}
static int screen_fillRect(lua_State *L)
{
Pen pen;
Lua::CheckPen(L, &pen, 1);
int x1 = luaL_checkint(L, 2);
int y1 = luaL_checkint(L, 3);
int x2 = luaL_checkint(L, 4);
int y2 = luaL_checkint(L, 5);
bool map = lua_toboolean(L, 6);
lua_pushboolean(L, Screen::fillRect(pen, x1, y1, x2, y2, map));
return 1;
}
static int screen_findGraphicsTile(lua_State *L)
{
auto str = luaL_checkstring(L, 1);
int x = luaL_checkint(L, 2);
int y = luaL_checkint(L, 3);
int tile, tile_gs;
if (Screen::findGraphicsTile(str, x, y, &tile, &tile_gs))
{
lua_pushinteger(L, tile);
lua_pushinteger(L, tile_gs);
return 2;
}
else
{
lua_pushnil(L);
return 1;
}
}
static int screen_raise(lua_State *L) {
df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, false);
// remove screen from the stack so it doesn't get returned as an output
lua_remove(L, 1);
Screen::raise(screen);
return 0;
}
static int screen_hideGuard(lua_State *L) {
df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, false);
luaL_checktype(L, 2, LUA_TFUNCTION);
// remove screen from the stack so it doesn't get returned as an output
lua_remove(L, 1);
Screen::Hide hideGuard(screen, Screen::Hide::RESTORE_AT_TOP);
int nargs = lua_gettop(L) - 1;
lua_call(L, nargs, LUA_MULTRET);
return lua_gettop(L);
}
namespace {
int screen_show(lua_State *L)
{
df::viewscreen *before = NULL;
if (lua_gettop(L) >= 2)
before = Lua::CheckDFObject<df::viewscreen>(L, 2);
df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, true);
bool ok = Screen::show(std::unique_ptr<df::viewscreen>{screen}, before);
lua_pushboolean(L, ok);
return 1;
}
static int screen_dismiss(lua_State *L)
{
df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, false);
bool to_first = lua_toboolean(L, 2);
Screen::dismiss(screen, to_first);
return 0;
}
static int screen_isDismissed(lua_State *L)
{
df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, false);
lua_pushboolean(L, Screen::isDismissed(screen));
return 1;
}
static int screen_doSimulateInput(lua_State *L)
{
auto screen = Lua::CheckDFObject<df::viewscreen>(L, 1);
luaL_checktype(L, 2, LUA_TTABLE);
if (!screen)
luaL_argerror(L, 1, "NULL screen");
int sz = lua_rawlen(L, 2);
std::set<df::interface_key> keys;
char str = '\0';
for (int j = 1; j <= sz; j++)
{
lua_rawgeti(L, 2, j);
df::interface_key k = (df::interface_key)lua_tointeger(L, -1);
if (!str && k > df::interface_key::STRING_A000 && k <= df::interface_key::STRING_A255)
str = Screen::keyToChar(k);
keys.insert(k);
lua_pop(L, 1);
}
// if we're injecting a text keybinding, ensure it is reflected in the enabler text buffer
std::string prev_input;
if (str) {
prev_input = (const char *)&df::global::enabler->last_text_input[0];
df::global::enabler->last_text_input[0] = str;
df::global::enabler->last_text_input[1] = '\0';
}
screen->feed(&keys);
if (str)
strcpy((char *)&df::global::enabler->last_text_input[0], prev_input.c_str());
return 0;
}
static int screen_keyToChar(lua_State *L)
{
auto keycode = (df::interface_key)luaL_checkint(L, 1);
int charcode = Screen::keyToChar(keycode);
if (charcode >= 0)
lua_pushinteger(L, charcode);
else
lua_pushnil(L);
return 1;
}
static int screen_charToKey(lua_State *L)
{
auto charcode = (char)luaL_checkint(L, 1);
df::interface_key keycode = Screen::charToKey(charcode);
if (keycode)
lua_pushinteger(L, keycode);
else
lua_pushnil(L);
return 1;
}
/*
static int screen_zoom(lua_State *L)
{
using df::global::enabler;
df::zoom_commands cmd = (df::zoom_commands)luaL_checkint(L, 1);
if (cmd < df::enum_traits<df::zoom_commands>::first_item_value ||
cmd > df::enum_traits<df::zoom_commands>::last_item_value)
{
luaL_error(L, "Invalid zoom command: %d", cmd);
}
if (!enabler)
{
luaL_error(L, "enabler unavailable");
}
enabler->zoom_display(cmd);
return 0;
}
*/
}
static const luaL_Reg dfhack_screen_funcs[] = {
{ "getMousePos", screen_getMousePos },
{ "getMousePixels", screen_getMousePixels },
{ "getWindowSize", screen_getWindowSize },
{ "paintTile", screen_paintTile },
{ "readTile", screen_readTile },
{ "paintString", screen_paintString },
{ "fillRect", screen_fillRect },
{ "findGraphicsTile", screen_findGraphicsTile },
CWRAP(raise, screen_raise),
CWRAP(hideGuard, screen_hideGuard),
CWRAP(show, screen_show),
CWRAP(dismiss, screen_dismiss),
CWRAP(isDismissed, screen_isDismissed),
{ "_doSimulateInput", screen_doSimulateInput },
{ "keyToChar", screen_keyToChar },
{ "charToKey", screen_charToKey },
//{ "zoom", screen_zoom },
{ NULL, NULL }
};
/***** Filesystem module *****/
static const LuaWrapper::FunctionReg dfhack_filesystem_module[] = {
WRAPM(Filesystem, getcwd),
WRAPM(Filesystem, restore_cwd),
WRAPM(Filesystem, get_initial_cwd),
WRAPM(Filesystem, chdir),
WRAPM(Filesystem, mkdir),
WRAPM(Filesystem, mkdir_recursive),
WRAPM(Filesystem, rmdir),
WRAPM(Filesystem, exists),
WRAPM(Filesystem, isfile),
WRAPM(Filesystem, isdir),
WRAPM(Filesystem, atime),
WRAPM(Filesystem, ctime),
WRAPM(Filesystem, mtime),
{NULL, NULL}
};
static int filesystem_listdir(lua_State *L)
{
luaL_checktype(L,1,LUA_TSTRING);
std::string dir=lua_tostring(L,1);
std::vector<std::string> files;
int err = DFHack::Filesystem::listdir(dir, files);
if (err)
{
lua_pushnil(L);
lua_pushstring(L, strerror(err));
lua_pushinteger(L, err);
return 3;
}
lua_newtable(L);
for(size_t i=0;i<files.size();i++)
{
lua_pushinteger(L,i+1);
lua_pushstring(L,files[i].c_str());
lua_settable(L,-3);
}
return 1;
}
static int filesystem_listdir_recursive(lua_State *L)
{
luaL_checktype(L,1,LUA_TSTRING);
std::string dir=lua_tostring(L,1);
int depth = 10;
if (lua_gettop(L) >= 2 && !lua_isnil(L, 2))
depth = luaL_checkint(L, 2);
bool include_prefix = true;
if (lua_gettop(L) >= 3 && !lua_isnil(L, 3))
include_prefix = lua_toboolean(L, 3);
std::map<std::string, bool> files;
int err = DFHack::Filesystem::listdir_recursive(dir, files, depth, include_prefix);
if (err != 0 && err != -1) {
lua_pushnil(L);
lua_pushstring(L, strerror(err));
lua_pushinteger(L, err);
return 3;
}
lua_newtable(L);
int i = 1;
for (auto it = files.begin(); it != files.end(); ++it)
{
lua_pushinteger(L, i++);
lua_newtable(L);
lua_pushstring(L, "path");
lua_pushstring(L, (it->first).c_str());
lua_settable(L, -3);
lua_pushstring(L, "isdir");
lua_pushboolean(L, it->second);
lua_settable(L, -3);
lua_settable(L, -3);
}
return 1;
}
static const luaL_Reg dfhack_filesystem_funcs[] = {
{"listdir", filesystem_listdir},
{"listdir_recursive", filesystem_listdir_recursive},
{NULL, NULL}
};
/***** Designations module *****/
static const LuaWrapper::FunctionReg dfhack_designations_module[] = {
WRAPM(Designations, markPlant),
WRAPM(Designations, unmarkPlant),
WRAPM(Designations, canMarkPlant),
WRAPM(Designations, canUnmarkPlant),
WRAPM(Designations, isPlantMarked),
{NULL, NULL}
};
static int designations_getPlantDesignationTile(lua_State *state)
{
return Lua::PushPosXYZ(state, Designations::getPlantDesignationTile(Lua::CheckDFObject<df::plant>(state, 1)));
}
static const luaL_Reg dfhack_designations_funcs[] = {
{"getPlantDesignationTile", designations_getPlantDesignationTile},
{NULL, NULL}
};
/***** Kitchen module *****/
static const LuaWrapper::FunctionReg dfhack_kitchen_module[] = {
WRAPM(Kitchen, findExclusion),
WRAPM(Kitchen, addExclusion),
WRAPM(Kitchen, removeExclusion),
{NULL, NULL}
};
/***** Console module *****/
namespace console {
void clear() {
Core::getInstance().getConsole().clear();
}
void flush() {
Core::getInstance().getConsole() << std::flush;
}
}
static const LuaWrapper::FunctionReg dfhack_console_module[] = {
WRAPM(console, clear),
WRAPM(console, flush),
{ NULL, NULL }
};
/***** Internal module *****/
static void *checkaddr(lua_State *L, int idx, bool allow_null = false)
{
luaL_checkany(L, idx);
void *rv;
if (lua_isnil(L, idx))
rv = NULL;
else if (lua_type(L, idx) == LUA_TNUMBER)
rv = (void*)lua_tounsigned(L, idx);
else
rv = Lua::CheckDFObject(L, NULL, idx);
if (!rv && !allow_null)
luaL_argerror(L, idx, "null pointer");
return rv;
}
static md5wrapper md5_wrap;
static uintptr_t getImageBase() { return Core::getInstance().p->getBase(); }
static intptr_t getRebaseDelta() { return Core::getInstance().vinfo->getRebaseDelta(); }
static int8_t getModstate() { return Core::getInstance().getModstate(); }
static std::string internal_strerror(int n) { return strerror(n); }
static std::string internal_md5(std::string s) { return md5_wrap.getHashFromString(s); }
struct heap_pointer_info
{
size_t address = 0;
size_t size = 0;
int status = 0;
};
//fixed sized, sorted
static std::vector<heap_pointer_info> heap_data;
//when dfhack upgrades to c++17, this would do well as a std::optional
static std::pair<bool, heap_pointer_info> heap_find(uintptr_t address)
{
auto it = std::lower_bound(heap_data.begin(), heap_data.end(), address,
[](heap_pointer_info t, uintptr_t address)
{
return t.address < address;
});
if (it == heap_data.end() || it->address != address)
return {false, heap_pointer_info()};
return {true, *it};
}
//this function only allocates the first time it is called
static int heap_take_snapshot()
{
#ifdef _WIN32
size_t max_entries = 256 * 1024 * 1024 / sizeof(heap_pointer_info);
//clearing the vector is guaranteed not to deallocate the memory
heap_data.clear();
heap_data.reserve(max_entries);
_HEAPINFO hinfo;
hinfo._pentry = nullptr;
int heap_status = 0;
while ((heap_status = _heapwalk(&hinfo)) == _HEAPOK && heap_data.size() < max_entries)
{
heap_pointer_info inf;
inf.address = reinterpret_cast<uintptr_t>(hinfo._pentry);
inf.size = hinfo._size;
inf.status = hinfo._useflag; //0 == _FREEENTRY, 1 == _USEDENTRY
heap_data.push_back(inf);
}
//sort by address
std::sort(heap_data.begin(), heap_data.end(),
[](heap_pointer_info t1, heap_pointer_info t2)
{
return t1.address < t2.address;
});
if (heap_status == _HEAPEMPTY || heap_status == _HEAPEND)
return 0;
if (heap_status == _HEAPBADPTR)
return 1;
if (heap_status == _HEAPBADBEGIN)
return 2;
if (heap_status == _HEAPBADNODE)
return 3;
#endif
return 0;
}
static int get_heap_state()
{
#ifdef _WIN32
int heap_status = _heapchk();
if (heap_status == _HEAPEMPTY || heap_status == _HEAPOK)
return 0;
if (heap_status == _HEAPBADPTR)
return 1;
if (heap_status == _HEAPBADBEGIN)
return 2;
if (heap_status == _HEAPBADNODE)
return 3;
#endif
return 0;
}
static bool is_address_in_heap(uintptr_t ptr)
{
return heap_find(ptr).first;
}
static bool is_address_active_in_heap(uintptr_t ptr)
{
std::pair<bool, heap_pointer_info> inf = heap_find(ptr);
if (!inf.first)
return false;
return inf.second.status == 1;
}
static bool is_address_used_after_free_in_heap(uintptr_t ptr)
{
std::pair<bool, heap_pointer_info> inf = heap_find(ptr);
if (!inf.first)
return false;
return inf.second.status != 1;
}
static int get_address_size_in_heap(uintptr_t ptr)
{
std::pair<bool, heap_pointer_info> inf = heap_find(ptr);
if (!inf.first)
return -1;
return inf.second.size;
}
//eg if I have a struct, does any address lie within the struct?
static uintptr_t get_root_address_of_heap_object(uintptr_t ptr)
{
//find the first element strictly greater than our pointer
auto it = std::upper_bound(heap_data.begin(), heap_data.end(), ptr, [](uintptr_t ptr, heap_pointer_info t1)
{
return ptr < t1.address;
});
//if we're at the start of the snapshot, no elements are less than our pointer
//therefore it is invalid
if (it == heap_data.begin())
return 0;
//get the first element less than or equal to ours
it--;
//our pointer is only valid if we lie in the first pointer lower in memory than it
if (ptr >= it->address && ptr < it->address + it->size)
return it->address;
return 0;
}
//msize crashes if you pass an invalid pointer to it, only use it if you *know* the thing you're looking at
//is in the heap/valid
static int msize_address(uintptr_t ptr)
{
#ifdef _WIN32
void* vptr = reinterpret_cast<void*>(ptr);
if (vptr)
return _msize(vptr);
#endif
return -1;
}
static const LuaWrapper::FunctionReg dfhack_internal_module[] = {
WRAP(getImageBase),
WRAP(getRebaseDelta),
WRAP(getModstate),
WRAPN(strerror, internal_strerror),
WRAPN(md5, internal_md5),
WRAPN(heapTakeSnapshot, heap_take_snapshot),
WRAPN(getHeapState, get_heap_state),
WRAPN(isAddressInHeap, is_address_in_heap),
WRAPN(isAddressActiveInHeap, is_address_active_in_heap),
WRAPN(isAddressUsedAfterFreeInHeap, is_address_used_after_free_in_heap),
WRAPN(getAddressSizeInHeap, get_address_size_in_heap),
WRAPN(getRootAddressOfHeapObject, get_root_address_of_heap_object),
WRAPN(msizeAddress, msize_address),
WRAP(getClipboardTextCp437),
WRAP(setClipboardTextCp437),
{ NULL, NULL }
};
static int internal_getmd5(lua_State *L)
{
auto& p = Core::getInstance().p;
if (p->getDescriptor()->getOS() == OS_WINDOWS)
luaL_error(L, "process MD5 not available on Windows");
lua_pushstring(L, p->getMD5().c_str());
return 1;
}
static int internal_getPE(lua_State *L)
{
auto& p = Core::getInstance().p;
if (p->getDescriptor()->getOS() != OS_WINDOWS)
luaL_error(L, "process PE timestamp not available on non-Windows");
lua_pushinteger(L, p->getPE());
return 1;
}
static int internal_getAddress(lua_State *L)
{
const char *name = luaL_checkstring(L, 1);
uintptr_t addr = Core::getInstance().vinfo->getAddress(name);
if (addr)
lua_pushinteger(L, addr);
else
lua_pushnil(L);
return 1;
}
static int internal_setAddress(lua_State *L)
{
std::string name = luaL_checkstring(L, 1);
uintptr_t addr = (uintptr_t)checkaddr(L, 2, true);
internal_getAddress(L);
// Set the address
Core::getInstance().vinfo->setAddress(name, addr);
auto fields = df::global::_identity.getFields();
for (int i = 0; fields && fields[i].mode != struct_field_info::END; ++i)
{
if (fields[i].name != name)
continue;
*(void**)fields[i].offset = (void*)addr;
}
// Print via printerr, so that it is definitely logged to stderr.log.
uintptr_t iaddr = addr - Core::getInstance().vinfo->getRebaseDelta();
fprintf(stderr, "Setting global '%s' to %p (%p)\n", name.c_str(), (void*)addr, (void*)iaddr);
fflush(stderr);
return 1;
}
static int internal_getVTable(lua_State *L)
{
const char *name = luaL_checkstring(L, 1);
uintptr_t addr = (uintptr_t)Core::getInstance().vinfo->getVTable(name);
if (addr)
lua_pushinteger(L, addr);
else
lua_pushnil(L);
return 1;
}
static int internal_adjustOffset(lua_State *L)
{
lua_settop(L, 2);
int off = luaL_checkint(L, 1);
int rv = Core::getInstance().p->adjustOffset(off, lua_toboolean(L, 2));
if (rv >= 0)
lua_pushinteger(L, rv);
else
lua_pushnil(L);
return 1;
}
static int internal_getMemRanges(lua_State *L)
{
std::vector<DFHack::t_memrange> ranges;
Core::getInstance().p->getMemRanges(ranges);
lua_newtable(L);
for(size_t i = 0; i < ranges.size(); i++)
{
lua_newtable(L);
lua_pushinteger(L, (uintptr_t)ranges[i].start);
lua_setfield(L, -2, "start_addr");
lua_pushinteger(L, (uintptr_t)ranges[i].end);
lua_setfield(L, -2, "end_addr");
lua_pushstring(L, ranges[i].name);
lua_setfield(L, -2, "name");
lua_pushboolean(L, ranges[i].read);
lua_setfield(L, -2, "read");
lua_pushboolean(L, ranges[i].write);
lua_setfield(L, -2, "write");
lua_pushboolean(L, ranges[i].execute);
lua_setfield(L, -2, "execute");
lua_pushboolean(L, ranges[i].shared);
lua_setfield(L, -2, "shared");
lua_pushboolean(L, ranges[i].valid);
lua_setfield(L, -2, "valid");
lua_rawseti(L, -2, i+1);
}
return 1;
}
static int internal_patchMemory(lua_State *L)
{
void *dest = checkaddr(L, 1);
void *src = checkaddr(L, 2);
int size = luaL_checkint(L, 3);
if (size < 0) luaL_argerror(L, 1, "negative size");
bool ok = Core::getInstance().p->patchMemory(dest, src, size);
lua_pushboolean(L, ok);
return 1;
}
static int internal_patchBytes(lua_State *L)
{
luaL_checktype(L, 1, LUA_TTABLE);
lua_settop(L, 2);
MemoryPatcher patcher;
if (!lua_isnil(L, 2))
{
luaL_checktype(L, 2, LUA_TTABLE);
lua_pushnil(L);
while (lua_next(L, 2))
{
uint8_t *addr = (uint8_t*)checkaddr(L, -2, true);
int isnum;
uint8_t value = (uint8_t)lua_tounsignedx(L, -1, &isnum);
if (!isnum)
luaL_error(L, "invalid value in verify table");
lua_pop(L, 1);
if (!patcher.verifyAccess(addr, 1, false))
{
lua_pushnil(L);
lua_pushstring(L, "invalid verify address");
lua_pushvalue(L, -3);
return 3;
}
if (*addr != value)
{
lua_pushnil(L);
lua_pushstring(L, "wrong verify value");
lua_pushvalue(L, -3);
return 3;
}
}
}
lua_pushnil(L);
while (lua_next(L, 1))
{
uint8_t *addr = (uint8_t*)checkaddr(L, -2, true);
int isnum;
lua_tounsignedx(L, -1, &isnum);
if (!isnum)
luaL_error(L, "invalid value in write table");
lua_pop(L, 1);
if (!patcher.verifyAccess(addr, 1, true))
{
lua_pushnil(L);
lua_pushstring(L, "invalid write address");
lua_pushvalue(L, -3);
return 3;
}
}
lua_pushnil(L);
while (lua_next(L, 1))
{
uint8_t *addr = (uint8_t*)checkaddr(L, -2, true);
uint8_t value = (uint8_t)lua_tounsigned(L, -1);
lua_pop(L, 1);
*addr = value;
}
lua_pushboolean(L, true);
return 1;
}
static int internal_memmove(lua_State *L)
{
void *dest = checkaddr(L, 1);
void *src = checkaddr(L, 2);
int size = luaL_checkint(L, 3);
if (size < 0) luaL_argerror(L, 1, "negative size");
memmove(dest, src, size);
return 0;
}
static int internal_memcmp(lua_State *L)
{
void *dest = checkaddr(L, 1);
void *src = checkaddr(L, 2);
int size = luaL_checkint(L, 3);
if (size < 0) luaL_argerror(L, 1, "negative size");
lua_pushinteger(L, memcmp(dest, src, size));
return 1;
}
static int internal_memscan(lua_State *L)
{
uint8_t *haystack = (uint8_t*)checkaddr(L, 1);
int hcount = luaL_checkint(L, 2);
int hstep = luaL_checkint(L, 3);
if (hstep == 0) luaL_argerror(L, 3, "zero step");
void *needle = checkaddr(L, 4);
int nsize = luaL_checkint(L, 5);
if (nsize < 0) luaL_argerror(L, 5, "negative size");
for (int i = 0; i <= hcount; i++)
{
uint8_t *p = haystack + i*hstep;
if (p + nsize > haystack + (hcount * hstep)) {
break;
}
if (memcmp(p, needle, nsize) == 0) {
lua_pushinteger(L, i);
lua_pushinteger(L, (lua_Integer)p);
return 2;
}
}
lua_pushnil(L);
return 1;
}
static int internal_diffscan(lua_State *L)
{
lua_settop(L, 8);
void *old_data = checkaddr(L, 1);
void *new_data = checkaddr(L, 2);
int start_idx = luaL_checkint(L, 3);
int end_idx = luaL_checkint(L, 4);
int eltsize = luaL_checkint(L, 5);
bool has_oldv = !lua_isnil(L, 6);
bool has_newv = !lua_isnil(L, 7);
bool has_diffv = !lua_isnil(L, 8);
#define LOOP(esz, etype) \
case esz: { \
etype *pold = (etype*)old_data; \
etype *pnew = (etype*)new_data; \
etype oldv = (etype)luaL_optint(L, 6, 0); \
etype newv = (etype)luaL_optint(L, 7, 0); \
etype diffv = (etype)luaL_optint(L, 8, 0); \
for (int i = start_idx; i < end_idx; i++) { \
if (pold[i] == pnew[i]) continue; \
if (has_oldv && pold[i] != oldv) continue; \
if (has_newv && pnew[i] != newv) continue; \
if (has_diffv && etype(pnew[i]-pold[i]) != diffv) continue; \
lua_pushinteger(L, i); return 1; \
} \
break; \
}
switch (eltsize) {
LOOP(1, uint8_t);
LOOP(2, uint16_t);
LOOP(4, uint32_t);
default:
luaL_argerror(L, 5, "invalid element size");
}
#undef LOOP
lua_pushnil(L);
return 1;
}
static int internal_cxxDemangle(lua_State *L)
{
std::string mangled = luaL_checkstring(L, 1);
std::string status;
std::string demangled = cxx_demangle(mangled, &status);
if (demangled.length())
{
lua_pushstring(L, demangled.c_str());
return 1;
}
else
{
lua_pushnil(L);
lua_pushstring(L, status.c_str());
return 2;
}
}
static int internal_runCommand(lua_State *L)
{
color_ostream *out = NULL;
std::unique_ptr<buffered_color_ostream> out_buffer;
command_result res;
if (lua_gettop(L) == 0)
{
lua_pushstring(L, "");
}
int type_1 = lua_type(L, 1);
bool use_console = lua_toboolean(L, 2);
if (use_console)
{
out = Lua::GetOutput(L);
if (!out)
{
out = &Core::getInstance().getConsole();
}
}
else
{
out_buffer.reset(new buffered_color_ostream());
out = out_buffer.get();
}
if (type_1 == LUA_TTABLE)
{
std::string command = "";
std::vector<std::string> args;
lua_pushnil(L); // first key
while (lua_next(L, 1) != 0)
{
if (command == "")
command = lua_tostring(L, -1);
else
args.push_back(lua_tostring(L, -1));
lua_pop(L, 1); // remove value, leave key
}
CoreSuspender suspend;
res = Core::getInstance().runCommand(*out, command, args);
}
else if (type_1 == LUA_TSTRING)
{
std::string command = lua_tostring(L, 1);
CoreSuspender suspend;
res = Core::getInstance().runCommand(*out, command);
}
else
{
lua_pushnil(L);
lua_pushfstring(L, "Expected table, got %s", lua_typename(L, type_1));
return 2;
}
lua_newtable(L);
lua_pushinteger(L, (int)res);
lua_setfield(L, -2, "status");
if (out_buffer)
{
auto fragments = out_buffer->fragments();
int i = 1;
for (auto iter = fragments.begin(); iter != fragments.end(); iter++, i++)
{
int color = iter->first;
std::string output = iter->second;
lua_createtable(L, 2, 0);
lua_pushinteger(L, color);
lua_rawseti(L, -2, 1);
lua_pushstring(L, output.c_str());
lua_rawseti(L, -2, 2);
lua_rawseti(L, -2, i);
}
}
lua_pushvalue(L, -1);
return 1;
}
static int internal_getModifiers(lua_State *L)
{
int8_t modstate = Core::getInstance().getModstate();
lua_newtable(L);
lua_pushstring(L, "shift");
lua_pushboolean(L, modstate & DFH_MOD_SHIFT);
lua_settable(L, -3);
lua_pushstring(L, "ctrl");
lua_pushboolean(L, modstate & DFH_MOD_CTRL);
lua_settable(L, -3);
lua_pushstring(L, "alt");
lua_pushboolean(L, modstate & DFH_MOD_ALT);
lua_settable(L, -3);
return 1;
}
static int internal_addScriptPath(lua_State *L)
{
const char *path = luaL_checkstring(L, 1);
bool search_before = (lua_gettop(L) > 1 && lua_toboolean(L, 2));
lua_pushboolean(L, Core::getInstance().addScriptPath(path, search_before));
return 1;
}
static int internal_removeScriptPath(lua_State *L)
{
const char *path = luaL_checkstring(L, 1);
lua_pushboolean(L, Core::getInstance().removeScriptPath(path));
return 1;
}
static int internal_getScriptPaths(lua_State *L)
{
int i = 1;
lua_newtable(L);
std::vector<std::string> paths;
Core::getInstance().getScriptPaths(&paths);
for (auto it = paths.begin(); it != paths.end(); ++it)
{
lua_pushinteger(L, i++);
lua_pushstring(L, it->c_str());
lua_settable(L, -3);
}
return 1;
}
static int internal_findScript(lua_State *L)
{
const char *name = luaL_checkstring(L, 1);
std::string path = Core::getInstance().findScript(name);
if (path.size())
lua_pushstring(L, path.c_str());
else
lua_pushnil(L);
return 1;
}
static int internal_listPlugins(lua_State *L)
{
auto plugins = Core::getInstance().getPluginManager();
int i = 1;
lua_newtable(L);
for (auto it = plugins->begin(); it != plugins->end(); ++it)
{
lua_pushinteger(L, i++);
lua_pushstring(L, it->first.c_str());
lua_settable(L, -3);
}
return 1;
}
static int internal_listCommands(lua_State *L)
{
auto plugins = Core::getInstance().getPluginManager();
const char *name = luaL_checkstring(L, 1);
auto plugin = plugins->getPluginByName(name);
if (!plugin)
{
lua_pushnil(L);
return 1;
}
size_t num_commands = plugin->size();
lua_newtable(L);
for (size_t i = 0; i < num_commands; ++i)
{
lua_pushinteger(L, i + 1);
lua_pushstring(L, (*plugin)[i].name.c_str());
lua_settable(L, -3);
}
return 1;
}
static const PluginCommand * getPluginCommand(const char * command)
{
auto plugins = Core::getInstance().getPluginManager();
auto plugin = plugins->getPluginByCommand(command);
if (!plugin)
{
return NULL;
}
size_t num_commands = plugin->size();
for (size_t i = 0; i < num_commands; ++i)
{
if ((*plugin)[i].name == command)
return &(*plugin)[i];
}
// not found (somehow)
return NULL;
}
static int internal_getCommandHelp(lua_State *L)
{
const PluginCommand *pc = getPluginCommand(luaL_checkstring(L, 1));
if (!pc)
{
lua_pushnil(L);
return 1;
}
std::string help = pc->description;
if (help.size() && help[help.size()-1] != '.')
help += ".";
if (pc->usage.size())
help += "\n" + pc->usage;
lua_pushstring(L, help.c_str());
return 1;
}
static int internal_getCommandDescription(lua_State *L)
{
const PluginCommand *pc = getPluginCommand(luaL_checkstring(L, 1));
if (!pc)
{
lua_pushnil(L);
return 1;
}
std::string help = pc->description;
if (help.size() && help[help.size()-1] != '.')
help += ".";
lua_pushstring(L, help.c_str());
return 1;
}
static int internal_threadid(lua_State *L)
{
std::stringstream ss;
ss << std::this_thread::get_id();
int i;
ss >> i;
lua_pushinteger(L, i);
return 1;
}
static int internal_md5file(lua_State *L)
{
const char *s = luaL_checkstring(L, 1);
uint32_t len;
char *first_kb_raw = nullptr;
std::vector<char> first_kb;
if (lua_toboolean(L, 2))
first_kb_raw = new char[1024];
std::string hash = md5_wrap.getHashFromFile(s, len, first_kb_raw);
bool err = (hash.find("file") != std::string::npos);
if (first_kb_raw)
{
first_kb.assign(first_kb_raw, first_kb_raw + 1024);
delete[] first_kb_raw;
}
if (err)
{
lua_pushnil(L);
lua_pushstring(L, hash.c_str());
return 2;
}
else
{
lua_pushstring(L, hash.c_str());
lua_pushinteger(L, len);
if (!first_kb.empty())
{
Lua::PushVector(L, first_kb);
return 3;
}
else
{
return 2;
}
}
}
static int internal_getSuppressDuplicateKeyboardEvents(lua_State *L) {
Lua::Push(L, Core::getInstance().getSuppressDuplicateKeyboardEvents());
return 1;
}
static int internal_setSuppressDuplicateKeyboardEvents(lua_State *L) {
bool suppress = lua_toboolean(L, 1);
Core::getInstance().setSuppressDuplicateKeyboardEvents(suppress);
return 0;
}
static const luaL_Reg dfhack_internal_funcs[] = {
{ "getPE", internal_getPE },
{ "getMD5", internal_getmd5 },
{ "getAddress", internal_getAddress },
{ "setAddress", internal_setAddress },
{ "getVTable", internal_getVTable },
{ "adjustOffset", internal_adjustOffset },
{ "getMemRanges", internal_getMemRanges },
{ "patchMemory", internal_patchMemory },
{ "patchBytes", internal_patchBytes },
{ "memmove", internal_memmove },
{ "memcmp", internal_memcmp },
{ "memscan", internal_memscan },
{ "diffscan", internal_diffscan },
{ "cxxDemangle", internal_cxxDemangle },
{ "getDir", filesystem_listdir },
{ "runCommand", internal_runCommand },
{ "getModifiers", internal_getModifiers },
{ "addScriptPath", internal_addScriptPath },
{ "removeScriptPath", internal_removeScriptPath },
{ "getScriptPaths", internal_getScriptPaths },
{ "findScript", internal_findScript },
{ "listPlugins", internal_listPlugins },
{ "listCommands", internal_listCommands },
{ "getCommandHelp", internal_getCommandHelp },
{ "getCommandDescription", internal_getCommandDescription },
{ "threadid", internal_threadid },
{ "md5File", internal_md5file },
{ "getSuppressDuplicateKeyboardEvents", internal_getSuppressDuplicateKeyboardEvents },
{ "setSuppressDuplicateKeyboardEvents", internal_setSuppressDuplicateKeyboardEvents },
{ NULL, NULL }
};
/************************
* Main Open function *
************************/
void OpenDFHackApi(lua_State *state)
{
OpenPersistent(state);
OpenMatinfo(state);
OpenPen(state);
OpenPenArray(state);
OpenRandom(state);
LuaWrapper::SetFunctionWrappers(state, dfhack_module);
luaL_setfuncs(state, dfhack_funcs, 0);
OpenModule(state, "gui", dfhack_gui_module, dfhack_gui_funcs);
OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs);
OpenModule(state, "textures", dfhack_textures_funcs);
OpenModule(state, "units", dfhack_units_module, dfhack_units_funcs);
OpenModule(state, "military", dfhack_military_module);
OpenModule(state, "items", dfhack_items_module, dfhack_items_funcs);
OpenModule(state, "maps", dfhack_maps_module, dfhack_maps_funcs);
OpenModule(state, "world", dfhack_world_module, dfhack_world_funcs);
OpenModule(state, "burrows", dfhack_burrows_module, dfhack_burrows_funcs);
OpenModule(state, "buildings", dfhack_buildings_module, dfhack_buildings_funcs);
OpenModule(state, "constructions", dfhack_constructions_module, dfhack_constructions_funcs);
OpenModule(state, "screen", dfhack_screen_module, dfhack_screen_funcs);
OpenModule(state, "filesystem", dfhack_filesystem_module, dfhack_filesystem_funcs);
OpenModule(state, "designations", dfhack_designations_module, dfhack_designations_funcs);
OpenModule(state, "kitchen", dfhack_kitchen_module);
OpenModule(state, "console", dfhack_console_module);
OpenModule(state, "internal", dfhack_internal_module, dfhack_internal_funcs);
}