diff options
Diffstat (limited to 'src/script')
52 files changed, 2464 insertions, 628 deletions
diff --git a/src/script/common/CMakeLists.txt b/src/script/common/CMakeLists.txt index d07f6ab1b..3e84b46c7 100644 --- a/src/script/common/CMakeLists.txt +++ b/src/script/common/CMakeLists.txt @@ -3,6 +3,7 @@ set(common_SCRIPT_COMMON_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/c_converter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/c_types.cpp ${CMAKE_CURRENT_SOURCE_DIR}/c_internal.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/c_packer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/helper.cpp PARENT_SCOPE) diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 8a5a3fe71..166980025 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -42,7 +42,7 @@ struct EnumString es_TileAnimationType[] = {TAT_NONE, "none"}, {TAT_VERTICAL_FRAMES, "vertical_frames"}, {TAT_SHEET_2D, "sheet_2d"}, - {0, NULL}, + {0, nullptr}, }; /******************************************************************************/ @@ -198,7 +198,7 @@ void read_object_properties(lua_State *L, int index, prop->hp_max = (u16)rangelim(hp_max, 0, U16_MAX); if (prop->hp_max < sao->getHP()) { - PlayerHPChangeReason reason(PlayerHPChangeReason::SET_HP); + PlayerHPChangeReason reason(PlayerHPChangeReason::SET_HP_MAX); sao->setHP(prop->hp_max, reason); } } @@ -550,13 +550,6 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) // tiles = {} lua_getfield(L, index, "tiles"); - // If nil, try the deprecated name "tile_images" instead - if(lua_isnil(L, -1)){ - lua_pop(L, 1); - warn_if_field_exists(L, index, "tile_images", - "Deprecated; new name is \"tiles\"."); - lua_getfield(L, index, "tile_images"); - } if(lua_istable(L, -1)){ int table = lua_gettop(L); lua_pushnil(L); @@ -613,13 +606,6 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) // special_tiles = {} lua_getfield(L, index, "special_tiles"); - // If nil, try the deprecated name "special_materials" instead - if(lua_isnil(L, -1)){ - lua_pop(L, 1); - warn_if_field_exists(L, index, "special_materials", - "Deprecated; new name is \"special_tiles\"."); - lua_getfield(L, index, "special_materials"); - } if(lua_istable(L, -1)){ int table = lua_gettop(L); lua_pushnil(L); @@ -1000,22 +986,25 @@ void push_nodebox(lua_State *L, const NodeBox &box) push_aabb3f(L, box.wall_side); lua_setfield(L, -2, "wall_side"); break; - case NODEBOX_CONNECTED: + case NODEBOX_CONNECTED: { lua_pushstring(L, "connected"); lua_setfield(L, -2, "type"); - push_box(L, box.connect_top); + const auto &c = box.getConnected(); + push_box(L, c.connect_top); lua_setfield(L, -2, "connect_top"); - push_box(L, box.connect_bottom); + push_box(L, c.connect_bottom); lua_setfield(L, -2, "connect_bottom"); - push_box(L, box.connect_front); + push_box(L, c.connect_front); lua_setfield(L, -2, "connect_front"); - push_box(L, box.connect_back); + push_box(L, c.connect_back); lua_setfield(L, -2, "connect_back"); - push_box(L, box.connect_left); + push_box(L, c.connect_left); lua_setfield(L, -2, "connect_left"); - push_box(L, box.connect_right); + push_box(L, c.connect_right); lua_setfield(L, -2, "connect_right"); + // half the boxes are missing here? break; + } default: FATAL_ERROR("Invalid box.type"); break; @@ -1048,22 +1037,26 @@ void push_palette(lua_State *L, const std::vector<video::SColor> *palette) /******************************************************************************/ void read_server_sound_params(lua_State *L, int index, - ServerSoundParams ¶ms) + ServerPlayingSound ¶ms) { if(index < 0) index = lua_gettop(L) + 1 + index; - // Clear - params = ServerSoundParams(); + if(lua_istable(L, index)){ + // Functional overlap: this may modify SimpleSoundSpec contents + getfloatfield(L, index, "fade", params.spec.fade); + getfloatfield(L, index, "pitch", params.spec.pitch); + getboolfield(L, index, "loop", params.spec.loop); + getfloatfield(L, index, "gain", params.gain); + + // Handle positional information getstringfield(L, index, "to_player", params.to_player); - getfloatfield(L, index, "fade", params.fade); - getfloatfield(L, index, "pitch", params.pitch); lua_getfield(L, index, "pos"); if(!lua_isnil(L, -1)){ v3f p = read_v3f(L, -1)*BS; params.pos = p; - params.type = ServerSoundParams::SSP_POSITIONAL; + params.type = SoundLocation::Position; } lua_pop(L, 1); lua_getfield(L, index, "object"); @@ -1072,13 +1065,12 @@ void read_server_sound_params(lua_State *L, int index, ServerActiveObject *sao = ObjectRef::getobject(ref); if(sao){ params.object = sao->getId(); - params.type = ServerSoundParams::SSP_OBJECT; + params.type = SoundLocation::Object; } } lua_pop(L, 1); params.max_hear_distance = BS*getfloatfield_default(L, index, "max_hear_distance", params.max_hear_distance/BS); - getboolfield(L, index, "loop", params.loop); getstringfield(L, index, "exclude_player", params.exclude_player); } } @@ -1143,20 +1135,24 @@ NodeBox read_nodebox(lua_State *L, int index) NODEBOXREAD(nodebox.wall_top, "wall_top"); NODEBOXREAD(nodebox.wall_bottom, "wall_bottom"); NODEBOXREAD(nodebox.wall_side, "wall_side"); - NODEBOXREADVEC(nodebox.connect_top, "connect_top"); - NODEBOXREADVEC(nodebox.connect_bottom, "connect_bottom"); - NODEBOXREADVEC(nodebox.connect_front, "connect_front"); - NODEBOXREADVEC(nodebox.connect_left, "connect_left"); - NODEBOXREADVEC(nodebox.connect_back, "connect_back"); - NODEBOXREADVEC(nodebox.connect_right, "connect_right"); - NODEBOXREADVEC(nodebox.disconnected_top, "disconnected_top"); - NODEBOXREADVEC(nodebox.disconnected_bottom, "disconnected_bottom"); - NODEBOXREADVEC(nodebox.disconnected_front, "disconnected_front"); - NODEBOXREADVEC(nodebox.disconnected_left, "disconnected_left"); - NODEBOXREADVEC(nodebox.disconnected_back, "disconnected_back"); - NODEBOXREADVEC(nodebox.disconnected_right, "disconnected_right"); - NODEBOXREADVEC(nodebox.disconnected, "disconnected"); - NODEBOXREADVEC(nodebox.disconnected_sides, "disconnected_sides"); + + if (nodebox.type == NODEBOX_CONNECTED) { + auto &c = nodebox.getConnected(); + NODEBOXREADVEC(c.connect_top, "connect_top"); + NODEBOXREADVEC(c.connect_bottom, "connect_bottom"); + NODEBOXREADVEC(c.connect_front, "connect_front"); + NODEBOXREADVEC(c.connect_left, "connect_left"); + NODEBOXREADVEC(c.connect_back, "connect_back"); + NODEBOXREADVEC(c.connect_right, "connect_right"); + NODEBOXREADVEC(c.disconnected_top, "disconnected_top"); + NODEBOXREADVEC(c.disconnected_bottom, "disconnected_bottom"); + NODEBOXREADVEC(c.disconnected_front, "disconnected_front"); + NODEBOXREADVEC(c.disconnected_left, "disconnected_left"); + NODEBOXREADVEC(c.disconnected_back, "disconnected_back"); + NODEBOXREADVEC(c.disconnected_right, "disconnected_right"); + NODEBOXREADVEC(c.disconnected, "disconnected"); + NODEBOXREADVEC(c.disconnected_sides, "disconnected_sides"); + } return nodebox; } @@ -1351,22 +1347,27 @@ void push_tool_capabilities(lua_State *L, } /******************************************************************************/ -void push_inventory_list(lua_State *L, Inventory *inv, const char *name) +void push_inventory_list(lua_State *L, const InventoryList &invlist) { - InventoryList *invlist = inv->getList(name); - if(invlist == NULL){ - lua_pushnil(L); - return; + push_items(L, invlist.getItems()); +} + +/******************************************************************************/ +void push_inventory_lists(lua_State *L, const Inventory &inv) +{ + const auto &lists = inv.getLists(); + lua_createtable(L, 0, lists.size()); + for(const InventoryList *list : lists) { + const std::string &name = list->getName(); + lua_pushlstring(L, name.c_str(), name.size()); + push_inventory_list(L, *list); + lua_rawset(L, -3); } - std::vector<ItemStack> items; - for(u32 i=0; i<invlist->getSize(); i++) - items.push_back(invlist->getItem(i)); - push_items(L, items); } /******************************************************************************/ void read_inventory_list(lua_State *L, int tableindex, - Inventory *inv, const char *name, Server* srv, int forcesize) + Inventory *inv, const char *name, IGameDef *gdef, int forcesize) { if(tableindex < 0) tableindex = lua_gettop(L) + 1 + tableindex; @@ -1378,7 +1379,7 @@ void read_inventory_list(lua_State *L, int tableindex, } // Get Lua-specified items to insert into the list - std::vector<ItemStack> items = read_items(L, tableindex,srv); + std::vector<ItemStack> items = read_items(L, tableindex, gdef); size_t listsize = (forcesize >= 0) ? forcesize : items.size(); // Create or resize/clear list @@ -1630,7 +1631,7 @@ void push_items(lua_State *L, const std::vector<ItemStack> &items) } /******************************************************************************/ -std::vector<ItemStack> read_items(lua_State *L, int index, Server *srv) +std::vector<ItemStack> read_items(lua_State *L, int index, IGameDef *gdef) { if(index < 0) index = lua_gettop(L) + 1 + index; @@ -1646,7 +1647,7 @@ std::vector<ItemStack> read_items(lua_State *L, int index, Server *srv) if (items.size() < (u32) key) { items.resize(key); } - items[key - 1] = read_item(L, -1, srv->idef()); + items[key - 1] = read_item(L, -1, gdef->idef()); lua_pop(L, 1); } return items; @@ -1697,24 +1698,19 @@ bool read_noiseparams(lua_State *L, int index, NoiseParams *np) void push_noiseparams(lua_State *L, NoiseParams *np) { lua_newtable(L); - push_float_string(L, np->offset); - lua_setfield(L, -2, "offset"); - push_float_string(L, np->scale); - lua_setfield(L, -2, "scale"); - push_float_string(L, np->persist); - lua_setfield(L, -2, "persistence"); - push_float_string(L, np->lacunarity); - lua_setfield(L, -2, "lacunarity"); - lua_pushnumber(L, np->seed); - lua_setfield(L, -2, "seed"); - lua_pushnumber(L, np->octaves); - lua_setfield(L, -2, "octaves"); + setfloatfield(L, -1, "offset", np->offset); + setfloatfield(L, -1, "scale", np->scale); + setfloatfield(L, -1, "persist", np->persist); + setfloatfield(L, -1, "persistence", np->persist); + setfloatfield(L, -1, "lacunarity", np->lacunarity); + setintfield( L, -1, "seed", np->seed); + setintfield( L, -1, "octaves", np->octaves); push_flags_string(L, flagdesc_noiseparams, np->flags, np->flags); lua_setfield(L, -2, "flags"); - push_v3_float_string(L, np->spread); + push_v3f(L, np->spread); lua_setfield(L, -2, "spread"); } @@ -1977,6 +1973,12 @@ void push_hud_element(lua_State *L, HudElement *elem) lua_pushnumber(L, elem->number); lua_setfield(L, -2, "number"); + if (elem->type == HUD_ELEM_WAYPOINT) { + // waypoints reuse the item field to store precision, precision = item - 1 + lua_pushnumber(L, elem->item - 1); + lua_setfield(L, -2, "precision"); + } + // push the item field for waypoints as well for backwards compatibility lua_pushnumber(L, elem->item); lua_setfield(L, -2, "item"); @@ -2141,3 +2143,35 @@ void push_collision_move_result(lua_State *L, const collisionMoveResult &res) lua_setfield(L, -2, "collisions"); /**/ } + + +void push_mod_spec(lua_State *L, const ModSpec &spec, bool include_unsatisfied) +{ + lua_newtable(L); + + lua_pushstring(L, spec.name.c_str()); + lua_setfield(L, -2, "name"); + + lua_pushstring(L, spec.author.c_str()); + lua_setfield(L, -2, "author"); + + lua_pushinteger(L, spec.release); + lua_setfield(L, -2, "release"); + + lua_pushstring(L, spec.desc.c_str()); + lua_setfield(L, -2, "description"); + + lua_pushstring(L, spec.path.c_str()); + lua_setfield(L, -2, "path"); + + lua_pushstring(L, spec.virtual_path.c_str()); + lua_setfield(L, -2, "virtual_path"); + + lua_newtable(L); + int i = 1; + for (const auto &dep : spec.unsatisfied_depends) { + lua_pushstring(L, dep.c_str()); + lua_rawseti(L, -2, i++); + } + lua_setfield(L, -2, "unsatisfied_depends"); +} diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index e762604a4..ade3e4c1e 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -42,6 +42,7 @@ extern "C" { // We do a explicit path include because by default c_content.h include src/client/hud.h // prior to the src/hud.h, which is not good on server only build #include "../../hud.h" +#include "content/mods.h" namespace Json { class Value; } @@ -53,12 +54,13 @@ struct ItemDefinition; struct ToolCapabilities; struct ObjectProperties; struct SimpleSoundSpec; -struct ServerSoundParams; +struct ServerPlayingSound; class Inventory; +class InventoryList; struct NodeBox; struct ContentFeatures; struct TileDef; -class Server; +class IGameDef; struct DigParams; struct HitParams; struct EnumString; @@ -90,7 +92,7 @@ void read_soundspec (lua_State *L, int index, NodeBox read_nodebox (lua_State *L, int index); void read_server_sound_params (lua_State *L, int index, - ServerSoundParams ¶ms); + ServerPlayingSound ¶ms); void push_dig_params (lua_State *L, const DigParams ¶ms); @@ -120,11 +122,12 @@ void push_object_properties (lua_State *L, ObjectProperties *prop); void push_inventory_list (lua_State *L, - Inventory *inv, - const char *name); + const InventoryList &invlist); +void push_inventory_lists (lua_State *L, + const Inventory &inv); void read_inventory_list (lua_State *L, int tableindex, Inventory *inv, const char *name, - Server *srv, int forcesize=-1); + IGameDef *gdef, int forcesize=-1); MapNode readnode (lua_State *L, int index, const NodeDefManager *ndef); @@ -164,7 +167,7 @@ void push_items (lua_State *L, std::vector<ItemStack> read_items (lua_State *L, int index, - Server* srv); + IGameDef* gdef); void push_soundspec (lua_State *L, const SimpleSoundSpec &spec); @@ -202,3 +205,5 @@ void push_hud_element (lua_State *L, HudElement *elem); bool read_hud_change (lua_State *L, HudElementStat &stat, HudElement *elem, void **value); void push_collision_move_result(lua_State *L, const collisionMoveResult &res); + +void push_mod_spec(lua_State *L, const ModSpec &spec, bool include_unsatisfied); diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index 7898b197d..69da35b73 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -18,8 +18,8 @@ with this program; if not, write to the Free Software Foundation, Inc., */ extern "C" { -#include "lua.h" -#include "lauxlib.h" +#include <lua.h> +#include <lauxlib.h> } #include "util/numeric.h" @@ -29,55 +29,52 @@ extern "C" { #include "common/c_internal.h" #include "constants.h" #include <set> +#include <cmath> -#define CHECK_TYPE(index, name, type) { \ +#define CHECK_TYPE(index, name, type) do { \ int t = lua_type(L, (index)); \ if (t != (type)) { \ throw LuaError(std::string("Invalid ") + (name) + \ " (expected " + lua_typename(L, (type)) + \ " got " + lua_typename(L, t) + ")."); \ } \ - } -#define CHECK_POS_COORD(name) CHECK_TYPE(-1, "position coordinate '" name "'", LUA_TNUMBER) -#define CHECK_FLOAT_RANGE(value, name) \ -if (value < F1000_MIN || value > F1000_MAX) { \ - std::ostringstream error_text; \ - error_text << "Invalid float vector dimension range '" name "' " << \ - "(expected " << F1000_MIN << " < " name " < " << F1000_MAX << \ - " got " << value << ")." << std::endl; \ - throw LuaError(error_text.str()); \ -} -#define CHECK_POS_TAB(index) CHECK_TYPE(index, "position", LUA_TTABLE) + } while(0) + +#define CHECK_FLOAT(value, name) do {\ + if (std::isnan(value) || std::isinf(value)) { \ + throw LuaError("Invalid float value for '" name \ + "' (NaN or infinity)"); \ + } \ + } while (0) + +#define CHECK_POS_COORD(name) CHECK_TYPE(-1, "vector coordinate " name, LUA_TNUMBER) +#define CHECK_POS_TAB(index) CHECK_TYPE(index, "vector", LUA_TTABLE) /** - * A helper which sets (if available) the vector metatable from builtin as metatable - * for the table on top of the stack + * A helper which sets the vector metatable for the table on top of the stack */ static void set_vector_metatable(lua_State *L) { - // get vector.metatable - lua_getglobal(L, "vector"); - if (!lua_istable(L, -1)) { - // there is no global vector table - lua_pop(L, 1); - errorstream << "set_vector_metatable in c_converter.cpp: " << - "missing global vector table" << std::endl; - return; - } - lua_getfield(L, -1, "metatable"); - // set the metatable - lua_setmetatable(L, -3); - // pop vector global - lua_pop(L, 1); + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_VECTOR_METATABLE); + lua_setmetatable(L, -2); } - -void push_float_string(lua_State *L, float value) +// Retrieve an integer vector where all components are optional +template<class T> +static bool getv3intfield(lua_State *L, int index, + const char *fieldname, T &result) { - auto str = ftos(value); - lua_pushstring(L, str.c_str()); + lua_getfield(L, index, fieldname); + bool got = false; + if (lua_istable(L, -1)) { + got |= getintfield(L, -1, "x", result.X); + got |= getintfield(L, -1, "y", result.Y); + got |= getintfield(L, -1, "z", result.Z); + } + lua_pop(L, 1); + return got; } void push_v3f(lua_State *L, v3f p) @@ -101,26 +98,6 @@ void push_v2f(lua_State *L, v2f p) lua_setfield(L, -2, "y"); } -void push_v3_float_string(lua_State *L, v3f p) -{ - lua_createtable(L, 0, 3); - push_float_string(L, p.X); - lua_setfield(L, -2, "x"); - push_float_string(L, p.Y); - lua_setfield(L, -2, "y"); - push_float_string(L, p.Z); - lua_setfield(L, -2, "z"); -} - -void push_v2_float_string(lua_State *L, v2f p) -{ - lua_createtable(L, 0, 2); - push_float_string(L, p.X); - lua_setfield(L, -2, "x"); - push_float_string(L, p.Y); - lua_setfield(L, -2, "y"); -} - v2s16 read_v2s16(lua_State *L, int index) { v2s16 p; @@ -185,10 +162,12 @@ v2f check_v2f(lua_State *L, int index) lua_getfield(L, index, "x"); CHECK_POS_COORD("x"); p.X = lua_tonumber(L, -1); + CHECK_FLOAT(p.X, "x"); lua_pop(L, 1); lua_getfield(L, index, "y"); CHECK_POS_COORD("y"); p.Y = lua_tonumber(L, -1); + CHECK_FLOAT(p.Y, "y"); lua_pop(L, 1); return p; } @@ -216,17 +195,17 @@ v3f check_v3f(lua_State *L, int index) lua_getfield(L, index, "x"); CHECK_POS_COORD("x"); pos.X = lua_tonumber(L, -1); - CHECK_FLOAT_RANGE(pos.X, "x") + CHECK_FLOAT(pos.X, "x"); lua_pop(L, 1); lua_getfield(L, index, "y"); CHECK_POS_COORD("y"); pos.Y = lua_tonumber(L, -1); - CHECK_FLOAT_RANGE(pos.Y, "y") + CHECK_FLOAT(pos.Y, "y"); lua_pop(L, 1); lua_getfield(L, index, "z"); CHECK_POS_COORD("z"); pos.Z = lua_tonumber(L, -1); - CHECK_FLOAT_RANGE(pos.Z, "z") + CHECK_FLOAT(pos.Z, "z"); lua_pop(L, 1); return pos; } @@ -254,17 +233,17 @@ v3d check_v3d(lua_State *L, int index) lua_getfield(L, index, "x"); CHECK_POS_COORD("x"); pos.X = lua_tonumber(L, -1); - CHECK_FLOAT_RANGE(pos.X, "x") + CHECK_FLOAT(pos.X, "x"); lua_pop(L, 1); lua_getfield(L, index, "y"); CHECK_POS_COORD("y"); pos.Y = lua_tonumber(L, -1); - CHECK_FLOAT_RANGE(pos.Y, "y") + CHECK_FLOAT(pos.Y, "y"); lua_pop(L, 1); lua_getfield(L, index, "z"); CHECK_POS_COORD("z"); pos.Z = lua_tonumber(L, -1); - CHECK_FLOAT_RANGE(pos.Z, "z") + CHECK_FLOAT(pos.Z, "z"); lua_pop(L, 1); return pos; } diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index 6ad6f3212..2af726d16 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -61,23 +61,10 @@ bool getintfield(lua_State *L, int table, return got; } -template<class T> -bool getv3intfield(lua_State *L, int index, - const char *fieldname, T &result) -{ - lua_getfield(L, index, fieldname); - bool got = false; - if (lua_istable(L, -1)) { - got |= getintfield(L, -1, "x", result.X); - got |= getintfield(L, -1, "y", result.Y); - got |= getintfield(L, -1, "z", result.Z); - } - lua_pop(L, 1); - return got; -} - +// Retrieve an v3s16 where all components are optional (falls back to default) v3s16 getv3s16field_default(lua_State *L, int table, const char *fieldname, v3s16 default_); + bool getstringfield(lua_State *L, int table, const char *fieldname, std::string &result); size_t getstringlistfield(lua_State *L, int table, @@ -100,6 +87,7 @@ void setboolfield(lua_State *L, int table, const char *fieldname, bool value); v3f checkFloatPos (lua_State *L, int index); +v2f check_v2f (lua_State *L, int index); v3f check_v3f (lua_State *L, int index); v3s16 check_v3s16 (lua_State *L, int index); @@ -118,9 +106,6 @@ std::vector<aabb3f> read_aabb3f_vector (lua_State *L, int index, f32 scale); size_t read_stringlist (lua_State *L, int index, std::vector<std::string> *result); -void push_float_string (lua_State *L, float value); -void push_v3_float_string(lua_State *L, v3f p); -void push_v2_float_string(lua_State *L, v2f p); void push_v2s16 (lua_State *L, v2s16 p); void push_v2s32 (lua_State *L, v2s32 p); void push_v3s16 (lua_State *L, v3s16 p); diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp index df82dba14..ddd2d184c 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -166,3 +166,17 @@ void log_deprecated(lua_State *L, std::string message, int stack_depth) infostream << script_get_backtrace(L) << std::endl; } +void call_string_dump(lua_State *L, int idx) +{ + // Retrieve string.dump from insecure env to avoid it being tampered with + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP); + if (!lua_isnil(L, -1)) + lua_getfield(L, -1, "string"); + else + lua_getglobal(L, "string"); + lua_getfield(L, -1, "dump"); + lua_remove(L, -2); // remove _G + lua_remove(L, -2); // remove 'string' table + lua_pushvalue(L, idx); + lua_call(L, 1, 1); +} diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h index 94cfd61fb..272a39941 100644 --- a/src/script/common/c_internal.h +++ b/src/script/common/c_internal.h @@ -55,6 +55,8 @@ extern "C" { #define CUSTOM_RIDX_CURRENT_MOD_NAME (CUSTOM_RIDX_BASE + 2) #define CUSTOM_RIDX_BACKTRACE (CUSTOM_RIDX_BASE + 3) #define CUSTOM_RIDX_HTTP_API_LUA (CUSTOM_RIDX_BASE + 4) +#define CUSTOM_RIDX_VECTOR_METATABLE (CUSTOM_RIDX_BASE + 5) +#define CUSTOM_RIDX_METATABLE_MAP (CUSTOM_RIDX_BASE + 6) // Determine if CUSTOM_RIDX_SCRIPTAPI will hold a light or full userdata @@ -138,3 +140,7 @@ DeprecatedHandlingMode get_deprecated_handling_mode(); * @param stack_depth How far on the stack to the first user function (ie: not builtin or core) */ void log_deprecated(lua_State *L, std::string message, int stack_depth = 1); + +// Safely call string.dump on a function value +// (does not pop, leaves one value on stack) +void call_string_dump(lua_State *L, int idx); diff --git a/src/script/common/c_packer.cpp b/src/script/common/c_packer.cpp new file mode 100644 index 000000000..597f5e447 --- /dev/null +++ b/src/script/common/c_packer.cpp @@ -0,0 +1,596 @@ +/* +Minetest +Copyright (C) 2022 sfan5 <sfan5@live.de> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include <cstdio> +#include <cstring> +#include <cmath> +#include <cassert> +#include <unordered_set> +#include <unordered_map> +#include "c_packer.h" +#include "c_internal.h" +#include "log.h" +#include "debug.h" +#include "threading/mutex_auto_lock.h" + +extern "C" { +#include <lauxlib.h> +} + +// +// Helpers +// + +// convert negative index to absolute position on Lua stack +static inline int absidx(lua_State *L, int idx) +{ + assert(idx < 0); + return lua_gettop(L) + idx + 1; +} + +// does the type put anything into PackedInstr::sdata? +static inline bool uses_sdata(int type) +{ + switch (type) { + case LUA_TSTRING: + case LUA_TFUNCTION: + case LUA_TUSERDATA: + return true; + default: + return false; + } +} + +// does the type put anything into PackedInstr::<union>? +static inline bool uses_union(int type) +{ + switch (type) { + case LUA_TNIL: + case LUA_TSTRING: + case LUA_TFUNCTION: + return false; + default: + return true; + } +} + +static inline bool can_set_into(int ktype, int vtype) +{ + switch (ktype) { + case LUA_TNUMBER: + return !uses_union(vtype); + case LUA_TSTRING: + return !uses_sdata(vtype); + default: + return false; + } +} + +// is the key suitable for use with set_into? +static inline bool suitable_key(lua_State *L, int idx) +{ + if (lua_type(L, idx) == LUA_TSTRING) { + // strings may not have a NULL byte (-> lua_setfield) + size_t len; + const char *str = lua_tolstring(L, idx, &len); + return strlen(str) == len; + } else { + assert(lua_type(L, idx) == LUA_TNUMBER); + // numbers must fit into an s32 and be integers (-> lua_rawseti) + lua_Number n = lua_tonumber(L, idx); + return std::floor(n) == n && n >= S32_MIN && n <= S32_MAX; + } +} + +namespace { + // checks if you left any values on the stack, for debugging + class StackChecker { + lua_State *L; + int top; + public: + StackChecker(lua_State *L) : L(L), top(lua_gettop(L)) {} + ~StackChecker() { + assert(lua_gettop(L) >= top); + if (lua_gettop(L) > top) { + rawstream << "Lua stack not cleaned up: " + << lua_gettop(L) << " != " << top + << " (false-positive if exception thrown)" << std::endl; + } + } + }; + + // Since an std::vector may reallocate, this is the only safe way to keep + // a reference to a particular element. + template <typename T> + class VectorRef { + std::vector<T> *vec; + size_t idx; + VectorRef(std::vector<T> *vec, size_t idx) : vec(vec), idx(idx) {} + public: + constexpr VectorRef() : vec(nullptr), idx(0) {} + static VectorRef<T> front(std::vector<T> &vec) { + return VectorRef(&vec, 0); + } + static VectorRef<T> back(std::vector<T> &vec) { + return VectorRef(&vec, vec.size() - 1); + } + T &operator*() { return (*vec)[idx]; } + T *operator->() { return &(*vec)[idx]; } + operator bool() const { return vec != nullptr; } + }; + + struct Packer { + PackInFunc fin; + PackOutFunc fout; + }; + + typedef std::pair<std::string, Packer> PackerTuple; +} + +static inline auto emplace(PackedValue &pv, s16 type) +{ + pv.i.emplace_back(); + auto ref = VectorRef<PackedInstr>::back(pv.i); + ref->type = type; + // Initialize fields that may be left untouched + if (type == LUA_TTABLE) { + ref->uidata1 = 0; + ref->uidata2 = 0; + } else if (type == LUA_TUSERDATA) { + ref->ptrdata = nullptr; + } else if (type == INSTR_POP) { + ref->sidata2 = 0; + } + return ref; +} + +// +// Management of registered packers +// + +static std::unordered_map<std::string, Packer> g_packers; +static std::mutex g_packers_lock; + +void script_register_packer(lua_State *L, const char *regname, + PackInFunc fin, PackOutFunc fout) +{ + // Store away callbacks + { + MutexAutoLock autolock(g_packers_lock); + auto it = g_packers.find(regname); + if (it == g_packers.end()) { + auto &ref = g_packers[regname]; + ref.fin = fin; + ref.fout = fout; + } else { + FATAL_ERROR_IF(it->second.fin != fin || it->second.fout != fout, + "Packer registered twice with mismatching callbacks"); + } + } + + // Save metatable so we can identify instances later + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP); + if (lua_isnil(L, -1)) { + lua_newtable(L); + lua_pushvalue(L, -1); + lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP); + } + + luaL_getmetatable(L, regname); + FATAL_ERROR_IF(lua_isnil(L, -1), "No metatable registered with that name"); + + // CUSTOM_RIDX_METATABLE_MAP contains { [metatable] = "regname", ... } + // check first + lua_pushstring(L, regname); + lua_rawget(L, -3); + if (!lua_isnil(L, -1)) { + FATAL_ERROR_IF(lua_topointer(L, -1) != lua_topointer(L, -2), + "Packer registered twice with inconsistent metatable"); + } + lua_pop(L, 1); + // then set + lua_pushstring(L, regname); + lua_rawset(L, -3); + + lua_pop(L, 1); +} + +static bool find_packer(const char *regname, PackerTuple &out) +{ + MutexAutoLock autolock(g_packers_lock); + auto it = g_packers.find(regname); + if (it == g_packers.end()) + return false; + // copy data for thread safety + out.first = it->first; + out.second = it->second; + return true; +} + +static bool find_packer(lua_State *L, int idx, PackerTuple &out) +{ +#ifndef NDEBUG + StackChecker checker(L); +#endif + + // retrieve metatable of the object + if (lua_getmetatable(L, idx) != 1) + return false; + + // use our global table to map it to the registry name + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP); + assert(lua_istable(L, -1)); + lua_pushvalue(L, -2); + lua_rawget(L, -2); + if (lua_isnil(L, -1)) { + lua_pop(L, 3); + return false; + } + + // load the associated data + bool found = find_packer(lua_tostring(L, -1), out); + FATAL_ERROR_IF(!found, "Inconsistent internal state"); + lua_pop(L, 3); + return true; +} + +// +// Packing implementation +// + +static VectorRef<PackedInstr> record_object(lua_State *L, int idx, PackedValue &pv, + std::unordered_map<const void *, s32> &seen) +{ + const void *ptr = lua_topointer(L, idx); + assert(ptr); + auto found = seen.find(ptr); + if (found == seen.end()) { + seen[ptr] = pv.i.size(); + return VectorRef<PackedInstr>(); + } + s32 ref = found->second; + assert(ref < (s32)pv.i.size()); + // reuse the value from first time + auto r = emplace(pv, INSTR_PUSHREF); + r->ref = ref; + pv.i[ref].keep_ref = true; + return r; +} + +static VectorRef<PackedInstr> pack_inner(lua_State *L, int idx, int vidx, PackedValue &pv, + std::unordered_map<const void *, s32> &seen) +{ +#ifndef NDEBUG + StackChecker checker(L); + assert(idx > 0); + assert(vidx > 0); +#endif + + switch (lua_type(L, idx)) { + case LUA_TNONE: + case LUA_TNIL: + return emplace(pv, LUA_TNIL); + case LUA_TBOOLEAN: { + auto r = emplace(pv, LUA_TBOOLEAN); + r->bdata = lua_toboolean(L, idx); + return r; + } + case LUA_TNUMBER: { + auto r = emplace(pv, LUA_TNUMBER); + r->ndata = lua_tonumber(L, idx); + return r; + } + case LUA_TSTRING: { + auto r = emplace(pv, LUA_TSTRING); + size_t len; + const char *str = lua_tolstring(L, idx, &len); + assert(str); + r->sdata.assign(str, len); + return r; + } + case LUA_TTABLE: { + auto r = record_object(L, idx, pv, seen); + if (r) + return r; + break; // execution continues + } + case LUA_TFUNCTION: { + auto r = record_object(L, idx, pv, seen); + if (r) + return r; + r = emplace(pv, LUA_TFUNCTION); + call_string_dump(L, idx); + size_t len; + const char *str = lua_tolstring(L, -1, &len); + assert(str); + r->sdata.assign(str, len); + lua_pop(L, 1); + return r; + } + case LUA_TUSERDATA: { + auto r = record_object(L, idx, pv, seen); + if (r) + return r; + PackerTuple ser; + if (!find_packer(L, idx, ser)) + throw LuaError("Cannot serialize unsupported userdata"); + pv.contains_userdata = true; + r = emplace(pv, LUA_TUSERDATA); + r->sdata = ser.first; + r->ptrdata = ser.second.fin(L, idx); + return r; + } + default: { + std::string err = "Cannot serialize type "; + err += lua_typename(L, lua_type(L, idx)); + throw LuaError(err); + } + } + + // LUA_TTABLE + lua_checkstack(L, 5); + + auto rtable = emplace(pv, LUA_TTABLE); + const int vi_table = vidx++; + + lua_pushnil(L); + while (lua_next(L, idx) != 0) { + // key at -2, value at -1 + const int ktype = lua_type(L, -2), vtype = lua_type(L, -1); + if (ktype == LUA_TNUMBER) + rtable->uidata1++; // narr + else + rtable->uidata2++; // nrec + + // check if we can use a shortcut + if (can_set_into(ktype, vtype) && suitable_key(L, -2)) { + // push only the value + auto rval = pack_inner(L, absidx(L, -1), vidx, pv, seen); + rval->pop = rval->type != LUA_TTABLE; + // and where to put it: + rval->set_into = vi_table; + if (ktype == LUA_TSTRING) + rval->sdata = lua_tostring(L, -2); + else + rval->sidata1 = lua_tointeger(L, -2); + // pop tables after the fact + if (!rval->pop) { + auto ri1 = emplace(pv, INSTR_POP); + ri1->sidata1 = vidx; + } + } else { + // push the key and value + pack_inner(L, absidx(L, -2), vidx, pv, seen); + vidx++; + pack_inner(L, absidx(L, -1), vidx, pv, seen); + vidx++; + // push an instruction to set them + auto ri1 = emplace(pv, INSTR_SETTABLE); + ri1->set_into = vi_table; + ri1->sidata1 = vidx - 2; + ri1->sidata2 = vidx - 1; + ri1->pop = true; + vidx -= 2; + } + + lua_pop(L, 1); + } + + assert(vidx == vi_table + 1); + return rtable; +} + +PackedValue *script_pack(lua_State *L, int idx) +{ + if (idx < 0) + idx = absidx(L, idx); + + PackedValue pv; + std::unordered_map<const void *, s32> seen; + pack_inner(L, idx, 1, pv, seen); + + return new PackedValue(std::move(pv)); +} + +// +// Unpacking implementation +// + +void script_unpack(lua_State *L, PackedValue *pv) +{ + lua_newtable(L); // table at index top to track ref indices -> objects + const int top = lua_gettop(L); + int ctr = 0; + + for (size_t packed_idx = 0; packed_idx < pv->i.size(); packed_idx++) { + auto &i = pv->i[packed_idx]; + + // If leaving values on stack make sure there's space (every 5th iteration) + if (!i.pop && (ctr++) >= 5) { + lua_checkstack(L, 5); + ctr = 0; + } + + switch (i.type) { + /* Instructions */ + case INSTR_SETTABLE: + lua_pushvalue(L, top + i.sidata1); // key + lua_pushvalue(L, top + i.sidata2); // value + lua_rawset(L, top + i.set_into); + if (i.pop) { + if (i.sidata1 != i.sidata2) { + // removing moves indices so pop higher index first + lua_remove(L, top + std::max(i.sidata1, i.sidata2)); + lua_remove(L, top + std::min(i.sidata1, i.sidata2)); + } else { + lua_remove(L, top + i.sidata1); + } + } + continue; + case INSTR_POP: + lua_remove(L, top + i.sidata1); + if (i.sidata2 > 0) + lua_remove(L, top + i.sidata2); + continue; + case INSTR_PUSHREF: + lua_pushinteger(L, i.ref); + lua_rawget(L, top); + break; + + /* Lua types */ + case LUA_TNIL: + lua_pushnil(L); + break; + case LUA_TBOOLEAN: + lua_pushboolean(L, i.bdata); + break; + case LUA_TNUMBER: + lua_pushnumber(L, i.ndata); + break; + case LUA_TSTRING: + lua_pushlstring(L, i.sdata.data(), i.sdata.size()); + break; + case LUA_TTABLE: + lua_createtable(L, i.uidata1, i.uidata2); + break; + case LUA_TFUNCTION: + luaL_loadbuffer(L, i.sdata.data(), i.sdata.size(), nullptr); + break; + case LUA_TUSERDATA: { + PackerTuple ser; + sanity_check(find_packer(i.sdata.c_str(), ser)); + ser.second.fout(L, i.ptrdata); + i.ptrdata = nullptr; // ownership taken by callback + break; + } + + default: + assert(0); + break; + } + + if (i.keep_ref) { + lua_pushinteger(L, packed_idx); + lua_pushvalue(L, -2); + lua_rawset(L, top); + } + + if (i.set_into) { + if (!i.pop) + lua_pushvalue(L, -1); + if (uses_sdata(i.type)) + lua_rawseti(L, top + i.set_into, i.sidata1); + else + lua_setfield(L, top + i.set_into, i.sdata.c_str()); + } else { + if (i.pop) + lua_pop(L, 1); + } + } + + // as part of the unpacking process we take ownership of all userdata + pv->contains_userdata = false; + // leave exactly one value on the stack + lua_settop(L, top+1); + lua_remove(L, top); +} + +// +// PackedValue +// + +PackedValue::~PackedValue() +{ + if (!contains_userdata) + return; + for (auto &i : this->i) { + if (i.type == LUA_TUSERDATA && i.ptrdata) { + PackerTuple ser; + if (find_packer(i.sdata.c_str(), ser)) { + // tell it to deallocate object + ser.second.fout(nullptr, i.ptrdata); + } else { + assert(false); + } + } + } +} + +// +// script_dump_packed +// + +#ifndef NDEBUG +void script_dump_packed(const PackedValue *val) +{ + printf("instruction stream: [\n"); + for (const auto &i : val->i) { + printf("\t("); + switch (i.type) { + case INSTR_SETTABLE: + printf("SETTABLE(%d, %d)", i.sidata1, i.sidata2); + break; + case INSTR_POP: + printf(i.sidata2 ? "POP(%d, %d)" : "POP(%d)", i.sidata1, i.sidata2); + break; + case INSTR_PUSHREF: + printf("PUSHREF(%d)", i.ref); + break; + case LUA_TNIL: + printf("nil"); + break; + case LUA_TBOOLEAN: + printf(i.bdata ? "true" : "false"); + break; + case LUA_TNUMBER: + printf("%f", i.ndata); + break; + case LUA_TSTRING: + printf("\"%s\"", i.sdata.c_str()); + break; + case LUA_TTABLE: + printf("table(%d, %d)", i.uidata1, i.uidata2); + break; + case LUA_TFUNCTION: + printf("function(%lu byte)", i.sdata.size()); + break; + case LUA_TUSERDATA: + printf("userdata %s %p", i.sdata.c_str(), i.ptrdata); + break; + default: + printf("!!UNKNOWN!!"); + break; + } + if (i.set_into) { + if (i.type >= 0 && uses_sdata(i.type)) + printf(", k=%d, into=%d", i.sidata1, i.set_into); + else if (i.type >= 0) + printf(", k=\"%s\", into=%d", i.sdata.c_str(), i.set_into); + else + printf(", into=%d", i.set_into); + } + if (i.keep_ref) + printf(", keep_ref"); + if (i.pop) + printf(", pop"); + printf(")\n"); + } + printf("]\n"); +} +#endif diff --git a/src/script/common/c_packer.h b/src/script/common/c_packer.h new file mode 100644 index 000000000..fe072c10a --- /dev/null +++ b/src/script/common/c_packer.h @@ -0,0 +1,126 @@ +/* +Minetest +Copyright (C) 2022 sfan5 <sfan5@live.de> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include <string> +#include <vector> +#include "irrlichttypes.h" +#include "util/basic_macros.h" + +extern "C" { +#include <lua.h> +} + +/* + This file defines an in-memory representation of Lua objects including + support for functions and userdata. It it used to move data between Lua + states and cannot be used for persistence or network transfer. +*/ + +#define INSTR_SETTABLE (-10) +#define INSTR_POP (-11) +#define INSTR_PUSHREF (-12) + +/** + * Represents a single instruction that pushes a new value or works with existing ones. + */ +struct PackedInstr +{ + s16 type; // LUA_T* or INSTR_* + u16 set_into; // set into table on stack + bool keep_ref; // is referenced later by INSTR_PUSHREF? + bool pop; // remove from stack? + union { + bool bdata; // boolean: value + lua_Number ndata; // number: value + struct { + u16 uidata1, uidata2; // table: narr, nrec + }; + struct { + /* + SETTABLE: key index, value index + POP: indices to remove + otherwise w/ set_into: numeric key, - + */ + s32 sidata1, sidata2; + }; + void *ptrdata; // userdata: implementation defined + s32 ref; // PUSHREF: index of referenced instr + }; + /* + - string: value + - function: buffer + - w/ set_into: string key (no null bytes!) + - userdata: name in registry + */ + std::string sdata; + + PackedInstr() : type(0), set_into(0), keep_ref(false), pop(false) {} +}; + +/** + * A packed value can be a primitive like a string or number but also a table + * including all of its contents. It is made up of a linear stream of + * 'instructions' that build the final value when executed. + */ +struct PackedValue +{ + std::vector<PackedInstr> i; + // Indicates whether there are any userdata pointers that need to be deallocated + bool contains_userdata = false; + + PackedValue() = default; + ~PackedValue(); + + DISABLE_CLASS_COPY(PackedValue) + + ALLOW_CLASS_MOVE(PackedValue) +}; + +/* + * Packing callback: Turns a Lua value at given index into a void* + */ +typedef void *(*PackInFunc)(lua_State *L, int idx); +/* + * Unpacking callback: Turns a void* back into the Lua value (left on top of stack) + * + * Note that this function must take ownership of the pointer, so make sure + * to free or keep the memory. + * `L` can be nullptr to indicate that data should just be discarded. + */ +typedef void (*PackOutFunc)(lua_State *L, void *ptr); +/* + * Register a packable type with the name of its metatable. + * + * Even though the callbacks are global this must be called for every Lua state + * that supports objects of this type. + * This function is thread-safe. + */ +void script_register_packer(lua_State *L, const char *regname, + PackInFunc fin, PackOutFunc fout); + +// Pack a Lua value +PackedValue *script_pack(lua_State *L, int idx); +// Unpack a Lua value (left on top of stack) +// Note that this may modify the PackedValue, reusability is not guaranteed! +void script_unpack(lua_State *L, PackedValue *val); + +// Dump contents of PackedValue to stdout for debugging +void script_dump_packed(const PackedValue *val); diff --git a/src/script/common/helper.cpp b/src/script/common/helper.cpp index fbf24e1b7..72de2b14a 100644 --- a/src/script/common/helper.cpp +++ b/src/script/common/helper.cpp @@ -17,35 +17,16 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +extern "C" { +#include <lauxlib.h> +} + #include "helper.h" #include <cmath> -#include <sstream> #include <irr_v2d.h> #include <irr_v3d.h> +#include "c_converter.h" #include "c_types.h" -#include "c_internal.h" - -// imported from c_converter.cpp with pure C++ style -static inline void check_lua_type(lua_State *L, int index, const char *name, int type) -{ - int t = lua_type(L, index); - if (t != type) { - std::string traceback = script_get_backtrace(L); - throw LuaError(std::string("Invalid ") + (name) + " (expected " + - lua_typename(L, (type)) + " got " + lua_typename(L, t) + - ").\n" + traceback); - } -} - -// imported from c_converter.cpp -#define CHECK_POS_COORD(name) \ - check_lua_type(L, -1, "position coordinate '" name "'", LUA_TNUMBER) -#define CHECK_POS_TAB(index) check_lua_type(L, index, "position", LUA_TTABLE) - -bool LuaHelper::isNaN(lua_State *L, int idx) -{ - return lua_type(L, idx) == LUA_TNUMBER && std::isnan(lua_tonumber(L, idx)); -} /* * Read template functions @@ -59,74 +40,41 @@ bool LuaHelper::readParam(lua_State *L, int index) template <> s16 LuaHelper::readParam(lua_State *L, int index) { - return lua_tonumber(L, index); + return luaL_checkinteger(L, index); } template <> int LuaHelper::readParam(lua_State *L, int index) { - return luaL_checkint(L, index); + return luaL_checkinteger(L, index); } template <> float LuaHelper::readParam(lua_State *L, int index) { - if (isNaN(L, index)) - throw LuaError("NaN value is not allowed."); + lua_Number v = luaL_checknumber(L, index); + if (std::isnan(v) && std::isinf(v)) + throw LuaError("Invalid float value (NaN or infinity)"); - return (float)luaL_checknumber(L, index); + return static_cast<float>(v); } template <> v2s16 LuaHelper::readParam(lua_State *L, int index) { - v2s16 p; - CHECK_POS_TAB(index); - lua_getfield(L, index, "x"); - CHECK_POS_COORD("x"); - p.X = readParam<s16>(L, -1); - lua_pop(L, 1); - lua_getfield(L, index, "y"); - CHECK_POS_COORD("y"); - p.Y = readParam<s16>(L, -1); - lua_pop(L, 1); - return p; + return read_v2s16(L, index); } template <> v2f LuaHelper::readParam(lua_State *L, int index) { - v2f p; - CHECK_POS_TAB(index); - lua_getfield(L, index, "x"); - CHECK_POS_COORD("x"); - p.X = readParam<float>(L, -1); - lua_pop(L, 1); - lua_getfield(L, index, "y"); - CHECK_POS_COORD("y"); - p.Y = readParam<float>(L, -1); - lua_pop(L, 1); - return p; + return check_v2f(L, index); } template <> v3f LuaHelper::readParam(lua_State *L, int index) { - v3f p; - CHECK_POS_TAB(index); - lua_getfield(L, index, "x"); - CHECK_POS_COORD("x"); - p.X = readParam<float>(L, -1); - lua_pop(L, 1); - lua_getfield(L, index, "y"); - CHECK_POS_COORD("y"); - p.Y = readParam<float>(L, -1); - lua_pop(L, 1); - lua_getfield(L, index, "z"); - CHECK_POS_COORD("z"); - p.Z = readParam<float>(L, -1); - lua_pop(L, 1); - return p; + return check_v3f(L, index); } template <> diff --git a/src/script/common/helper.h b/src/script/common/helper.h index 6491e73cf..fc462b6ef 100644 --- a/src/script/common/helper.h +++ b/src/script/common/helper.h @@ -21,14 +21,11 @@ with this program; if not, write to the Free Software Foundation, Inc., extern "C" { #include <lua.h> -#include <lauxlib.h> } class LuaHelper { protected: - static bool isNaN(lua_State *L, int idx); - /** * Read a value using a template type T from Lua State L and index * diff --git a/src/script/cpp_api/s_async.cpp b/src/script/cpp_api/s_async.cpp index dacdcd75a..42a794ceb 100644 --- a/src/script/cpp_api/s_async.cpp +++ b/src/script/cpp_api/s_async.cpp @@ -21,9 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <cstdlib> extern "C" { -#include "lua.h" -#include "lauxlib.h" -#include "lualib.h" +#include <lua.h> +#include <lauxlib.h> +#include <lualib.h> } #include "server.h" @@ -32,6 +32,7 @@ extern "C" { #include "filesys.h" #include "porting.h" #include "common/c_internal.h" +#include "common/c_packer.h" #include "lua_api/l_base.h" /******************************************************************************/ @@ -76,19 +77,34 @@ void AsyncEngine::initialize(unsigned int numEngines) { initDone = true; - for (unsigned int i = 0; i < numEngines; i++) { - AsyncWorkerThread *toAdd = new AsyncWorkerThread(this, - std::string("AsyncWorker-") + itos(i)); - workerThreads.push_back(toAdd); - toAdd->start(); + if (numEngines == 0) { + // Leave one core for the main thread and one for whatever else + autoscaleMaxWorkers = Thread::getNumberOfProcessors(); + if (autoscaleMaxWorkers >= 2) + autoscaleMaxWorkers -= 2; + infostream << "AsyncEngine: using at most " << autoscaleMaxWorkers + << " threads with automatic scaling" << std::endl; + + addWorkerThread(); + } else { + for (unsigned int i = 0; i < numEngines; i++) + addWorkerThread(); } } +void AsyncEngine::addWorkerThread() +{ + AsyncWorkerThread *toAdd = new AsyncWorkerThread(this, + std::string("AsyncWorker-") + itos(workerThreads.size())); + workerThreads.push_back(toAdd); + toAdd->start(); +} + /******************************************************************************/ u32 AsyncEngine::queueAsyncJob(std::string &&func, std::string &¶ms, const std::string &mod_origin) { - jobQueueMutex.lock(); + MutexAutoLock autolock(jobQueueMutex); u32 jobId = jobIdCounter++; jobQueue.emplace_back(); @@ -99,7 +115,23 @@ u32 AsyncEngine::queueAsyncJob(std::string &&func, std::string &¶ms, to_add.mod_origin = mod_origin; jobQueueCounter.post(); - jobQueueMutex.unlock(); + return jobId; +} + +u32 AsyncEngine::queueAsyncJob(std::string &&func, PackedValue *params, + const std::string &mod_origin) +{ + MutexAutoLock autolock(jobQueueMutex); + u32 jobId = jobIdCounter++; + + jobQueue.emplace_back(); + auto &to_add = jobQueue.back(); + to_add.id = jobId; + to_add.function = std::move(func); + to_add.params_ext.reset(params); + to_add.mod_origin = mod_origin; + + jobQueueCounter.post(); return jobId; } @@ -132,6 +164,12 @@ void AsyncEngine::putJobResult(LuaJobInfo &&result) /******************************************************************************/ void AsyncEngine::step(lua_State *L) { + stepJobResults(L); + stepAutoscale(); +} + +void AsyncEngine::stepJobResults(lua_State *L) +{ int error_handler = PUSH_ERROR_HANDLER(L); lua_getglobal(L, "core"); @@ -148,7 +186,10 @@ void AsyncEngine::step(lua_State *L) luaL_checktype(L, -1, LUA_TFUNCTION); lua_pushinteger(L, j.id); - lua_pushlstring(L, j.result.data(), j.result.size()); + if (j.result_ext) + script_unpack(L, j.result_ext.get()); + else + lua_pushlstring(L, j.result.data(), j.result.size()); // Call handler const char *origin = j.mod_origin.empty() ? nullptr : j.mod_origin.c_str(); @@ -161,12 +202,71 @@ void AsyncEngine::step(lua_State *L) lua_pop(L, 2); // Pop core and error handler } +void AsyncEngine::stepAutoscale() +{ + if (workerThreads.size() >= autoscaleMaxWorkers) + return; + + MutexAutoLock autolock(jobQueueMutex); + + // 2) If the timer elapsed, check again + if (autoscaleTimer && porting::getTimeMs() >= autoscaleTimer) { + autoscaleTimer = 0; + // Determine overlap with previous snapshot + unsigned int n = 0; + for (const auto &it : jobQueue) + n += autoscaleSeenJobs.count(it.id); + autoscaleSeenJobs.clear(); + infostream << "AsyncEngine: " << n << " jobs were still waiting after 1s" << std::endl; + // Start this many new threads + while (workerThreads.size() < autoscaleMaxWorkers && n > 0) { + addWorkerThread(); + n--; + } + return; + } + + // 1) Check if there's anything in the queue + if (!autoscaleTimer && !jobQueue.empty()) { + // Take a snapshot of all jobs we have seen + for (const auto &it : jobQueue) + autoscaleSeenJobs.emplace(it.id); + // and set a timer for 1 second + autoscaleTimer = porting::getTimeMs() + 1000; + } +} + /******************************************************************************/ -void AsyncEngine::prepareEnvironment(lua_State* L, int top) +bool AsyncEngine::prepareEnvironment(lua_State* L, int top) { for (StateInitializer &stateInitializer : stateInitializers) { stateInitializer(L, top); } + + auto *script = ModApiBase::getScriptApiBase(L); + try { + script->loadMod(Server::getBuiltinLuaPath() + DIR_DELIM + "init.lua", + BUILTIN_MOD_NAME); + } catch (const ModError &e) { + errorstream << "Execution of async base environment failed: " + << e.what() << std::endl; + FATAL_ERROR("Execution of async base environment failed"); + } + + // Load per mod stuff + if (server) { + const auto &list = server->m_async_init_files; + try { + for (auto &it : list) + script->loadMod(it.second, it.first); + } catch (const ModError &e) { + errorstream << "Failed to load mod script inside async environment." << std::endl; + server->setAsyncFatalError(e.what()); + return false; + } + } + + return true; } /******************************************************************************/ @@ -178,15 +278,25 @@ AsyncWorkerThread::AsyncWorkerThread(AsyncEngine* jobDispatcher, { lua_State *L = getStack(); + if (jobDispatcher->server) { + setGameDef(jobDispatcher->server); + + if (g_settings->getBool("secure.enable_security")) + initializeSecurity(); + } + // Prepare job lua environment lua_getglobal(L, "core"); int top = lua_gettop(L); // Push builtin initialization type - lua_pushstring(L, "async"); + lua_pushstring(L, jobDispatcher->server ? "async_game" : "async"); lua_setglobal(L, "INIT"); - jobDispatcher->prepareEnvironment(L, top); + if (!jobDispatcher->prepareEnvironment(L, top)) { + // can't throw from here so we're stuck with this + isErrored = true; + } } /******************************************************************************/ @@ -198,19 +308,20 @@ AsyncWorkerThread::~AsyncWorkerThread() /******************************************************************************/ void* AsyncWorkerThread::run() { - lua_State *L = getStack(); + if (isErrored) + return nullptr; - try { - loadMod(getServer()->getBuiltinLuaPath() + DIR_DELIM + "init.lua", - BUILTIN_MOD_NAME); - } catch (const ModError &e) { - errorstream << "Execution of async base environment failed: " - << e.what() << std::endl; - FATAL_ERROR("Execution of async base environment failed"); - } + lua_State *L = getStack(); int error_handler = PUSH_ERROR_HANDLER(L); + auto report_error = [this] (const ModError &e) { + if (jobDispatcher->server) + jobDispatcher->server->setAsyncFatalError(e.what()); + else + errorstream << e.what() << std::endl; + }; + lua_getglobal(L, "core"); if (lua_isnil(L, -1)) { FATAL_ERROR("Unable to find core within async environment!"); @@ -223,6 +334,8 @@ void* AsyncWorkerThread::run() if (!jobDispatcher->getJob(&j) || stopRequested()) continue; + const bool use_ext = !!j.params_ext; + lua_getfield(L, -1, "job_processor"); if (lua_isnil(L, -1)) FATAL_ERROR("Unable to get async job processor!"); @@ -232,7 +345,10 @@ void* AsyncWorkerThread::run() errorstream << "ASYNC WORKER: Unable to deserialize function" << std::endl; lua_pushnil(L); } - lua_pushlstring(L, j.params.data(), j.params.size()); + if (use_ext) + script_unpack(L, j.params_ext.get()); + else + lua_pushlstring(L, j.params.data(), j.params.size()); // Call it setOriginDirect(j.mod_origin.empty() ? nullptr : j.mod_origin.c_str()); @@ -241,19 +357,28 @@ void* AsyncWorkerThread::run() try { scriptError(result, "<async>"); } catch (const ModError &e) { - errorstream << e.what() << std::endl; + report_error(e); } } else { // Fetch result - size_t length; - const char *retval = lua_tolstring(L, -1, &length); - j.result.assign(retval, length); + if (use_ext) { + try { + j.result_ext.reset(script_pack(L, -1)); + } catch (const ModError &e) { + report_error(e); + result = LUA_ERRERR; + } + } else { + size_t length; + const char *retval = lua_tolstring(L, -1, &length); + j.result.assign(retval, length); + } } lua_pop(L, 1); // Pop retval // Put job result - if (!j.result.empty()) + if (result == 0) jobDispatcher->putJobResult(std::move(j)); } diff --git a/src/script/cpp_api/s_async.h b/src/script/cpp_api/s_async.h index 697cb0221..1e34e40ea 100644 --- a/src/script/cpp_api/s_async.h +++ b/src/script/cpp_api/s_async.h @@ -21,11 +21,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <vector> #include <deque> +#include <unordered_set> +#include <memory> +#include <lua.h> #include "threading/semaphore.h" #include "threading/thread.h" -#include "lua.h" +#include "common/c_packer.h" #include "cpp_api/s_base.h" +#include "cpp_api/s_security.h" // Forward declarations class AsyncEngine; @@ -42,8 +46,12 @@ struct LuaJobInfo std::string function; // Parameter to be passed to function (serialized) std::string params; + // Alternative parameters + std::unique_ptr<PackedValue> params_ext; // Result of function call (serialized) std::string result; + // Alternative result + std::unique_ptr<PackedValue> result_ext; // Name of the mod who invoked this call std::string mod_origin; // JobID used to identify a job and match it to callback @@ -51,7 +59,8 @@ struct LuaJobInfo }; // Asynchronous working environment -class AsyncWorkerThread : public Thread, virtual public ScriptApiBase { +class AsyncWorkerThread : public Thread, + virtual public ScriptApiBase, public ScriptApiSecurity { friend class AsyncEngine; public: virtual ~AsyncWorkerThread(); @@ -63,6 +72,7 @@ protected: private: AsyncEngine *jobDispatcher = nullptr; + bool isErrored = false; }; // Asynchornous thread and job management @@ -71,6 +81,7 @@ class AsyncEngine { typedef void (*StateInitializer)(lua_State *L, int top); public: AsyncEngine() = default; + AsyncEngine(Server *server) : server(server) {}; ~AsyncEngine(); /** @@ -81,7 +92,7 @@ public: /** * Create async engine tasks and lock function registration - * @param numEngines Number of async threads to be started + * @param numEngines Number of worker threads, 0 for automatic scaling */ void initialize(unsigned int numEngines); @@ -95,8 +106,16 @@ public: const std::string &mod_origin = ""); /** + * Queue an async job + * @param func Serialized lua function + * @param params Serialized parameters (takes ownership!) + * @return ID of queued job + */ + u32 queueAsyncJob(std::string &&func, PackedValue *params, + const std::string &mod_origin = ""); + + /** * Engine step to process finished jobs - * the engine step is one way to pass events back, PushFinishedJobs another * @param L The Lua stack */ void step(lua_State *L); @@ -117,18 +136,43 @@ protected: void putJobResult(LuaJobInfo &&result); /** + * Start an additional worker thread + */ + void addWorkerThread(); + + /** + * Process finished jobs callbacks + */ + void stepJobResults(lua_State *L); + + /** + * Handle automatic scaling of worker threads + */ + void stepAutoscale(); + + /** * Initialize environment with current registred functions * this function adds all functions registred by registerFunction to the * passed lua stack * @param L Lua stack to initialize * @param top Stack position + * @return false if a mod error ocurred */ - void prepareEnvironment(lua_State* L, int top); + bool prepareEnvironment(lua_State* L, int top); private: // Variable locking the engine against further modification bool initDone = false; + // Maximum number of worker threads for automatic scaling + // 0 if disabled + unsigned int autoscaleMaxWorkers = 0; + u64 autoscaleTimer = 0; + std::unordered_set<u32> autoscaleSeenJobs; + + // Only set for the server async environment (duh) + Server *server = nullptr; + // Internal store for registred state initializers std::vector<StateInitializer> stateInitializers; diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index f7b8a5102..595c9e540 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -121,6 +121,14 @@ ScriptApiBase::ScriptApiBase(ScriptingType type): lua_newtable(m_luastack); lua_setglobal(m_luastack, "core"); + // vector.metatable is stored in the registry for quick access from C++. + lua_newtable(m_luastack); + lua_rawseti(m_luastack, LUA_REGISTRYINDEX, CUSTOM_RIDX_VECTOR_METATABLE); + lua_newtable(m_luastack); + lua_rawgeti(m_luastack, LUA_REGISTRYINDEX, CUSTOM_RIDX_VECTOR_METATABLE); + lua_setfield(m_luastack, -2, "metatable"); + lua_setglobal(m_luastack, "vector"); + if (m_type == ScriptingType::Client) lua_pushstring(m_luastack, "/"); else diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index c889fffa0..b02a0c7be 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -281,15 +281,7 @@ bool ScriptApiClient::on_inventory_open(Inventory *inventory) lua_getglobal(L, "core"); lua_getfield(L, -1, "registered_on_inventory_open"); - std::vector<const InventoryList*> lists = inventory->getLists(); - std::vector<const InventoryList*>::iterator iter = lists.begin(); - lua_createtable(L, 0, lists.size()); - for (; iter != lists.end(); iter++) { - const char* name = (*iter)->getName().c_str(); - lua_pushstring(L, name); - push_inventory_list(L, inventory, name); - lua_rawset(L, -3); - } + push_inventory_lists(L, *inventory); try { runCallbacks(1, RUN_CALLBACKS_MODE_OR); diff --git a/src/script/cpp_api/s_entity.cpp b/src/script/cpp_api/s_entity.cpp index 06337b9e8..852a27ddc 100644 --- a/src/script/cpp_api/s_entity.cpp +++ b/src/script/cpp_api/s_entity.cpp @@ -103,7 +103,7 @@ void ScriptApiEntity::luaentity_Activate(u16 id, lua_pop(L, 2); // Pop object and error handler } -void ScriptApiEntity::luaentity_Deactivate(u16 id) +void ScriptApiEntity::luaentity_Deactivate(u16 id, bool removal) { SCRIPTAPI_PRECHECKHEADER @@ -120,9 +120,9 @@ void ScriptApiEntity::luaentity_Deactivate(u16 id) if (!lua_isnil(L, -1)) { luaL_checktype(L, -1, LUA_TFUNCTION); lua_pushvalue(L, object); - + lua_pushboolean(L, removal); setOriginFromTable(object); - PCALL_RES(lua_pcall(L, 1, 0, error_handler)); + PCALL_RES(lua_pcall(L, 2, 0, error_handler)); } else { lua_pop(L, 1); } @@ -244,7 +244,7 @@ bool ScriptApiEntity::luaentity_Punch(u16 id, { SCRIPTAPI_PRECHECKHEADER - //infostream<<"scriptapi_luaentity_step: id="<<id<<std::endl; + assert(puncher); int error_handler = PUSH_ERROR_HANDLER(L); @@ -294,7 +294,10 @@ bool ScriptApiEntity::luaentity_run_simple_callback(u16 id, } luaL_checktype(L, -1, LUA_TFUNCTION); lua_pushvalue(L, object); // self - objectrefGetOrCreate(L, sao); // killer reference + if (sao) + objectrefGetOrCreate(L, sao); // sao reference + else + lua_pushnil(L); setOriginFromTable(object); PCALL_RES(lua_pcall(L, 2, 1, error_handler)); diff --git a/src/script/cpp_api/s_entity.h b/src/script/cpp_api/s_entity.h index 7658ae922..13f3e9aa3 100644 --- a/src/script/cpp_api/s_entity.h +++ b/src/script/cpp_api/s_entity.h @@ -33,7 +33,7 @@ public: bool luaentity_Add(u16 id, const char *name); void luaentity_Activate(u16 id, const std::string &staticdata, u32 dtime_s); - void luaentity_Deactivate(u16 id); + void luaentity_Deactivate(u16 id, bool removal); void luaentity_Remove(u16 id); std::string luaentity_GetStaticdata(u16 id); void luaentity_GetProperties(u16 id, diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index 874c37b6e..af68f689f 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -140,10 +140,10 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) bool simple_catch_up = true; getboolfield(L, current_abm, "catch_up", simple_catch_up); - + s16 min_y = INT16_MIN; getintfield(L, current_abm, "min_y", min_y); - + s16 max_y = INT16_MAX; getintfield(L, current_abm, "max_y", max_y); diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index a6c5114b2..316b19926 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -98,6 +98,7 @@ void ScriptApiSecurity::initializeSecurity() "type", "unpack", "_VERSION", + "vector", "xpcall", }; static const char *whitelist_tables[] = { @@ -127,8 +128,6 @@ void ScriptApiSecurity::initializeSecurity() "gethook", "traceback", "getinfo", - "getmetatable", - "setmetatable", "upvalueid", "sethook", "debug", @@ -254,6 +253,10 @@ void ScriptApiSecurity::initializeSecurity() lua_pushnil(L); lua_setfield(L, old_globals, "core"); + // 'vector' as well. + lua_pushnil(L); + lua_setfield(L, old_globals, "vector"); + lua_pop(L, 1); // Pop globals_backup @@ -296,6 +299,7 @@ void ScriptApiSecurity::initializeSecurityClient() "type", "unpack", "_VERSION", + "vector", "xpcall", // Completely safe libraries "coroutine", @@ -413,6 +417,12 @@ void ScriptApiSecurity::setLuaEnv(lua_State *L, int thread) bool ScriptApiSecurity::isSecure(lua_State *L) { +#ifndef SERVER + auto script = ModApiBase::getScriptApiBase(L); + // CSM keeps no globals backup but is always secure + if (script->getType() == ScriptingType::Client) + return true; +#endif lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP); bool secure = !lua_isnil(L, -1); lua_pop(L, 1); diff --git a/src/script/lua_api/l_areastore.cpp b/src/script/lua_api/l_areastore.cpp index 45724e604..ec2656c4a 100644 --- a/src/script/lua_api/l_areastore.cpp +++ b/src/script/lua_api/l_areastore.cpp @@ -27,26 +27,26 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include <fstream> -static inline void get_data_and_border_flags(lua_State *L, u8 start_i, - bool *borders, bool *data) +static inline void get_data_and_corner_flags(lua_State *L, u8 start_i, + bool *corners, bool *data) { if (!lua_isboolean(L, start_i)) return; - *borders = lua_toboolean(L, start_i); + *corners = lua_toboolean(L, start_i); if (!lua_isboolean(L, start_i + 1)) return; *data = lua_toboolean(L, start_i + 1); } static void push_area(lua_State *L, const Area *a, - bool include_borders, bool include_data) + bool include_corners, bool include_data) { - if (!include_borders && !include_data) { + if (!include_corners && !include_data) { lua_pushboolean(L, true); return; } lua_newtable(L); - if (include_borders) { + if (include_corners) { push_v3s16(L, a->minedge); lua_setfield(L, -2, "min"); push_v3s16(L, a->maxedge); @@ -59,13 +59,13 @@ static void push_area(lua_State *L, const Area *a, } static inline void push_areas(lua_State *L, const std::vector<Area *> &areas, - bool borders, bool data) + bool corners, bool data) { lua_newtable(L); size_t cnt = areas.size(); for (size_t i = 0; i < cnt; i++) { lua_pushnumber(L, areas[i]->id); - push_area(L, areas[i], borders, data); + push_area(L, areas[i], corners, data); lua_settable(L, -3); } } @@ -94,7 +94,7 @@ int LuaAreaStore::gc_object(lua_State *L) return 0; } -// get_area(id, include_borders, include_data) +// get_area(id, include_corners, include_data) int LuaAreaStore::l_get_area(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -104,9 +104,9 @@ int LuaAreaStore::l_get_area(lua_State *L) u32 id = luaL_checknumber(L, 2); - bool include_borders = true; + bool include_corners = true; bool include_data = false; - get_data_and_border_flags(L, 3, &include_borders, &include_data); + get_data_and_corner_flags(L, 3, &include_corners, &include_data); const Area *res; @@ -114,12 +114,12 @@ int LuaAreaStore::l_get_area(lua_State *L) if (!res) return 0; - push_area(L, res, include_borders, include_data); + push_area(L, res, include_corners, include_data); return 1; } -// get_areas_for_pos(pos, include_borders, include_data) +// get_areas_for_pos(pos, include_corners, include_data) int LuaAreaStore::l_get_areas_for_pos(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -129,19 +129,19 @@ int LuaAreaStore::l_get_areas_for_pos(lua_State *L) v3s16 pos = check_v3s16(L, 2); - bool include_borders = true; + bool include_corners = true; bool include_data = false; - get_data_and_border_flags(L, 3, &include_borders, &include_data); + get_data_and_corner_flags(L, 3, &include_corners, &include_data); std::vector<Area *> res; ast->getAreasForPos(&res, pos); - push_areas(L, res, include_borders, include_data); + push_areas(L, res, include_corners, include_data); return 1; } -// get_areas_in_area(edge1, edge2, accept_overlap, include_borders, include_data) +// get_areas_in_area(corner1, corner2, accept_overlap, include_corners, include_data) int LuaAreaStore::l_get_areas_in_area(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -149,25 +149,26 @@ int LuaAreaStore::l_get_areas_in_area(lua_State *L) LuaAreaStore *o = checkobject(L, 1); AreaStore *ast = o->as; - v3s16 minedge = check_v3s16(L, 2); - v3s16 maxedge = check_v3s16(L, 3); + v3s16 minp = check_v3s16(L, 2); + v3s16 maxp = check_v3s16(L, 3); + sortBoxVerticies(minp, maxp); - bool include_borders = true; + bool include_corners = true; bool include_data = false; bool accept_overlap = false; if (lua_isboolean(L, 4)) { accept_overlap = readParam<bool>(L, 4); - get_data_and_border_flags(L, 5, &include_borders, &include_data); + get_data_and_corner_flags(L, 5, &include_corners, &include_data); } std::vector<Area *> res; - ast->getAreasInArea(&res, minedge, maxedge, accept_overlap); - push_areas(L, res, include_borders, include_data); + ast->getAreasInArea(&res, minp, maxp, accept_overlap); + push_areas(L, res, include_corners, include_data); return 1; } -// insert_area(edge1, edge2, data, id) +// insert_area(corner1, corner2, data, id) int LuaAreaStore::l_insert_area(lua_State *L) { NO_MAP_LOCK_REQUIRED; diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index aaced7cd0..05ac53cbb 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -268,30 +268,32 @@ int ModApiClient::l_sound_play(lua_State *L) SimpleSoundSpec spec; read_soundspec(L, 1, spec); + SoundLocation type = SoundLocation::Local; float gain = 1.0f; - float pitch = 1.0f; - bool looped = false; - s32 handle; + v3f position; if (lua_istable(L, 2)) { getfloatfield(L, 2, "gain", gain); - getfloatfield(L, 2, "pitch", pitch); - getboolfield(L, 2, "loop", looped); + getfloatfield(L, 2, "pitch", spec.pitch); + getboolfield(L, 2, "loop", spec.loop); lua_getfield(L, 2, "pos"); if (!lua_isnil(L, -1)) { - v3f pos = read_v3f(L, -1) * BS; + position = read_v3f(L, -1) * BS; + type = SoundLocation::Position; lua_pop(L, 1); - handle = sound->playSoundAt( - spec.name, looped, gain * spec.gain, pos, pitch); - lua_pushinteger(L, handle); - return 1; } } - handle = sound->playSound(spec.name, looped, gain * spec.gain, spec.fade, pitch); - lua_pushinteger(L, handle); + spec.gain *= gain; + s32 handle; + if (type == SoundLocation::Local) + handle = sound->playSound(spec); + else + handle = sound->playSoundAt(spec, position); + + lua_pushinteger(L, handle); return 1; } diff --git a/src/script/lua_api/l_craft.cpp b/src/script/lua_api/l_craft.cpp index 18622ee00..137b210be 100644 --- a/src/script/lua_api/l_craft.cpp +++ b/src/script/lua_api/l_craft.cpp @@ -371,8 +371,9 @@ int ModApiCraft::l_clear_craft(lua_State *L) int ModApiCraft::l_get_craft_result(lua_State *L) { NO_MAP_LOCK_REQUIRED; + IGameDef *gdef = getGameDef(L); - int input_i = 1; + const int input_i = 1; std::string method_s = getstringfield_default(L, input_i, "method", "normal"); enum CraftMethod method = (CraftMethod)getenumfield(L, input_i, "method", es_CraftMethod, CRAFT_METHOD_NORMAL); @@ -382,10 +383,9 @@ int ModApiCraft::l_get_craft_result(lua_State *L) width = luaL_checkinteger(L, -1); lua_pop(L, 1); lua_getfield(L, input_i, "items"); - std::vector<ItemStack> items = read_items(L, -1,getServer(L)); + std::vector<ItemStack> items = read_items(L, -1, gdef); lua_pop(L, 1); // items - IGameDef *gdef = getServer(L); ICraftDefManager *cdef = gdef->cdef(); CraftInput input(method, width, items); CraftOutput output; @@ -465,13 +465,13 @@ static void push_craft_recipes(lua_State *L, IGameDef *gdef, const std::vector<CraftDefinition*> &recipes, const CraftOutput &output) { - lua_createtable(L, recipes.size(), 0); - if (recipes.empty()) { lua_pushnil(L); return; } + lua_createtable(L, recipes.size(), 0); + std::vector<CraftDefinition*>::const_iterator it = recipes.begin(); for (unsigned i = 0; it != recipes.end(); ++it) { lua_newtable(L); @@ -487,10 +487,9 @@ int ModApiCraft::l_get_craft_recipe(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string item = luaL_checkstring(L, 1); - Server *server = getServer(L); + IGameDef *gdef = getGameDef(L); CraftOutput output(item, 0); - std::vector<CraftDefinition*> recipes = server->cdef() - ->getCraftRecipes(output, server, 1); + auto recipes = gdef->cdef()->getCraftRecipes(output, gdef, 1); lua_createtable(L, 1, 0); @@ -500,7 +499,7 @@ int ModApiCraft::l_get_craft_recipe(lua_State *L) setintfield(L, -1, "width", 0); return 1; } - push_craft_recipe(L, server, recipes[0], output); + push_craft_recipe(L, gdef, recipes[0], output); return 1; } @@ -510,12 +509,11 @@ int ModApiCraft::l_get_all_craft_recipes(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string item = luaL_checkstring(L, 1); - Server *server = getServer(L); + IGameDef *gdef = getGameDef(L); CraftOutput output(item, 0); - std::vector<CraftDefinition*> recipes = server->cdef() - ->getCraftRecipes(output, server); + auto recipes = gdef->cdef()->getCraftRecipes(output, gdef); - push_craft_recipes(L, server, recipes, output); + push_craft_recipes(L, gdef, recipes, output); return 1; } @@ -527,3 +525,11 @@ void ModApiCraft::Initialize(lua_State *L, int top) API_FCT(register_craft); API_FCT(clear_craft); } + +void ModApiCraft::InitializeAsync(lua_State *L, int top) +{ + // all read-only functions + API_FCT(get_all_craft_recipes); + API_FCT(get_craft_recipe); + API_FCT(get_craft_result); +} diff --git a/src/script/lua_api/l_craft.h b/src/script/lua_api/l_craft.h index 9002b23ef..5234af56f 100644 --- a/src/script/lua_api/l_craft.h +++ b/src/script/lua_api/l_craft.h @@ -45,4 +45,5 @@ private: public: static void Initialize(lua_State *L, int top); + static void InitializeAsync(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 18ee3a521..b26c89e7d 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -640,7 +640,7 @@ int ModApiEnvMod::l_add_entity(lua_State *L) v3f pos = checkFloatPos(L, 1); const char *name = luaL_checkstring(L, 2); - const char *staticdata = luaL_optstring(L, 3, ""); + std::string staticdata = readParam<std::string>(L, 3, ""); ServerActiveObject *obj = new LuaEntitySAO(env, pos, name, staticdata); int objectid = env->addActiveObject(obj); @@ -757,7 +757,7 @@ int ModApiEnvMod::l_get_objects_in_area(lua_State *L) { GET_ENV_PTR; ScriptApiBase *script = getScriptApiBase(L); - + v3f minp = read_v3f(L, 1) * BS; v3f maxp = read_v3f(L, 2) * BS; aabb3f box(minp, maxp); @@ -1219,7 +1219,8 @@ int ModApiEnvMod::l_emerge_area(lua_State *L) sortBoxVerticies(bpmin, bpmax); size_t num_blocks = VoxelArea(bpmin, bpmax).getVolume(); - assert(num_blocks != 0); + if (num_blocks == 0) + return 0; if (lua_isfunction(L, 3)) { callback = LuaEmergeAreaCallback; @@ -1386,7 +1387,7 @@ int ModApiEnvMod::l_transforming_liquid_add(lua_State *L) GET_ENV_PTR; v3s16 p0 = read_v3s16(L, 1); - env->getMap().transforming_liquid_add(p0); + env->getServerMap().transforming_liquid_add(p0); return 1; } @@ -1409,7 +1410,7 @@ int ModApiEnvMod::l_compare_block_status(lua_State *L) v3s16 nodepos = check_v3s16(L, 1); std::string condition_s = luaL_checkstring(L, 2); auto status = env->getBlockStatus(getNodeBlockPos(nodepos)); - + int condition_i = -1; if (!string_to_enum(es_BlockStatusType, condition_i, condition_s)) return 0; // Unsupported diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 67c76faae..a7d406d2a 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -114,7 +114,7 @@ private: // get_objects_inside_radius(pos, radius) static int l_get_objects_inside_radius(lua_State *L); - + // get_objects_in_area(pos, minp, maxp) static int l_get_objects_in_area(lua_State *L); diff --git a/src/script/lua_api/l_internal.h b/src/script/lua_api/l_internal.h index 672e535ca..de73ff42a 100644 --- a/src/script/lua_api/l_internal.h +++ b/src/script/lua_api/l_internal.h @@ -69,7 +69,7 @@ with this program; if not, write to the Free Software Foundation, Inc., // Retrieve Environment pointer as `env` (no map lock) #define GET_PLAIN_ENV_PTR_NO_MAP_LOCK \ - Environment *env = (Environment *)getEnv(L); \ + Environment *env = getEnv(L); \ if (env == NULL) \ return 0 diff --git a/src/script/lua_api/l_inventory.cpp b/src/script/lua_api/l_inventory.cpp index b0a4ee194..175047e58 100644 --- a/src/script/lua_api/l_inventory.cpp +++ b/src/script/lua_api/l_inventory.cpp @@ -214,11 +214,16 @@ int InvRef::l_get_list(lua_State *L) InvRef *ref = checkobject(L, 1); const char *listname = luaL_checkstring(L, 2); Inventory *inv = getinv(L, ref); - if(inv){ - push_inventory_list(L, inv, listname); - } else { + if (!inv) { lua_pushnil(L); + return 1; } + InventoryList *invlist = inv->getList(listname); + if (!invlist) { + lua_pushnil(L); + return 1; + } + push_inventory_list(L, *invlist); return 1; } @@ -242,7 +247,7 @@ int InvRef::l_set_list(lua_State *L) return 0; } -// get_lists(self) -> list of InventoryLists +// get_lists(self) -> table that maps listnames to InventoryLists int InvRef::l_get_lists(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -251,15 +256,7 @@ int InvRef::l_get_lists(lua_State *L) if (!inv) { return 0; } - std::vector<const InventoryList*> lists = inv->getLists(); - std::vector<const InventoryList*>::iterator iter = lists.begin(); - lua_createtable(L, 0, lists.size()); - for (; iter != lists.end(); iter++) { - const char* name = (*iter)->getName().c_str(); - lua_pushstring(L, name); - push_inventory_list(L, inv, name); - lua_rawset(L, -3); - } + push_inventory_lists(L, *inv); return 1; } diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp index 794d8a6e5..13d046d00 100644 --- a/src/script/lua_api/l_item.cpp +++ b/src/script/lua_api/l_item.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "common/c_packer.h" #include "itemdef.h" #include "nodedef.h" #include "server.h" @@ -342,7 +343,7 @@ int LuaItemStack::l_get_tool_capabilities(lua_State *L) } // add_wear(self, amount) -> true/false -// The range for "amount" is [0,65535]. Wear is only added if the item +// The range for "amount" is [0,65536]. Wear is only added if the item // is a tool. Adding wear might destroy the item. // Returns true if the item is (or was) a tool. int LuaItemStack::l_add_wear(lua_State *L) @@ -356,6 +357,25 @@ int LuaItemStack::l_add_wear(lua_State *L) return 1; } +// add_wear_by_uses(self, max_uses) -> true/false +// The range for "max_uses" is [0,65536]. +// Adds wear to the item in such a way that, if +// only this function is called to add wear, the item +// will be destroyed exactly after `max_uses` times of calling it. +// No-op if `max_uses` is 0 or item is not a tool. +// Returns true if the item is (or was) a tool. +int LuaItemStack::l_add_wear_by_uses(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + u32 max_uses = readParam<int>(L, 2); + u32 add_wear = calculateResultWear(max_uses, item.wear); + bool result = item.addWear(add_wear, getGameDef(L)->idef()); + lua_pushboolean(L, result); + return 1; +} + // add_item(self, itemstack or itemstring or table or nil) -> itemstack // Returns leftover item stack int LuaItemStack::l_add_item(lua_State *L) @@ -441,6 +461,7 @@ int LuaItemStack::create_object(lua_State *L) lua_setmetatable(L, -2); return 1; } + // Not callable from Lua int LuaItemStack::create(lua_State *L, const ItemStack &item) { @@ -457,6 +478,20 @@ LuaItemStack *LuaItemStack::checkobject(lua_State *L, int narg) return *(LuaItemStack **)luaL_checkudata(L, narg, className); } +void *LuaItemStack::packIn(lua_State *L, int idx) +{ + LuaItemStack *o = checkobject(L, idx); + return new ItemStack(o->getItem()); +} + +void LuaItemStack::packOut(lua_State *L, void *ptr) +{ + ItemStack *stack = reinterpret_cast<ItemStack*>(ptr); + if (L) + create(L, *stack); + delete stack; +} + void LuaItemStack::Register(lua_State *L) { lua_newtable(L); @@ -488,6 +523,8 @@ void LuaItemStack::Register(lua_State *L) // Can be created from Lua (ItemStack(itemstack or itemstring or table or nil)) lua_register(L, className, create_object); + + script_register_packer(L, className, packIn, packOut); } const char LuaItemStack::className[] = "ItemStack"; @@ -514,6 +551,7 @@ const luaL_Reg LuaItemStack::methods[] = { luamethod(LuaItemStack, get_definition), luamethod(LuaItemStack, get_tool_capabilities), luamethod(LuaItemStack, add_wear), + luamethod(LuaItemStack, add_wear_by_uses), luamethod(LuaItemStack, add_item), luamethod(LuaItemStack, item_fits), luamethod(LuaItemStack, take_item), @@ -576,6 +614,9 @@ int ModApiItemMod::l_register_item_raw(lua_State *L) // be done if (f.name == "ignore") return 0; + // This would break everything + if (f.name.empty()) + throw LuaError("Cannot register node with empty name"); content_t id = ndef->set(f.name, f); @@ -632,8 +673,8 @@ int ModApiItemMod::l_get_content_id(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string name = luaL_checkstring(L, 1); - const IItemDefManager *idef = getGameDef(L)->getItemDefManager(); - const NodeDefManager *ndef = getGameDef(L)->getNodeDefManager(); + const IItemDefManager *idef = getGameDef(L)->idef(); + const NodeDefManager *ndef = getGameDef(L)->ndef(); // If this is called at mod load time, NodeDefManager isn't aware of // aliases yet, so we need to handle them manually @@ -658,7 +699,7 @@ int ModApiItemMod::l_get_name_from_content_id(lua_State *L) NO_MAP_LOCK_REQUIRED; content_t c = luaL_checkint(L, 1); - const NodeDefManager *ndef = getGameDef(L)->getNodeDefManager(); + const NodeDefManager *ndef = getGameDef(L)->ndef(); const char *name = ndef->get(c).name.c_str(); lua_pushstring(L, name); @@ -673,3 +714,10 @@ void ModApiItemMod::Initialize(lua_State *L, int top) API_FCT(get_content_id); API_FCT(get_name_from_content_id); } + +void ModApiItemMod::InitializeAsync(lua_State *L, int top) +{ + // all read-only functions + API_FCT(get_content_id); + API_FCT(get_name_from_content_id); +} diff --git a/src/script/lua_api/l_item.h b/src/script/lua_api/l_item.h index 16878c101..a392555d2 100644 --- a/src/script/lua_api/l_item.h +++ b/src/script/lua_api/l_item.h @@ -108,11 +108,20 @@ private: static int l_get_tool_capabilities(lua_State *L); // add_wear(self, amount) -> true/false - // The range for "amount" is [0,65535]. Wear is only added if the item + // The range for "amount" is [0,65536]. Wear is only added if the item // is a tool. Adding wear might destroy the item. // Returns true if the item is (or was) a tool. static int l_add_wear(lua_State *L); + // add_wear_by_uses(self, max_uses) -> true/false + // The range for "max_uses" is [0,65536]. + // Adds wear to the item in such a way that, if + // only this function is called to add wear, the item + // will be destroyed exactly after `max_uses` times of calling it. + // No-op if `max_uses` is 0 or item is not a tool. + // Returns true if the item is (or was) a tool. + static int l_add_wear_by_uses(lua_State *L); + // add_item(self, itemstack or itemstring or table or nil) -> itemstack // Returns leftover item stack static int l_add_item(lua_State *L); @@ -141,8 +150,11 @@ public: // Not callable from Lua static int create(lua_State *L, const ItemStack &item); static LuaItemStack* checkobject(lua_State *L, int narg); - static void Register(lua_State *L); + static void *packIn(lua_State *L, int idx); + static void packOut(lua_State *L, void *ptr); + + static void Register(lua_State *L); }; class ModApiItemMod : public ModApiBase { @@ -152,6 +164,8 @@ private: static int l_register_alias_raw(lua_State *L); static int l_get_content_id(lua_State *L); static int l_get_name_from_content_id(lua_State *L); + public: static void Initialize(lua_State *L, int top); + static void InitializeAsync(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 736ad022f..cf4a057e1 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -38,7 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/client.h" #include "client/renderingengine.h" #include "network/networkprotocol.h" - +#include "content/mod_configuration.h" /******************************************************************************/ std::string ModApiMainMenu::getTextData(lua_State *L, std::string name) @@ -139,6 +139,14 @@ int ModApiMainMenu::l_start(lua_State *L) data->password = getTextData(L,"password"); data->address = getTextData(L,"address"); data->port = getTextData(L,"port"); + + const auto val = getTextData(L, "allow_login_or_register"); + if (val == "login") + data->allow_login_or_register = ELoginRegister::Login; + else if (val == "register") + data->allow_login_or_register = ELoginRegister::Register; + else + data->allow_login_or_register = ELoginRegister::Any; } data->serverdescription = getTextData(L,"serverdescription"); data->servername = getTextData(L,"servername"); @@ -304,7 +312,11 @@ int ModApiMainMenu::l_get_games(lua_State *L) lua_settable(L, top_lvl2); lua_pushstring(L, "name"); - lua_pushstring(L, game.name.c_str()); + lua_pushstring(L, game.title.c_str()); + lua_settable(L, top_lvl2); + + lua_pushstring(L, "title"); + lua_pushstring(L, game.title.c_str()); lua_settable(L, top_lvl2); lua_pushstring(L, "author"); @@ -323,9 +335,9 @@ int ModApiMainMenu::l_get_games(lua_State *L) lua_newtable(L); int table2 = lua_gettop(L); int internal_index = 1; - for (const std::string &addon_mods_path : game.addon_mods_paths) { + for (const auto &addon_mods_path : game.addon_mods_paths) { lua_pushnumber(L, internal_index); - lua_pushstring(L, addon_mods_path.c_str()); + lua_pushstring(L, addon_mods_path.second.c_str()); lua_settable(L, table2); internal_index++; } @@ -356,6 +368,11 @@ int ModApiMainMenu::l_get_content_info(lua_State *L) lua_pushstring(L, spec.author.c_str()); lua_setfield(L, -2, "author"); + if (!spec.title.empty()) { + lua_pushstring(L, spec.title.c_str()); + lua_setfield(L, -2, "title"); + } + lua_pushinteger(L, spec.release); lua_setfield(L, -2, "release"); @@ -393,6 +410,100 @@ int ModApiMainMenu::l_get_content_info(lua_State *L) } /******************************************************************************/ +int ModApiMainMenu::l_check_mod_configuration(lua_State *L) +{ + std::string worldpath = luaL_checkstring(L, 1); + + ModConfiguration modmgr; + + // Add all game mods + SubgameSpec gamespec = findWorldSubgame(worldpath); + modmgr.addGameMods(gamespec); + modmgr.addModsInPath(worldpath + DIR_DELIM + "worldmods", "worldmods"); + + // Add user-configured mods + std::vector<ModSpec> modSpecs; + + luaL_checktype(L, 2, LUA_TTABLE); + + lua_pushnil(L); + while (lua_next(L, 2)) { + // Ignore non-string keys + if (lua_type(L, -2) != LUA_TSTRING) { + throw LuaError( + "Unexpected non-string key in table passed to " + "core.check_mod_configuration"); + } + + std::string modpath = luaL_checkstring(L, -1); + lua_pop(L, 1); + std::string virtual_path = lua_tostring(L, -1); + + modSpecs.emplace_back(); + ModSpec &spec = modSpecs.back(); + spec.name = fs::GetFilenameFromPath(modpath.c_str()); + spec.path = modpath; + spec.virtual_path = virtual_path; + if (!parseModContents(spec)) { + throw LuaError("Not a mod!"); + } + } + + modmgr.addMods(modSpecs); + try { + modmgr.checkConflictsAndDeps(); + } catch (const ModError &err) { + errorstream << err.what() << std::endl; + + lua_newtable(L); + + lua_pushboolean(L, false); + lua_setfield(L, -2, "is_consistent"); + + lua_newtable(L); + lua_setfield(L, -2, "unsatisfied_mods"); + + lua_newtable(L); + lua_setfield(L, -2, "satisfied_mods"); + + lua_pushstring(L, err.what()); + lua_setfield(L, -2, "error_message"); + return 1; + } + + + lua_newtable(L); + + lua_pushboolean(L, modmgr.isConsistent()); + lua_setfield(L, -2, "is_consistent"); + + lua_newtable(L); + int top = lua_gettop(L); + unsigned int index = 1; + for (const auto &spec : modmgr.getUnsatisfiedMods()) { + lua_pushnumber(L, index); + push_mod_spec(L, spec, true); + lua_settable(L, top); + index++; + } + + lua_setfield(L, -2, "unsatisfied_mods"); + + lua_newtable(L); + top = lua_gettop(L); + index = 1; + for (const auto &spec : modmgr.getMods()) { + lua_pushnumber(L, index); + push_mod_spec(L, spec, false); + lua_settable(L, top); + index++; + } + lua_setfield(L, -2, "satisfied_mods"); + + return 1; +} + +/******************************************************************************/ int ModApiMainMenu::l_show_keys_menu(lua_State *L) { GUIEngine* engine = getGuiEngine(L); @@ -533,14 +644,14 @@ int ModApiMainMenu::l_get_modpath(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_get_modpaths(lua_State *L) { - int index = 1; lua_newtable(L); + ModApiMainMenu::l_get_modpath(L); - lua_rawseti(L, -2, index); + lua_setfield(L, -2, "mods"); + for (const std::string &component : getEnvModPaths()) { - index++; lua_pushstring(L, component.c_str()); - lua_rawseti(L, -2, index); + lua_setfield(L, -2, fs::AbsolutePath(component).c_str()); } return 1; } @@ -860,6 +971,19 @@ int ModApiMainMenu::l_open_dir(lua_State *L) } /******************************************************************************/ +int ModApiMainMenu::l_share_file(lua_State *L) +{ +#ifdef __ANDROID__ + std::string path = luaL_checkstring(L, 1); + porting::shareFileAndroid(path); + lua_pushboolean(L, true); +#else + lua_pushboolean(L, false); +#endif + return 1; +} + +/******************************************************************************/ int ModApiMainMenu::l_do_async_callback(lua_State *L) { MainMenuScripting *script = getScriptApi<MainMenuScripting>(L); @@ -891,6 +1015,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(get_worlds); API_FCT(get_games); API_FCT(get_content_info); + API_FCT(check_mod_configuration); API_FCT(start); API_FCT(close); API_FCT(show_keys_menu); @@ -924,6 +1049,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(get_max_supp_proto); API_FCT(open_url); API_FCT(open_dir); + API_FCT(share_file); API_FCT(do_async_callback); } diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index 781185425..9dc40c7f4 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -82,6 +82,8 @@ private: static int l_get_content_info(lua_State *L); + static int l_check_mod_configuration(lua_State *L); + //gui static int l_show_keys_menu(lua_State *L); @@ -152,6 +154,8 @@ private: static int l_open_dir(lua_State *L); + static int l_share_file(lua_State *L); + // async static int l_do_async_callback(lua_State *L); diff --git a/src/script/lua_api/l_nodemeta.cpp b/src/script/lua_api/l_nodemeta.cpp index 34760157d..bdc4844c0 100644 --- a/src/script/lua_api/l_nodemeta.cpp +++ b/src/script/lua_api/l_nodemeta.cpp @@ -40,7 +40,7 @@ NodeMetaRef* NodeMetaRef::checkobject(lua_State *L, int narg) Metadata* NodeMetaRef::getmeta(bool auto_create) { if (m_is_local) - return m_meta; + return m_local_meta; NodeMetadata *meta = m_env->getMap().getNodeMetadata(m_p); if (meta == NULL && auto_create) { @@ -62,9 +62,14 @@ void NodeMetaRef::clearMeta() void NodeMetaRef::reportMetadataChange(const std::string *name) { SANITY_CHECK(!m_is_local); - // NOTE: This same code is in rollback_interface.cpp // Inform other things that the metadata has changed - NodeMetadata *meta = dynamic_cast<NodeMetadata*>(m_meta); + NodeMetadata *meta = dynamic_cast<NodeMetadata*>(getmeta(false)); + + // If the metadata is now empty, get rid of it + if (meta && meta->empty()) { + clearMeta(); + meta = nullptr; + } MapEditEvent event; event.type = MEET_BLOCK_NODE_METADATA_CHANGED; @@ -127,18 +132,14 @@ void NodeMetaRef::handleToTable(lua_State *L, Metadata *_meta) // fields MetaDataRef::handleToTable(L, _meta); - NodeMetadata *meta = (NodeMetadata*) _meta; + NodeMetadata *meta = (NodeMetadata *) _meta; // inventory - lua_newtable(L); Inventory *inv = meta->getInventory(); if (inv) { - std::vector<const InventoryList *> lists = inv->getLists(); - for(std::vector<const InventoryList *>::const_iterator - i = lists.begin(); i != lists.end(); ++i) { - push_inventory_list(L, inv, (*i)->getName().c_str()); - lua_setfield(L, -2, (*i)->getName().c_str()); - } + push_inventory_lists(L, *inv); + } else { + lua_newtable(L); } lua_setfield(L, -2, "inventory"); } @@ -178,8 +179,8 @@ NodeMetaRef::NodeMetaRef(v3s16 p, ServerEnvironment *env): } NodeMetaRef::NodeMetaRef(Metadata *meta): - m_meta(meta), - m_is_local(true) + m_is_local(true), + m_local_meta(meta) { } diff --git a/src/script/lua_api/l_nodemeta.h b/src/script/lua_api/l_nodemeta.h index fdc1766ed..265ece3d0 100644 --- a/src/script/lua_api/l_nodemeta.h +++ b/src/script/lua_api/l_nodemeta.h @@ -33,10 +33,12 @@ class NodeMetadata; class NodeMetaRef : public MetaDataRef { private: + bool m_is_local = false; + // Set for server metadata v3s16 m_p; ServerEnvironment *m_env = nullptr; - Metadata *m_meta = nullptr; - bool m_is_local = false; + // Set for client metadata + Metadata *m_local_meta = nullptr; static const char className[]; static const luaL_Reg methodsServer[]; diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp index f43ba837a..5561eaebf 100644 --- a/src/script/lua_api/l_noise.cpp +++ b/src/script/lua_api/l_noise.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "common/c_packer.h" #include "log.h" #include "porting.h" #include "util/numeric.h" @@ -30,7 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., LuaPerlinNoise */ -LuaPerlinNoise::LuaPerlinNoise(NoiseParams *params) : +LuaPerlinNoise::LuaPerlinNoise(const NoiseParams *params) : np(*params) { } @@ -101,6 +102,25 @@ LuaPerlinNoise *LuaPerlinNoise::checkobject(lua_State *L, int narg) } +void *LuaPerlinNoise::packIn(lua_State *L, int idx) +{ + LuaPerlinNoise *o = checkobject(L, idx); + return new NoiseParams(o->np); +} + +void LuaPerlinNoise::packOut(lua_State *L, void *ptr) +{ + NoiseParams *np = reinterpret_cast<NoiseParams*>(ptr); + if (L) { + LuaPerlinNoise *o = new LuaPerlinNoise(np); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + } + delete np; +} + + void LuaPerlinNoise::Register(lua_State *L) { lua_newtable(L); @@ -126,6 +146,8 @@ void LuaPerlinNoise::Register(lua_State *L) lua_pop(L, 1); lua_register(L, className, create_object); + + script_register_packer(L, className, packIn, packOut); } @@ -141,12 +163,10 @@ luaL_Reg LuaPerlinNoise::methods[] = { LuaPerlinNoiseMap */ -LuaPerlinNoiseMap::LuaPerlinNoiseMap(NoiseParams *params, s32 seed, v3s16 size) +LuaPerlinNoiseMap::LuaPerlinNoiseMap(const NoiseParams *np, s32 seed, v3s16 size) { - m_is3d = size.Z > 1; - np = *params; try { - noise = new Noise(&np, seed, size.X, size.Y, size.Z); + noise = new Noise(np, seed, size.X, size.Y, size.Z); } catch (InvalidNoiseParamsException &e) { throw LuaError(e.what()); } @@ -217,7 +237,7 @@ int LuaPerlinNoiseMap::l_get_3d_map(lua_State *L) LuaPerlinNoiseMap *o = checkobject(L, 1); v3f p = check_v3f(L, 2); - if (!o->m_is3d) + if (!o->is3D()) return 0; Noise *n = o->noise; @@ -248,7 +268,7 @@ int LuaPerlinNoiseMap::l_get_3d_map_flat(lua_State *L) v3f p = check_v3f(L, 2); bool use_buffer = lua_istable(L, 3); - if (!o->m_is3d) + if (!o->is3D()) return 0; Noise *n = o->noise; @@ -289,7 +309,7 @@ int LuaPerlinNoiseMap::l_calc_3d_map(lua_State *L) LuaPerlinNoiseMap *o = checkobject(L, 1); v3f p = check_v3f(L, 2); - if (!o->m_is3d) + if (!o->is3D()) return 0; Noise *n = o->noise; @@ -359,6 +379,35 @@ LuaPerlinNoiseMap *LuaPerlinNoiseMap::checkobject(lua_State *L, int narg) } +struct NoiseMapParams { + NoiseParams np; + s32 seed; + v3s16 size; +}; + +void *LuaPerlinNoiseMap::packIn(lua_State *L, int idx) +{ + LuaPerlinNoiseMap *o = checkobject(L, idx); + NoiseMapParams *ret = new NoiseMapParams(); + ret->np = o->noise->np; + ret->seed = o->noise->seed; + ret->size = v3s16(o->noise->sx, o->noise->sy, o->noise->sz); + return ret; +} + +void LuaPerlinNoiseMap::packOut(lua_State *L, void *ptr) +{ + NoiseMapParams *p = reinterpret_cast<NoiseMapParams*>(ptr); + if (L) { + LuaPerlinNoiseMap *o = new LuaPerlinNoiseMap(&p->np, p->seed, p->size); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + } + delete p; +} + + void LuaPerlinNoiseMap::Register(lua_State *L) { lua_newtable(L); @@ -384,6 +433,8 @@ void LuaPerlinNoiseMap::Register(lua_State *L) lua_pop(L, 1); lua_register(L, className, create_object); + + script_register_packer(L, className, packIn, packOut); } diff --git a/src/script/lua_api/l_noise.h b/src/script/lua_api/l_noise.h index 9f50dfd3f..5d34a479b 100644 --- a/src/script/lua_api/l_noise.h +++ b/src/script/lua_api/l_noise.h @@ -30,6 +30,7 @@ class LuaPerlinNoise : public ModApiBase { private: NoiseParams np; + static const char className[]; static luaL_Reg methods[]; @@ -42,7 +43,7 @@ private: static int l_get_3d(lua_State *L); public: - LuaPerlinNoise(NoiseParams *params); + LuaPerlinNoise(const NoiseParams *params); ~LuaPerlinNoise() = default; // LuaPerlinNoise(seed, octaves, persistence, scale) @@ -51,6 +52,9 @@ public: static LuaPerlinNoise *checkobject(lua_State *L, int narg); + static void *packIn(lua_State *L, int idx); + static void packOut(lua_State *L, void *ptr); + static void Register(lua_State *L); }; @@ -59,9 +63,8 @@ public: */ class LuaPerlinNoiseMap : public ModApiBase { - NoiseParams np; Noise *noise; - bool m_is3d; + static const char className[]; static luaL_Reg methods[]; @@ -80,16 +83,20 @@ class LuaPerlinNoiseMap : public ModApiBase static int l_get_map_slice(lua_State *L); public: - LuaPerlinNoiseMap(NoiseParams *np, s32 seed, v3s16 size); - + LuaPerlinNoiseMap(const NoiseParams *np, s32 seed, v3s16 size); ~LuaPerlinNoiseMap(); + inline bool is3D() const { return noise->sz > 1; } + // LuaPerlinNoiseMap(np, size) // Creates an LuaPerlinNoiseMap and leaves it on top of stack static int create_object(lua_State *L); static LuaPerlinNoiseMap *checkobject(lua_State *L, int narg); + static void *packIn(lua_State *L, int idx); + static void packOut(lua_State *L, void *ptr); + static void Register(lua_State *L); }; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 407b48db0..6bd07a4c1 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -420,8 +420,7 @@ int ObjectRef::l_set_local_animation(lua_State *L) float frame_speed = readParam<float>(L, 6, 30.0f); getServer(L)->setLocalPlayerAnimations(player, frames, frame_speed); - lua_pushboolean(L, true); - return 1; + return 0; } // get_local_animation(self) @@ -464,8 +463,7 @@ int ObjectRef::l_set_eye_offset(lua_State *L) offset_third.Y = rangelim(offset_third.Y,-10,15); //1.5*BS getServer(L)->setPlayerEyeOffset(player, offset_first, offset_third); - lua_pushboolean(L, true); - return 1; + return 0; } // get_eye_offset(self) @@ -737,8 +735,7 @@ int ObjectRef::l_set_nametag_attributes(lua_State *L) prop->validate(); sao->notifyObjectPropertiesModified(); - lua_pushboolean(L, true); - return 1; + return 0; } // get_nametag_attributes(self) @@ -1116,7 +1113,7 @@ int ObjectRef::l_set_look_vertical(lua_State *L) float pitch = readParam<float>(L, 2) * core::RADTODEG; playersao->setLookPitchAndSend(pitch); - return 1; + return 0; } // set_look_horizontal(self, radians) @@ -1131,7 +1128,7 @@ int ObjectRef::l_set_look_horizontal(lua_State *L) float yaw = readParam<float>(L, 2) * core::RADTODEG; playersao->setPlayerYawAndSend(yaw); - return 1; + return 0; } // DEPRECATED @@ -1151,7 +1148,7 @@ int ObjectRef::l_set_look_pitch(lua_State *L) float pitch = readParam<float>(L, 2) * core::RADTODEG; playersao->setLookPitchAndSend(pitch); - return 1; + return 0; } // DEPRECATED @@ -1171,7 +1168,7 @@ int ObjectRef::l_set_look_yaw(lua_State *L) float yaw = readParam<float>(L, 2) * core::RADTODEG; playersao->setPlayerYawAndSend(yaw); - return 1; + return 0; } // set_fov(self, degrees, is_multiplier, transition_time) @@ -1310,8 +1307,7 @@ int ObjectRef::l_set_inventory_formspec(lua_State *L) player->inventory_formspec = formspec; getServer(L)->reportInventoryFormspecModified(player->getName()); - lua_pushboolean(L, true); - return 1; + return 0; } // get_inventory_formspec(self) -> formspec @@ -1342,8 +1338,7 @@ int ObjectRef::l_set_formspec_prepend(lua_State *L) player->formspec_prepend = formspec; getServer(L)->reportFormspecPrependModified(player->getName()); - lua_pushboolean(L, true); - return 1; + return 0; } // get_formspec_prepend(self) @@ -1603,8 +1598,7 @@ int ObjectRef::l_hud_set_flags(lua_State *L) if (!getServer(L)->hudSetFlags(player, flags, mask)) return 0; - lua_pushboolean(L, true); - return 1; + return 0; } // hud_get_flags(self) @@ -1617,20 +1611,11 @@ int ObjectRef::l_hud_get_flags(lua_State *L) return 0; lua_newtable(L); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE); - lua_setfield(L, -2, "hotbar"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_HEALTHBAR_VISIBLE); - lua_setfield(L, -2, "healthbar"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE); - lua_setfield(L, -2, "crosshair"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE); - lua_setfield(L, -2, "wielditem"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_BREATHBAR_VISIBLE); - lua_setfield(L, -2, "breathbar"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE); - lua_setfield(L, -2, "minimap"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE); - lua_setfield(L, -2, "minimap_radar"); + const EnumString *esp = es_HudBuiltinElement; + for (int i = 0; esp[i].str; i++) { + lua_pushboolean(L, (player->hud_flags & esp[i].num) != 0); + lua_setfield(L, -2, esp[i].str); + } return 1; } @@ -1870,11 +1855,37 @@ int ObjectRef::l_set_sky(lua_State *L) } getServer(L)->setSky(player, sky_params); - lua_pushboolean(L, true); - return 1; + return 0; } -// get_sky(self) +static void push_sky_color(lua_State *L, const SkyboxParams ¶ms) +{ + lua_newtable(L); + if (params.type == "regular") { + push_ARGB8(L, params.sky_color.day_sky); + lua_setfield(L, -2, "day_sky"); + push_ARGB8(L, params.sky_color.day_horizon); + lua_setfield(L, -2, "day_horizon"); + push_ARGB8(L, params.sky_color.dawn_sky); + lua_setfield(L, -2, "dawn_sky"); + push_ARGB8(L, params.sky_color.dawn_horizon); + lua_setfield(L, -2, "dawn_horizon"); + push_ARGB8(L, params.sky_color.night_sky); + lua_setfield(L, -2, "night_sky"); + push_ARGB8(L, params.sky_color.night_horizon); + lua_setfield(L, -2, "night_horizon"); + push_ARGB8(L, params.sky_color.indoors); + lua_setfield(L, -2, "indoors"); + } + push_ARGB8(L, params.fog_sun_tint); + lua_setfield(L, -2, "fog_sun_tint"); + push_ARGB8(L, params.fog_moon_tint); + lua_setfield(L, -2, "fog_moon_tint"); + lua_pushstring(L, params.fog_tint_type.c_str()); + lua_setfield(L, -2, "fog_tint_type"); +} + +// get_sky(self, as_table) int ObjectRef::l_get_sky(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -1883,10 +1894,30 @@ int ObjectRef::l_get_sky(lua_State *L) if (player == nullptr) return 0; - SkyboxParams skybox_params = player->getSkyParams(); + const SkyboxParams &skybox_params = player->getSkyParams(); + // handle the deprecated version + if (!readParam<bool>(L, 2, false)) { + log_deprecated(L, "Deprecated call to get_sky, please check lua_api.txt"); + + push_ARGB8(L, skybox_params.bgcolor); + lua_pushlstring(L, skybox_params.type.c_str(), skybox_params.type.size()); + + lua_newtable(L); + s16 i = 1; + for (const std::string &texture : skybox_params.textures) { + lua_pushlstring(L, texture.c_str(), texture.size()); + lua_rawseti(L, -2, i++); + } + lua_pushboolean(L, skybox_params.clouds); + return 4; + } + + lua_newtable(L); push_ARGB8(L, skybox_params.bgcolor); + lua_setfield(L, -2, "base_color"); lua_pushlstring(L, skybox_params.type.c_str(), skybox_params.type.size()); + lua_setfield(L, -2, "type"); lua_newtable(L); s16 i = 1; @@ -1894,44 +1925,30 @@ int ObjectRef::l_get_sky(lua_State *L) lua_pushlstring(L, texture.c_str(), texture.size()); lua_rawseti(L, -2, i++); } + lua_setfield(L, -2, "textures"); lua_pushboolean(L, skybox_params.clouds); - return 4; + lua_setfield(L, -2, "clouds"); + + push_sky_color(L, skybox_params); + lua_setfield(L, -2, "sky_color"); + return 1; } +// DEPRECATED // get_sky_color(self) int ObjectRef::l_get_sky_color(lua_State *L) { NO_MAP_LOCK_REQUIRED; + + log_deprecated(L, "Deprecated call to get_sky_color, use get_sky instead"); + ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); if (player == nullptr) return 0; const SkyboxParams &skybox_params = player->getSkyParams(); - - lua_newtable(L); - if (skybox_params.type == "regular") { - push_ARGB8(L, skybox_params.sky_color.day_sky); - lua_setfield(L, -2, "day_sky"); - push_ARGB8(L, skybox_params.sky_color.day_horizon); - lua_setfield(L, -2, "day_horizon"); - push_ARGB8(L, skybox_params.sky_color.dawn_sky); - lua_setfield(L, -2, "dawn_sky"); - push_ARGB8(L, skybox_params.sky_color.dawn_horizon); - lua_setfield(L, -2, "dawn_horizon"); - push_ARGB8(L, skybox_params.sky_color.night_sky); - lua_setfield(L, -2, "night_sky"); - push_ARGB8(L, skybox_params.sky_color.night_horizon); - lua_setfield(L, -2, "night_horizon"); - push_ARGB8(L, skybox_params.sky_color.indoors); - lua_setfield(L, -2, "indoors"); - } - push_ARGB8(L, skybox_params.fog_sun_tint); - lua_setfield(L, -2, "fog_sun_tint"); - push_ARGB8(L, skybox_params.fog_moon_tint); - lua_setfield(L, -2, "fog_moon_tint"); - lua_pushstring(L, skybox_params.fog_tint_type.c_str()); - lua_setfield(L, -2, "fog_tint_type"); + push_sky_color(L, skybox_params); return 1; } @@ -1960,8 +1977,7 @@ int ObjectRef::l_set_sun(lua_State *L) } getServer(L)->setSun(player, sun_params); - lua_pushboolean(L, true); - return 1; + return 0; } //get_sun(self) @@ -2014,8 +2030,7 @@ int ObjectRef::l_set_moon(lua_State *L) } getServer(L)->setMoon(player, moon_params); - lua_pushboolean(L, true); - return 1; + return 0; } // get_moon(self) @@ -2069,9 +2084,11 @@ int ObjectRef::l_set_stars(lua_State *L) "scale", star_params.scale); } + star_params.day_opacity = getfloatfield_default(L, 2, + "day_opacity", star_params.day_opacity); + getServer(L)->setStars(player, star_params); - lua_pushboolean(L, true); - return 1; + return 0; } // get_stars(self) @@ -2094,6 +2111,8 @@ int ObjectRef::l_get_stars(lua_State *L) lua_setfield(L, -2, "star_color"); lua_pushnumber(L, star_params.scale); lua_setfield(L, -2, "scale"); + lua_pushnumber(L, star_params.day_opacity); + lua_setfield(L, -2, "day_opacity"); return 1; } @@ -2138,8 +2157,7 @@ int ObjectRef::l_set_clouds(lua_State *L) } getServer(L)->setClouds(player, cloud_params); - lua_pushboolean(L, true); - return 1; + return 0; } int ObjectRef::l_get_clouds(lua_State *L) @@ -2193,8 +2211,7 @@ int ObjectRef::l_override_day_night_ratio(lua_State *L) } getServer(L)->overrideDayNightRatio(player, do_override, ratio); - lua_pushboolean(L, true); - return 1; + return 0; } // get_day_night_ratio(self) @@ -2271,6 +2288,61 @@ int ObjectRef::l_set_minimap_modes(lua_State *L) return 0; } +// set_lighting(self, lighting) +int ObjectRef::l_set_lighting(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == nullptr) + return 0; + + luaL_checktype(L, 2, LUA_TTABLE); + Lighting lighting = player->getLighting(); + lua_getfield(L, 2, "shadows"); + if (lua_istable(L, -1)) { + lighting.shadow_intensity = getfloatfield_default(L, -1, "intensity", lighting.shadow_intensity); + } + lua_pop(L, -1); + + getServer(L)->setLighting(player, lighting); + return 0; +} + +// get_lighting(self) +int ObjectRef::l_get_lighting(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == nullptr) + return 0; + + const Lighting &lighting = player->getLighting(); + + lua_newtable(L); // result + lua_newtable(L); // "shadows" + lua_pushnumber(L, lighting.shadow_intensity); + lua_setfield(L, -2, "intensity"); + lua_setfield(L, -2, "shadows"); + return 1; +} + +// respawn(self) +int ObjectRef::l_respawn(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == nullptr) + return 0; + + getServer(L)->RespawnPlayer(player->getPeerId()); + lua_pushboolean(L, true); + return 1; +} + + ObjectRef::ObjectRef(ServerActiveObject *object): m_object(object) {} @@ -2424,5 +2496,9 @@ luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, get_eye_offset), luamethod(ObjectRef, send_mapblock), luamethod(ObjectRef, set_minimap_modes), + luamethod(ObjectRef, set_lighting), + luamethod(ObjectRef, get_lighting), + luamethod(ObjectRef, respawn), + {0,0} }; diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index db3a3a7cf..b36bab492 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -316,9 +316,10 @@ private: // set_sky(self, sky_parameters) static int l_set_sky(lua_State *L); - // get_sky(self) + // get_sky(self, as_table) static int l_get_sky(lua_State *L); + // DEPRECATED // get_sky_color(self) static int l_get_sky_color(lua_State* L); @@ -375,4 +376,13 @@ private: // set_minimap_modes(self, modes, wanted_mode) static int l_set_minimap_modes(lua_State *L); + + // set_lighting(self, lighting) + static int l_set_lighting(lua_State *L); + + // get_lighting(self) + static int l_get_lighting(lua_State *L); + + // respawn(self) + static int l_respawn(lua_State *L); }; diff --git a/src/script/lua_api/l_particleparams.h b/src/script/lua_api/l_particleparams.h new file mode 100644 index 000000000..03f11c07f --- /dev/null +++ b/src/script/lua_api/l_particleparams.h @@ -0,0 +1,282 @@ +/* +Minetest +Copyright (C) 2021 velartrill, Lexi Hale <lexi@hale.su> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once +#include "lua_api/l_particles.h" +#include "lua_api/l_object.h" +#include "lua_api/l_internal.h" +#include "common/c_converter.h" +#include "common/c_content.h" +#include "server.h" +#include "particles.h" + +namespace LuaParticleParams +{ + using namespace ParticleParamTypes; + + template<typename T> + inline void readNumericLuaValue(lua_State* L, T& ret) + { + if (lua_isnil(L,-1)) + return; + + if (std::is_integral<T>()) + ret = lua_tointeger(L, -1); + else + ret = lua_tonumber(L, -1); + } + + template <typename T, size_t N> + inline void readNumericLuaValue(lua_State* L, Parameter<T,N>& ret) + { + readNumericLuaValue<T>(L, ret.val); + } + + // these are unfortunately necessary as C++ intentionally disallows function template + // specialization and there's no way to make template overloads reliably resolve correctly + inline void readLuaValue(lua_State* L, f32Parameter& ret) { readNumericLuaValue(L, ret); } + inline void readLuaValue(lua_State* L, f32& ret) { readNumericLuaValue(L, ret); } + inline void readLuaValue(lua_State* L, u16& ret) { readNumericLuaValue(L, ret); } + inline void readLuaValue(lua_State* L, u8& ret) { readNumericLuaValue(L, ret); } + + inline void readLuaValue(lua_State* L, v3fParameter& ret) + { + if (lua_isnil(L, -1)) + return; + + if (lua_isnumber(L, -1)) { // shortcut for uniform vectors + auto n = lua_tonumber(L, -1); + ret = v3fParameter(n,n,n); + } else { + ret = (v3fParameter)check_v3f(L, -1); + } + } + + inline void readLuaValue(lua_State* L, v2fParameter& ret) + { + if (lua_isnil(L, -1)) + return; + + if (lua_isnumber(L, -1)) { // shortcut for uniform vectors + auto n = lua_tonumber(L, -1); + ret = v2fParameter(n,n); + } else { + ret = (v2fParameter)check_v2f(L, -1); + } + } + + inline void readLuaValue(lua_State* L, TweenStyle& ret) + { + if (lua_isnil(L, -1)) + return; + + static const EnumString opts[] = { + {(int)TweenStyle::fwd, "fwd"}, + {(int)TweenStyle::rev, "rev"}, + {(int)TweenStyle::pulse, "pulse"}, + {(int)TweenStyle::flicker, "flicker"}, + {0, nullptr}, + }; + + luaL_checktype(L, -1, LUA_TSTRING); + int v = (int)TweenStyle::fwd; + if (!string_to_enum(opts, v, lua_tostring(L, -1))) { + throw LuaError("tween style must be one of ('fwd', 'rev', 'pulse', 'flicker')"); + } + ret = (TweenStyle)v; + } + + inline void readLuaValue(lua_State* L, AttractorKind& ret) + { + if (lua_isnil(L, -1)) + return; + + static const EnumString opts[] = { + {(int)AttractorKind::none, "none"}, + {(int)AttractorKind::point, "point"}, + {(int)AttractorKind::line, "line"}, + {(int)AttractorKind::plane, "plane"}, + {0, nullptr}, + }; + + luaL_checktype(L, -1, LUA_TSTRING); + int v = (int)AttractorKind::none; + if (!string_to_enum(opts, v, lua_tostring(L, -1))) { + throw LuaError("attractor kind must be one of ('none', 'point', 'line', 'plane')"); + } + ret = (AttractorKind)v; + } + + inline void readLuaValue(lua_State* L, BlendMode& ret) + { + if (lua_isnil(L, -1)) + return; + + static const EnumString opts[] = { + {(int)BlendMode::alpha, "alpha"}, + {(int)BlendMode::add, "add"}, + {(int)BlendMode::sub, "sub"}, + {(int)BlendMode::screen, "screen"}, + {0, nullptr}, + }; + + luaL_checktype(L, -1, LUA_TSTRING); + int v = (int)BlendMode::alpha; + if (!string_to_enum(opts, v, lua_tostring(L, -1))) { + throw LuaError("blend mode must be one of ('alpha', 'add', 'sub', 'screen')"); + } + ret = (BlendMode)v; + } + + template <typename T> void + readLuaValue(lua_State* L, RangedParameter<T>& field) + { + if (lua_isnil(L,-1)) + return; + if (!lua_istable(L,-1)) // is this is just a literal value? + goto set_uniform; + + lua_getfield(L, -1, "min"); + // handle convenience syntax for non-range values + if (lua_isnil(L,-1)) { + lua_pop(L, 1); + goto set_uniform; + } + readLuaValue(L,field.min); + lua_pop(L, 1); + + lua_getfield(L, -1, "max"); + readLuaValue(L,field.max); + lua_pop(L, 1); + + lua_getfield(L, -1, "bias"); + if (!lua_isnil(L,-1)) + readLuaValue(L,field.bias); + lua_pop(L, 1); + return; + + set_uniform: + readLuaValue(L, field.min); + readLuaValue(L, field.max); + } + + template <typename T> void + readLegacyValue(lua_State* L, const char* name, T& field) {} + + template <typename T> void + readLegacyValue(lua_State* L, const char* name, RangedParameter<T>& field) + { + int tbl = lua_gettop(L); + lua_pushliteral(L, "min"); + lua_pushstring(L, name); + lua_concat(L, 2); + lua_gettable(L, tbl); + if (!lua_isnil(L, -1)) { + readLuaValue(L, field.min); + } + lua_settop(L, tbl); + + lua_pushliteral(L, "max"); + lua_pushstring(L, name); + lua_concat(L, 2); + lua_gettable(L, tbl); + if (!lua_isnil(L, -1)) { + readLuaValue(L, field.max); + } + lua_settop(L, tbl); + } + + template <typename T> void + readTweenTable(lua_State* L, const char* name, TweenedParameter<T>& field) + { + int tbl = lua_gettop(L); + + lua_pushstring(L, name); + lua_pushliteral(L, "_tween"); + lua_concat(L, 2); + lua_gettable(L, tbl); + if(lua_istable(L, -1)) { + int tween = lua_gettop(L); + // get the starting value + lua_pushinteger(L, 1), lua_gettable(L, tween); + readLuaValue(L, field.start); + lua_pop(L, 1); + + // get the final value -- use len instead of 2 so that this + // gracefully degrades if keyframe support is later added + lua_pushinteger(L, (lua_Integer)lua_objlen(L, -1)), lua_gettable(L, tween); + readLuaValue(L, field.end); + lua_pop(L, 1); + + // get the effect settings + lua_getfield(L, -1, "style"); + if (!lua_isnil(L,-1)) + readLuaValue(L, field.style); + lua_pop(L, 1); + + lua_getfield(L, -1, "reps"); + if (!lua_isnil(L,-1)) + readLuaValue(L, field.reps); + lua_pop(L, 1); + + lua_getfield(L, -1, "start"); + if (!lua_isnil(L,-1)) + readLuaValue(L, field.beginning); + lua_pop(L, 1); + + goto done; + } else { + lua_pop(L,1); + } + // the table is not present; check for nonanimated values + + lua_getfield(L, tbl, name); + if(!lua_isnil(L, -1)) { + readLuaValue(L, field.start); + lua_settop(L, tbl); + goto set_uniform; + } else { + lua_pop(L,1); + } + + // the goto did not trigger, so this table is not present either + // check for pre-5.6.0 legacy values + readLegacyValue(L, name, field.start); + + set_uniform: + field.end = field.start; + done: + lua_settop(L, tbl); // clean up after ourselves + } + + inline u16 readAttachmentID(lua_State* L, const char* name) + { + u16 id = 0; + lua_getfield(L, -1, name); + if (!lua_isnil(L, -1)) { + ObjectRef *ref = ObjectRef::checkobject(L, -1); + if (auto obj = ObjectRef::getobject(ref)) + id = obj->getId(); + } + lua_pop(L, 1); + return id; + } + + void readTexValue(lua_State* L, ServerParticleTexture& tex); +} diff --git a/src/script/lua_api/l_particles.cpp b/src/script/lua_api/l_particles.cpp index a51c4fe20..586c7dc73 100644 --- a/src/script/lua_api/l_particles.cpp +++ b/src/script/lua_api/l_particles.cpp @@ -20,30 +20,50 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_particles.h" #include "lua_api/l_object.h" #include "lua_api/l_internal.h" +#include "lua_api/l_particleparams.h" #include "common/c_converter.h" #include "common/c_content.h" #include "server.h" #include "particles.h" -// add_particle({pos=, velocity=, acceleration=, expirationtime=, -// size=, collisiondetection=, collision_removal=, object_collision=, -// vertical=, texture=, player=}) -// pos/velocity/acceleration = {x=num, y=num, z=num} -// expirationtime = num (seconds) -// size = num -// collisiondetection = bool -// collision_removal = bool -// object_collision = bool -// vertical = bool -// texture = e.g."default_wood.png" -// animation = TileAnimation definition -// glow = num +void LuaParticleParams::readTexValue(lua_State* L, ServerParticleTexture& tex) +{ + StackUnroller unroll(L); + + tex.animated = false; + if (lua_isstring(L, -1)) { + tex.string = lua_tostring(L, -1); + return; + } + + luaL_checktype(L, -1, LUA_TTABLE); + lua_getfield(L, -1, "name"); + tex.string = luaL_checkstring(L, -1); + lua_pop(L, 1); + + lua_getfield(L, -1, "animation"); + if (! lua_isnil(L, -1)) { + tex.animated = true; + tex.animation = read_animation_definition(L, -1); + } + lua_pop(L, 1); + + lua_getfield(L, -1, "blend"); + LuaParticleParams::readLuaValue(L, tex.blendmode); + lua_pop(L, 1); + + LuaParticleParams::readTweenTable(L, "alpha", tex.alpha); + LuaParticleParams::readTweenTable(L, "scale", tex.scale); + +} + +// add_particle({...}) int ModApiParticles::l_add_particle(lua_State *L) { NO_MAP_LOCK_REQUIRED; // Get parameters - struct ParticleParameters p; + ParticleParameters p; std::string playername; if (lua_gettop(L) > 1) // deprecated @@ -56,7 +76,7 @@ int ModApiParticles::l_add_particle(lua_State *L) p.expirationtime = luaL_checknumber(L, 4); p.size = luaL_checknumber(L, 5); p.collisiondetection = readParam<bool>(L, 6); - p.texture = luaL_checkstring(L, 7); + p.texture.string = luaL_checkstring(L, 7); if (lua_gettop(L) == 8) // only spawn for a single player playername = luaL_checkstring(L, 8); } @@ -108,7 +128,12 @@ int ModApiParticles::l_add_particle(lua_State *L) p.animation = read_animation_definition(L, -1); lua_pop(L, 1); - p.texture = getstringfield_default(L, 1, "texture", p.texture); + lua_getfield(L, 1, "texture"); + if (!lua_isnil(L, -1)) { + LuaParticleParams::readTexValue(L, p.texture); + } + lua_pop(L, 1); + p.glow = getintfield_default(L, 1, "glow", p.glow); lua_getfield(L, 1, "node"); @@ -119,34 +144,26 @@ int ModApiParticles::l_add_particle(lua_State *L) p.node_tile = getintfield_default(L, 1, "node_tile", p.node_tile); playername = getstringfield_default(L, 1, "playername", ""); + + lua_getfield(L, 1, "drag"); + if (lua_istable(L, -1)) + p.drag = check_v3f(L, -1); + lua_pop(L, 1); + + lua_getfield(L, 1, "jitter"); + LuaParticleParams::readLuaValue(L, p.jitter); + lua_pop(L, 1); + + lua_getfield(L, 1, "bounce"); + LuaParticleParams::readLuaValue(L, p.bounce); + lua_pop(L, 1); } getServer(L)->spawnParticle(playername, p); return 1; } -// add_particlespawner({amount=, time=, -// minpos=, maxpos=, -// minvel=, maxvel=, -// minacc=, maxacc=, -// minexptime=, maxexptime=, -// minsize=, maxsize=, -// collisiondetection=, -// collision_removal=, -// object_collision=, -// vertical=, -// texture=, -// player=}) -// minpos/maxpos/minvel/maxvel/minacc/maxacc = {x=num, y=num, z=num} -// minexptime/maxexptime = num (seconds) -// minsize/maxsize = num -// collisiondetection = bool -// collision_removal = bool -// object_collision = bool -// vertical = bool -// texture = e.g."default_wood.png" -// animation = TileAnimation definition -// glow = num +// add_particlespawner({...}) int ModApiParticles::l_add_particlespawner(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -156,24 +173,31 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) ServerActiveObject *attached = NULL; std::string playername; + using namespace ParticleParamTypes; if (lua_gettop(L) > 1) //deprecated { log_deprecated(L, "Deprecated add_particlespawner call with " "individual parameters instead of definition"); p.amount = luaL_checknumber(L, 1); p.time = luaL_checknumber(L, 2); - p.minpos = check_v3f(L, 3); - p.maxpos = check_v3f(L, 4); - p.minvel = check_v3f(L, 5); - p.maxvel = check_v3f(L, 6); - p.minacc = check_v3f(L, 7); - p.maxacc = check_v3f(L, 8); - p.minexptime = luaL_checknumber(L, 9); - p.maxexptime = luaL_checknumber(L, 10); - p.minsize = luaL_checknumber(L, 11); - p.maxsize = luaL_checknumber(L, 12); + auto minpos = check_v3f(L, 3); + auto maxpos = check_v3f(L, 4); + auto minvel = check_v3f(L, 5); + auto maxvel = check_v3f(L, 6); + auto minacc = check_v3f(L, 7); + auto maxacc = check_v3f(L, 8); + auto minexptime = luaL_checknumber(L, 9); + auto maxexptime = luaL_checknumber(L, 10); + auto minsize = luaL_checknumber(L, 11); + auto maxsize = luaL_checknumber(L, 12); + p.pos = v3fRange(minpos, maxpos); + p.vel = v3fRange(minvel, maxvel); + p.acc = v3fRange(minacc, maxacc); + p.exptime = f32Range(minexptime, maxexptime); + p.size = f32Range(minsize, maxsize); + p.collisiondetection = readParam<bool>(L, 13); - p.texture = luaL_checkstring(L, 14); + p.texture.string = luaL_checkstring(L, 14); if (lua_gettop(L) == 15) // only spawn for a single player playername = luaL_checkstring(L, 15); } @@ -182,40 +206,46 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) p.amount = getintfield_default(L, 1, "amount", p.amount); p.time = getfloatfield_default(L, 1, "time", p.time); - lua_getfield(L, 1, "minpos"); - if (lua_istable(L, -1)) - p.minpos = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "maxpos"); - if (lua_istable(L, -1)) - p.maxpos = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "minvel"); - if (lua_istable(L, -1)) - p.minvel = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "maxvel"); - if (lua_istable(L, -1)) - p.maxvel = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "minacc"); - if (lua_istable(L, -1)) - p.minacc = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "maxacc"); - if (lua_istable(L, -1)) - p.maxacc = check_v3f(L, -1); - lua_pop(L, 1); + // set default values + p.exptime = 1; + p.size = 1; + + // read spawner parameters from the table + LuaParticleParams::readTweenTable(L, "pos", p.pos); + LuaParticleParams::readTweenTable(L, "vel", p.vel); + LuaParticleParams::readTweenTable(L, "acc", p.acc); + LuaParticleParams::readTweenTable(L, "size", p.size); + LuaParticleParams::readTweenTable(L, "exptime", p.exptime); + LuaParticleParams::readTweenTable(L, "drag", p.drag); + LuaParticleParams::readTweenTable(L, "jitter", p.jitter); + LuaParticleParams::readTweenTable(L, "bounce", p.bounce); + lua_getfield(L, 1, "attract"); + if (!lua_isnil(L, -1)) { + luaL_checktype(L, -1, LUA_TTABLE); + lua_getfield(L, -1, "kind"); + LuaParticleParams::readLuaValue(L, p.attractor_kind); + lua_pop(L,1); + + lua_getfield(L, -1, "die_on_contact"); + if (!lua_isnil(L, -1)) + p.attractor_kill = readParam<bool>(L, -1); + lua_pop(L,1); + + if (p.attractor_kind != AttractorKind::none) { + LuaParticleParams::readTweenTable(L, "strength", p.attract); + LuaParticleParams::readTweenTable(L, "origin", p.attractor_origin); + p.attractor_attachment = LuaParticleParams::readAttachmentID(L, "origin_attached"); + if (p.attractor_kind != AttractorKind::point) { + LuaParticleParams::readTweenTable(L, "direction", p.attractor_direction); + p.attractor_direction_attachment = LuaParticleParams::readAttachmentID(L, "direction_attached"); + } + } + } else { + p.attractor_kind = AttractorKind::none; + } + lua_pop(L,1); + LuaParticleParams::readTweenTable(L, "radius", p.radius); - p.minexptime = getfloatfield_default(L, 1, "minexptime", p.minexptime); - p.maxexptime = getfloatfield_default(L, 1, "maxexptime", p.maxexptime); - p.minsize = getfloatfield_default(L, 1, "minsize", p.minsize); - p.maxsize = getfloatfield_default(L, 1, "maxsize", p.maxsize); p.collisiondetection = getboolfield_default(L, 1, "collisiondetection", p.collisiondetection); p.collision_removal = getboolfield_default(L, 1, @@ -234,11 +264,29 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) attached = ObjectRef::getobject(ref); } + lua_getfield(L, 1, "texture"); + if (!lua_isnil(L, -1)) { + LuaParticleParams::readTexValue(L, p.texture); + } + lua_pop(L, 1); + p.vertical = getboolfield_default(L, 1, "vertical", p.vertical); - p.texture = getstringfield_default(L, 1, "texture", p.texture); playername = getstringfield_default(L, 1, "playername", ""); p.glow = getintfield_default(L, 1, "glow", p.glow); + lua_getfield(L, 1, "texpool"); + if (lua_istable(L, -1)) { + size_t tl = lua_objlen(L, -1); + p.texpool.reserve(tl); + for (size_t i = 0; i < tl; ++i) { + lua_pushinteger(L, i+1), lua_gettable(L, -2); + p.texpool.emplace_back(); + LuaParticleParams::readTexValue(L, p.texpool.back()); + lua_pop(L,1); + } + } + lua_pop(L, 1); + lua_getfield(L, 1, "node"); if (lua_istable(L, -1)) p.node = readnode(L, -1, getGameDef(L)->ndef()); diff --git a/src/script/lua_api/l_particles_local.cpp b/src/script/lua_api/l_particles_local.cpp index cc68b13a5..62cbab8e9 100644 --- a/src/script/lua_api/l_particles_local.cpp +++ b/src/script/lua_api/l_particles_local.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_converter.h" #include "lua_api/l_internal.h" #include "lua_api/l_object.h" +#include "lua_api/l_particleparams.h" #include "client/particles.h" #include "client/client.h" #include "client/clientevent.h" @@ -49,6 +50,19 @@ int ModApiParticlesLocal::l_add_particle(lua_State *L) p.acc = check_v3f(L, -1); lua_pop(L, 1); + lua_getfield(L, 1, "drag"); + if (lua_istable(L, -1)) + p.drag = check_v3f(L, -1); + lua_pop(L, 1); + + lua_getfield(L, 1, "jitter"); + LuaParticleParams::readLuaValue(L, p.jitter); + lua_pop(L, 1); + + lua_getfield(L, 1, "bounce"); + LuaParticleParams::readLuaValue(L, p.bounce); + lua_pop(L, 1); + p.expirationtime = getfloatfield_default(L, 1, "expirationtime", p.expirationtime); p.size = getfloatfield_default(L, 1, "size", p.size); @@ -64,7 +78,11 @@ int ModApiParticlesLocal::l_add_particle(lua_State *L) p.animation = read_animation_definition(L, -1); lua_pop(L, 1); - p.texture = getstringfield_default(L, 1, "texture", p.texture); + lua_getfield(L, 1, "texture"); + if (!lua_isnil(L, -1)) { + LuaParticleParams::readTexValue(L,p.texture); + } + lua_pop(L, 1); p.glow = getintfield_default(L, 1, "glow", p.glow); lua_getfield(L, 1, "node"); @@ -88,44 +106,50 @@ int ModApiParticlesLocal::l_add_particlespawner(lua_State *L) // Get parameters ParticleSpawnerParameters p; - p.amount = getintfield_default(L, 1, "amount", p.amount); p.time = getfloatfield_default(L, 1, "time", p.time); - lua_getfield(L, 1, "minpos"); - if (lua_istable(L, -1)) - p.minpos = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "maxpos"); - if (lua_istable(L, -1)) - p.maxpos = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "minvel"); - if (lua_istable(L, -1)) - p.minvel = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "maxvel"); - if (lua_istable(L, -1)) - p.maxvel = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "minacc"); - if (lua_istable(L, -1)) - p.minacc = check_v3f(L, -1); - lua_pop(L, 1); + // set default values + p.exptime = 1; + p.size = 1; + + // read spawner parameters from the table + using namespace ParticleParamTypes; + LuaParticleParams::readTweenTable(L, "pos", p.pos); + LuaParticleParams::readTweenTable(L, "vel", p.vel); + LuaParticleParams::readTweenTable(L, "acc", p.acc); + LuaParticleParams::readTweenTable(L, "size", p.size); + LuaParticleParams::readTweenTable(L, "exptime", p.exptime); + LuaParticleParams::readTweenTable(L, "drag", p.drag); + LuaParticleParams::readTweenTable(L, "jitter", p.jitter); + LuaParticleParams::readTweenTable(L, "bounce", p.bounce); + lua_getfield(L, 1, "attract"); + if (!lua_isnil(L, -1)) { + luaL_checktype(L, -1, LUA_TTABLE); + lua_getfield(L, -1, "kind"); + LuaParticleParams::readLuaValue(L, p.attractor_kind); + lua_pop(L,1); + + lua_getfield(L, -1, "die_on_contact"); + if (!lua_isnil(L, -1)) + p.attractor_kill = readParam<bool>(L, -1); + lua_pop(L,1); + + if (p.attractor_kind != AttractorKind::none) { + LuaParticleParams::readTweenTable(L, "strength", p.attract); + LuaParticleParams::readTweenTable(L, "origin", p.attractor_origin); + p.attractor_attachment = LuaParticleParams::readAttachmentID(L, "origin_attached"); + if (p.attractor_kind != AttractorKind::point) { + LuaParticleParams::readTweenTable(L, "direction", p.attractor_direction); + p.attractor_direction_attachment = LuaParticleParams::readAttachmentID(L, "direction_attached"); + } + } + } else { + p.attractor_kind = AttractorKind::none; + } + lua_pop(L,1); + LuaParticleParams::readTweenTable(L, "radius", p.radius); - lua_getfield(L, 1, "maxacc"); - if (lua_istable(L, -1)) - p.maxacc = check_v3f(L, -1); - lua_pop(L, 1); - - p.minexptime = getfloatfield_default(L, 1, "minexptime", p.minexptime); - p.maxexptime = getfloatfield_default(L, 1, "maxexptime", p.maxexptime); - p.minsize = getfloatfield_default(L, 1, "minsize", p.minsize); - p.maxsize = getfloatfield_default(L, 1, "maxsize", p.maxsize); p.collisiondetection = getboolfield_default(L, 1, "collisiondetection", p.collisiondetection); p.collision_removal = getboolfield_default(L, 1, @@ -137,10 +161,28 @@ int ModApiParticlesLocal::l_add_particlespawner(lua_State *L) p.animation = read_animation_definition(L, -1); lua_pop(L, 1); + lua_getfield(L, 1, "texture"); + if (!lua_isnil(L, -1)) { + LuaParticleParams::readTexValue(L, p.texture); + } + lua_pop(L, 1); + p.vertical = getboolfield_default(L, 1, "vertical", p.vertical); - p.texture = getstringfield_default(L, 1, "texture", p.texture); p.glow = getintfield_default(L, 1, "glow", p.glow); + lua_getfield(L, 1, "texpool"); + if (lua_istable(L, -1)) { + size_t tl = lua_objlen(L, -1); + p.texpool.reserve(tl); + for (size_t i = 0; i < tl; ++i) { + lua_pushinteger(L, i+1), lua_gettable(L, -2); + p.texpool.emplace_back(); + LuaParticleParams::readTexValue(L, p.texpool.back()); + lua_pop(L,1); + } + } + lua_pop(L, 1); + lua_getfield(L, 1, "node"); if (lua_istable(L, -1)) p.node = readnode(L, -1, getGameDef(L)->ndef()); diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 88ab5e16b..a5daae346 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "common/c_packer.h" #include "cpp_api/s_base.h" #include "cpp_api/s_security.h" #include "scripting_server.h" @@ -61,11 +62,8 @@ int ModApiServer::l_get_server_uptime(lua_State *L) int ModApiServer::l_get_server_max_lag(lua_State *L) { NO_MAP_LOCK_REQUIRED; - ServerEnvironment *s_env = dynamic_cast<ServerEnvironment *>(getEnv(L)); - if (!s_env) - lua_pushnil(L); - else - lua_pushnumber(L, s_env->getMaxLagEstimate()); + GET_ENV_PTR; + lua_pushnumber(L, env->getMaxLagEstimate()); return 1; } @@ -325,12 +323,15 @@ int ModApiServer::l_disconnect_player(lua_State *L) else message.append("Disconnected."); - RemotePlayer *player = dynamic_cast<ServerEnvironment *>(getEnv(L))->getPlayer(name); - if (player == NULL) { + Server *server = getServer(L); + + RemotePlayer *player = server->getEnv().getPlayer(name); + if (!player) { lua_pushboolean(L, false); // No such player return 1; } - getServer(L)->DenyAccess_Legacy(player->getPeerId(), utf8_to_wide(message)); + + server->DenyAccess(player->getPeerId(), SERVER_ACCESSDENIED_CUSTOM_STRING, message); lua_pushboolean(L, true); return 1; } @@ -392,12 +393,11 @@ int ModApiServer::l_get_modpath(lua_State *L) { NO_MAP_LOCK_REQUIRED; std::string modname = luaL_checkstring(L, 1); - const ModSpec *mod = getServer(L)->getModSpec(modname); - if (!mod) { + const ModSpec *mod = getGameDef(L)->getModSpec(modname); + if (!mod) lua_pushnil(L); - return 1; - } - lua_pushstring(L, mod->path.c_str()); + else + lua_pushstring(L, mod->path.c_str()); return 1; } @@ -409,13 +409,14 @@ int ModApiServer::l_get_modnames(lua_State *L) // Get a list of mods std::vector<std::string> modlist; - getServer(L)->getModNames(modlist); + for (auto &it : getGameDef(L)->getMods()) + modlist.emplace_back(it.name); std::sort(modlist.begin(), modlist.end()); // Package them up for Lua lua_createtable(L, modlist.size(), 0); - std::vector<std::string>::iterator iter = modlist.begin(); + auto iter = modlist.begin(); for (u16 i = 0; iter != modlist.end(); ++iter) { lua_pushstring(L, iter->c_str()); lua_rawseti(L, -2, ++i); @@ -427,8 +428,8 @@ int ModApiServer::l_get_modnames(lua_State *L) int ModApiServer::l_get_worldpath(lua_State *L) { NO_MAP_LOCK_REQUIRED; - std::string worldpath = getServer(L)->getWorldPath(); - lua_pushstring(L, worldpath.c_str()); + const Server *srv = getServer(L); + lua_pushstring(L, srv->getWorldPath().c_str()); return 1; } @@ -436,16 +437,15 @@ int ModApiServer::l_get_worldpath(lua_State *L) int ModApiServer::l_sound_play(lua_State *L) { NO_MAP_LOCK_REQUIRED; - SimpleSoundSpec spec; - read_soundspec(L, 1, spec); - ServerSoundParams params; + ServerPlayingSound params; + read_soundspec(L, 1, params.spec); read_server_sound_params(L, 2, params); bool ephemeral = lua_gettop(L) > 2 && readParam<bool>(L, 3); if (ephemeral) { - getServer(L)->playSound(spec, params, true); + getServer(L)->playSound(params, true); lua_pushnil(L); } else { - s32 handle = getServer(L)->playSound(spec, params); + s32 handle = getServer(L)->playSound(params); lua_pushinteger(L, handle); } return 1; @@ -510,7 +510,8 @@ int ModApiServer::l_dynamic_add_media(lua_State *L) int ModApiServer::l_is_singleplayer(lua_State *L) { NO_MAP_LOCK_REQUIRED; - lua_pushboolean(L, getServer(L)->isSingleplayer()); + const Server *srv = getServer(L); + lua_pushboolean(L, srv->isSingleplayer()); return 1; } @@ -525,6 +526,76 @@ int ModApiServer::l_notify_authentication_modified(lua_State *L) return 0; } +// do_async_callback(func, params, mod_origin) +int ModApiServer::l_do_async_callback(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ServerScripting *script = getScriptApi<ServerScripting>(L); + + luaL_checktype(L, 1, LUA_TFUNCTION); + luaL_checktype(L, 2, LUA_TTABLE); + luaL_checktype(L, 3, LUA_TSTRING); + + call_string_dump(L, 1); + size_t func_length; + const char *serialized_func_raw = lua_tolstring(L, -1, &func_length); + + PackedValue *param = script_pack(L, 2); + + std::string mod_origin = readParam<std::string>(L, 3); + + u32 jobId = script->queueAsync( + std::string(serialized_func_raw, func_length), + param, mod_origin); + + lua_settop(L, 0); + lua_pushinteger(L, jobId); + return 1; +} + +// register_async_dofile(path) +int ModApiServer::l_register_async_dofile(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + std::string path = readParam<std::string>(L, 1); + CHECK_SECURE_PATH(L, path.c_str(), false); + + // Find currently running mod name (only at init time) + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + if (!lua_isstring(L, -1)) + return 0; + std::string modname = readParam<std::string>(L, -1); + + getServer(L)->m_async_init_files.emplace_back(modname, path); + lua_pushboolean(L, true); + return 1; +} + +// serialize_roundtrip(value) +// Meant for unit testing the packer from Lua +int ModApiServer::l_serialize_roundtrip(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + int top = lua_gettop(L); + auto *pv = script_pack(L, 1); + if (top != lua_gettop(L)) + throw LuaError("stack values leaked"); + +#ifndef NDEBUG + script_dump_packed(pv); +#endif + + top = lua_gettop(L); + script_unpack(L, pv); + delete pv; + if (top + 1 != lua_gettop(L)) + throw LuaError("stack values leaked"); + + return 1; +} + void ModApiServer::Initialize(lua_State *L, int top) { API_FCT(request_shutdown); @@ -558,4 +629,18 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(remove_player); API_FCT(unban_player_or_ip); API_FCT(notify_authentication_modified); + + API_FCT(do_async_callback); + API_FCT(register_async_dofile); + API_FCT(serialize_roundtrip); +} + +void ModApiServer::InitializeAsync(lua_State *L, int top) +{ + API_FCT(get_worldpath); + API_FCT(is_singleplayer); + + API_FCT(get_current_modname); + API_FCT(get_modpath); + API_FCT(get_modnames); } diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index f05c0b7c9..a4f38c34e 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -106,6 +106,16 @@ private: // notify_authentication_modified(name) static int l_notify_authentication_modified(lua_State *L); + // do_async_callback(func, params, mod_origin) + static int l_do_async_callback(lua_State *L); + + // register_async_dofile(path) + static int l_register_async_dofile(lua_State *L); + + // serialize_roundtrip(obj) + static int l_serialize_roundtrip(lua_State *L); + public: static void Initialize(lua_State *L, int top); + static void InitializeAsync(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_settings.cpp b/src/script/lua_api/l_settings.cpp index 14398dda2..3f3fda56e 100644 --- a/src/script/lua_api/l_settings.cpp +++ b/src/script/lua_api/l_settings.cpp @@ -27,9 +27,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" -/* This protects: - * 'secure.*' settings from being set - * some mapgen settings from being set +/* This protects the following from being set: + * 'secure.*' settings + * some security-relevant settings + * (better solution pending) + * some mapgen settings * (not security-criticial, just to avoid messing up user configs) */ #define CHECK_SETTING_SECURITY(L, name) \ @@ -41,7 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc., static inline int checkSettingSecurity(lua_State* L, const std::string &name) { if (ScriptApiSecurity::isSecure(L) && name.compare(0, 7, "secure.") == 0) - throw LuaError("Attempt to set secure setting."); + throw LuaError("Attempted to set secure setting."); bool is_mainmenu = false; #ifndef SERVER @@ -54,6 +56,17 @@ static inline int checkSettingSecurity(lua_State* L, const std::string &name) return -1; } + const char *disallowed[] = { + "main_menu_script", "shader_path", "texture_path", "screenshot_path", + "serverlist_file", "serverlist_url", "map-dir", "contentdb_url", + }; + if (!is_mainmenu) { + for (const char *name2 : disallowed) { + if (name == name2) + throw LuaError("Attempted to set disallowed setting."); + } + } + return 0; } diff --git a/src/script/lua_api/l_sound.cpp b/src/script/lua_api/l_sound.cpp index b86eda53e..934b4a07e 100644 --- a/src/script/lua_api/l_sound.cpp +++ b/src/script/lua_api/l_sound.cpp @@ -28,9 +28,9 @@ int ModApiSound::l_sound_play(lua_State *L) { SimpleSoundSpec spec; read_soundspec(L, 1, spec); - bool looped = readParam<bool>(L, 2); + spec.loop = readParam<bool>(L, 2); - s32 handle = getGuiEngine(L)->playSound(spec, looped); + s32 handle = getGuiEngine(L)->playSound(spec); lua_pushinteger(L, handle); diff --git a/src/script/lua_api/l_storage.cpp b/src/script/lua_api/l_storage.cpp index b8f4347a8..b6c53e353 100644 --- a/src/script/lua_api/l_storage.cpp +++ b/src/script/lua_api/l_storage.cpp @@ -25,12 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc., int ModApiStorage::l_get_mod_storage(lua_State *L) { - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); - if (!lua_isstring(L, -1)) { - return 0; - } - - std::string mod_name = readParam<std::string>(L, -1); + // Note that this is wrapped in Lua, see builtin/common/mod_storage.lua + std::string mod_name = readParam<std::string>(L, 1); ModMetadata *store = nullptr; diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index b04f26fda..f602aed99 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -159,6 +159,17 @@ int ModApiUtil::l_write_json(lua_State *L) return 1; } +// get_tool_wear_after_use(uses[, initial_wear]) +int ModApiUtil::l_get_tool_wear_after_use(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + u32 uses = readParam<int>(L, 1); + u16 initial_wear = readParam<int>(L, 2, 0); + u16 wear = calculateResultWear(uses, initial_wear); + lua_pushnumber(L, wear); + return 1; +} + // get_dig_params(groups, tool_capabilities[, wear]) int ModApiUtil::l_get_dig_params(lua_State *L) { @@ -469,6 +480,8 @@ int ModApiUtil::l_get_version(lua_State *L) lua_setfield(L, table, "hash"); } + lua_pushboolean(L, DEVELOPMENT_BUILD); + lua_setfield(L, table, "is_dev"); return 1; } @@ -586,6 +599,7 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(parse_json); API_FCT(write_json); + API_FCT(get_tool_wear_after_use); API_FCT(get_dig_params); API_FCT(get_hit_params); @@ -647,6 +661,9 @@ void ModApiUtil::InitializeClient(lua_State *L, int top) API_FCT(sha1); API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); + + LuaSettings::create(L, g_settings, g_settings_path); + lua_setfield(L, top, "settings"); } void ModApiUtil::InitializeAsync(lua_State *L, int top) @@ -671,6 +688,9 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) API_FCT(cpdir); API_FCT(mvdir); API_FCT(get_dir_list); + API_FCT(safe_file_write); + + API_FCT(request_insecure_environment); API_FCT(encode_base64); API_FCT(decode_base64); @@ -680,6 +700,8 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); + API_FCT(encode_png); + API_FCT(get_last_run_mod); API_FCT(set_last_run_mod); diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index fcf8a1057..ec86c6632 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -50,6 +50,9 @@ private: // write_json(data[, styled]) static int l_write_json(lua_State *L); + // get_tool_wear_after_use(uses[, initial_wear]) + static int l_get_tool_wear_after_use(lua_State *L); + // get_dig_params(groups, tool_capabilities[, wear]) static int l_get_dig_params(lua_State *L); @@ -129,6 +132,4 @@ public: static void Initialize(lua_State *L, int top); static void InitializeAsync(lua_State *L, int top); static void InitializeClient(lua_State *L, int top); - - static void InitializeAsync(AsyncEngine &engine); }; diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index e040e545b..6187a47db 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -17,11 +17,12 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - +#include <map> #include "lua_api/l_vmanip.h" #include "lua_api/l_internal.h" #include "common/c_content.h" #include "common/c_converter.h" +#include "common/c_packer.h" #include "emerge.h" #include "environment.h" #include "map.h" @@ -45,6 +46,8 @@ int LuaVoxelManip::l_read_from_map(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); MMVManip *vm = o->vm; + if (vm->isOrphan()) + return 0; v3s16 bp1 = getNodeBlockPos(check_v3s16(L, 2)); v3s16 bp2 = getNodeBlockPos(check_v3s16(L, 3)); @@ -112,23 +115,23 @@ int LuaVoxelManip::l_write_to_map(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); bool update_light = !lua_isboolean(L, 2) || readParam<bool>(L, 2); + GET_ENV_PTR; ServerMap *map = &(env->getServerMap()); + + std::map<v3s16, MapBlock*> modified_blocks; if (o->is_mapgen_vm || !update_light) { - o->vm->blitBackAll(&(o->modified_blocks)); + o->vm->blitBackAll(&modified_blocks); } else { - voxalgo::blit_back_with_light(map, o->vm, - &(o->modified_blocks)); + voxalgo::blit_back_with_light(map, o->vm, &modified_blocks); } MapEditEvent event; event.type = MEET_OTHER; - for (const auto &modified_block : o->modified_blocks) - event.modified_blocks.insert(modified_block.first); - + for (const auto &it : modified_blocks) + event.modified_blocks.insert(it.first); map->dispatchEvent(event); - o->modified_blocks.clear(); return 0; } @@ -166,7 +169,7 @@ int LuaVoxelManip::l_update_liquids(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); - Map *map = &(env->getMap()); + ServerMap *map = &(env->getServerMap()); const NodeDefManager *ndef = getServer(L)->getNodeDefManager(); MMVManip *vm = o->vm; @@ -429,6 +432,34 @@ LuaVoxelManip *LuaVoxelManip::checkobject(lua_State *L, int narg) return *(LuaVoxelManip **)ud; // unbox pointer } +void *LuaVoxelManip::packIn(lua_State *L, int idx) +{ + LuaVoxelManip *o = checkobject(L, idx); + + if (o->is_mapgen_vm) + throw LuaError("nope"); + return o->vm->clone(); +} + +void LuaVoxelManip::packOut(lua_State *L, void *ptr) +{ + MMVManip *vm = reinterpret_cast<MMVManip*>(ptr); + if (!L) { + delete vm; + return; + } + + // Associate vmanip with map if the Lua env has one + Environment *env = getEnv(L); + if (env) + vm->reparent(&(env->getMap())); + + LuaVoxelManip *o = new LuaVoxelManip(vm, false); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); +} + void LuaVoxelManip::Register(lua_State *L) { lua_newtable(L); @@ -455,6 +486,8 @@ void LuaVoxelManip::Register(lua_State *L) // Can be created from Lua (VoxelManip()) lua_register(L, className, create_object); + + script_register_packer(L, className, packIn, packOut); } const char LuaVoxelManip::className[] = "VoxelManip"; diff --git a/src/script/lua_api/l_vmanip.h b/src/script/lua_api/l_vmanip.h index 15ab9eef8..005133335 100644 --- a/src/script/lua_api/l_vmanip.h +++ b/src/script/lua_api/l_vmanip.h @@ -19,7 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include <map> #include "irr_v3d.h" #include "lua_api/l_base.h" @@ -33,7 +32,6 @@ class MMVManip; class LuaVoxelManip : public ModApiBase { private: - std::map<v3s16, MapBlock *> modified_blocks; bool is_mapgen_vm = false; static const char className[]; @@ -77,5 +75,8 @@ public: static LuaVoxelManip *checkobject(lua_State *L, int narg); + static void *packIn(lua_State *L, int idx); + static void packOut(lua_State *L, void *ptr); + static void Register(lua_State *L); }; diff --git a/src/script/scripting_client.cpp b/src/script/scripting_client.cpp index 6643a9509..377205379 100644 --- a/src/script/scripting_client.cpp +++ b/src/script/scripting_client.cpp @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_nodemeta.h" #include "lua_api/l_localplayer.h" #include "lua_api/l_camera.h" +#include "lua_api/l_settings.h" ClientScripting::ClientScripting(Client *client): ScriptApiBase(ScriptingType::Client) @@ -73,6 +74,7 @@ void ClientScripting::InitializeModApi(lua_State *L, int top) LuaLocalPlayer::Register(L); LuaCamera::Register(L); ModChannelRef::Register(L); + LuaSettings::Register(L); ModApiUtil::InitializeClient(L, top); ModApiClient::Initialize(L, top); diff --git a/src/script/scripting_server.cpp b/src/script/scripting_server.cpp index 85411ded4..b462141b0 100644 --- a/src/script/scripting_server.cpp +++ b/src/script/scripting_server.cpp @@ -47,11 +47,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_storage.h" extern "C" { -#include "lualib.h" +#include <lualib.h> } ServerScripting::ServerScripting(Server* server): - ScriptApiBase(ScriptingType::Server) + ScriptApiBase(ScriptingType::Server), + asyncEngine(server) { setGameDef(server); @@ -88,6 +89,48 @@ ServerScripting::ServerScripting(Server* server): infostream << "SCRIPTAPI: Initialized game modules" << std::endl; } +void ServerScripting::initAsync() +{ + // Save globals to transfer + { + lua_State *L = getStack(); + lua_getglobal(L, "core"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_getfield(L, -1, "get_globals_to_transfer"); + lua_call(L, 0, 1); + auto *data = script_pack(L, -1); + assert(!data->contains_userdata); + getServer()->m_async_globals_data.reset(data); + lua_pushnil(L); + lua_setfield(L, -3, "get_globals_to_transfer"); // unset function too + lua_pop(L, 2); // pop 'core', return value + } + + infostream << "SCRIPTAPI: Initializing async engine" << std::endl; + asyncEngine.registerStateInitializer(InitializeAsync); + asyncEngine.registerStateInitializer(ModApiUtil::InitializeAsync); + asyncEngine.registerStateInitializer(ModApiCraft::InitializeAsync); + asyncEngine.registerStateInitializer(ModApiItemMod::InitializeAsync); + asyncEngine.registerStateInitializer(ModApiServer::InitializeAsync); + // not added: ModApiMapgen is a minefield for thread safety + // not added: ModApiHttp async api can't really work together with our jobs + // not added: ModApiStorage is probably not thread safe(?) + + asyncEngine.initialize(0); +} + +void ServerScripting::stepAsync() +{ + asyncEngine.step(getStack()); +} + +u32 ServerScripting::queueAsync(std::string &&serialized_func, + PackedValue *param, const std::string &mod_origin) +{ + return asyncEngine.queueAsyncJob(std::move(serialized_func), + param, mod_origin); +} + void ServerScripting::InitializeModApi(lua_State *L, int top) { // Register reference classes (userdata) @@ -125,3 +168,24 @@ void ServerScripting::InitializeModApi(lua_State *L, int top) ModApiStorage::Initialize(L, top); ModApiChannels::Initialize(L, top); } + +void ServerScripting::InitializeAsync(lua_State *L, int top) +{ + // classes + LuaItemStack::Register(L); + LuaPerlinNoise::Register(L); + LuaPerlinNoiseMap::Register(L); + LuaPseudoRandom::Register(L); + LuaPcgRandom::Register(L); + LuaSecureRandom::Register(L); + LuaVoxelManip::Register(L); + LuaSettings::Register(L); + + // globals data + lua_getglobal(L, "core"); + luaL_checktype(L, -1, LUA_TTABLE); + auto *data = ModApiBase::getServer(L)->m_async_globals_data.get(); + script_unpack(L, data); + lua_setfield(L, -2, "transferred_globals"); + lua_pop(L, 1); // pop 'core' +} diff --git a/src/script/scripting_server.h b/src/script/scripting_server.h index bf06ab197..9803397c5 100644 --- a/src/script/scripting_server.h +++ b/src/script/scripting_server.h @@ -27,6 +27,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_player.h" #include "cpp_api/s_server.h" #include "cpp_api/s_security.h" +#include "cpp_api/s_async.h" + +struct PackedValue; /*****************************************************************************/ /* Scripting <-> Server Game Interface */ @@ -48,6 +51,20 @@ public: // use ScriptApiBase::loadMod() to load mods + // Initialize async engine, call this AFTER loading all mods + void initAsync(); + + // Global step handler to collect async results + void stepAsync(); + + // Pass job to async threads + u32 queueAsync(std::string &&serialized_func, + PackedValue *param, const std::string &mod_origin); + private: void InitializeModApi(lua_State *L, int top); + + static void InitializeAsync(lua_State *L, int top); + + AsyncEngine asyncEngine; }; |