summaryrefslogtreecommitdiff
path: root/src/script
diff options
context:
space:
mode:
Diffstat (limited to 'src/script')
-rw-r--r--src/script/common/CMakeLists.txt1
-rw-r--r--src/script/common/c_content.cpp176
-rw-r--r--src/script/common/c_content.h19
-rw-r--r--src/script/common/c_converter.cpp99
-rw-r--r--src/script/common/c_converter.h21
-rw-r--r--src/script/common/c_internal.cpp14
-rw-r--r--src/script/common/c_internal.h6
-rw-r--r--src/script/common/c_packer.cpp596
-rw-r--r--src/script/common/c_packer.h126
-rw-r--r--src/script/common/helper.cpp80
-rw-r--r--src/script/common/helper.h3
-rw-r--r--src/script/cpp_api/s_async.cpp183
-rw-r--r--src/script/cpp_api/s_async.h54
-rw-r--r--src/script/cpp_api/s_base.cpp8
-rw-r--r--src/script/cpp_api/s_client.cpp10
-rw-r--r--src/script/cpp_api/s_entity.cpp13
-rw-r--r--src/script/cpp_api/s_entity.h2
-rw-r--r--src/script/cpp_api/s_env.cpp4
-rw-r--r--src/script/cpp_api/s_security.cpp14
-rw-r--r--src/script/lua_api/l_areastore.cpp49
-rw-r--r--src/script/lua_api/l_client.cpp26
-rw-r--r--src/script/lua_api/l_craft.cpp32
-rw-r--r--src/script/lua_api/l_craft.h1
-rw-r--r--src/script/lua_api/l_env.cpp11
-rw-r--r--src/script/lua_api/l_env.h2
-rw-r--r--src/script/lua_api/l_internal.h2
-rw-r--r--src/script/lua_api/l_inventory.cpp23
-rw-r--r--src/script/lua_api/l_item.cpp56
-rw-r--r--src/script/lua_api/l_item.h18
-rw-r--r--src/script/lua_api/l_mainmenu.cpp142
-rw-r--r--src/script/lua_api/l_mainmenu.h4
-rw-r--r--src/script/lua_api/l_nodemeta.cpp27
-rw-r--r--src/script/lua_api/l_nodemeta.h6
-rw-r--r--src/script/lua_api/l_noise.cpp67
-rw-r--r--src/script/lua_api/l_noise.h17
-rw-r--r--src/script/lua_api/l_object.cpp214
-rw-r--r--src/script/lua_api/l_object.h12
-rw-r--r--src/script/lua_api/l_particleparams.h282
-rw-r--r--src/script/lua_api/l_particles.cpp214
-rw-r--r--src/script/lua_api/l_particles_local.cpp114
-rw-r--r--src/script/lua_api/l_server.cpp131
-rw-r--r--src/script/lua_api/l_server.h10
-rw-r--r--src/script/lua_api/l_settings.cpp21
-rw-r--r--src/script/lua_api/l_sound.cpp4
-rw-r--r--src/script/lua_api/l_storage.cpp8
-rw-r--r--src/script/lua_api/l_util.cpp22
-rw-r--r--src/script/lua_api/l_util.h5
-rw-r--r--src/script/lua_api/l_vmanip.cpp51
-rw-r--r--src/script/lua_api/l_vmanip.h5
-rw-r--r--src/script/scripting_client.cpp2
-rw-r--r--src/script/scripting_server.cpp68
-rw-r--r--src/script/scripting_server.h17
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 &params)
+ ServerPlayingSound &params)
{
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 &params);
+ ServerPlayingSound &params);
void push_dig_params (lua_State *L,
const DigParams &params);
@@ -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 &&params,
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 &&params,
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 &params)
+{
+ 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;
};