From 2efae3ffd720095222c800e016286a45c9fe1e5c Mon Sep 17 00:00:00 2001 From: Loic Blot Date: Sat, 21 Jan 2017 15:02:08 +0100 Subject: [CSM] Client side modding * rename GameScripting to ServerScripting * Make getBuiltinLuaPath static serverside * Add on_shutdown callback * Add on_receiving_chat_message & on_sending_chat_message callbacks * ScriptApiBase: use IGameDef instead of Server This permits to share common attribute between client & server * Enable mod security in client side modding without conditions --- builtin/client/init.lua | 22 ++++++++++++++++ builtin/client/register.lua | 62 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 builtin/client/init.lua create mode 100644 builtin/client/register.lua (limited to 'builtin/client') diff --git a/builtin/client/init.lua b/builtin/client/init.lua new file mode 100644 index 000000000..d14301ade --- /dev/null +++ b/builtin/client/init.lua @@ -0,0 +1,22 @@ +-- Minetest: builtin/client/init.lua +local scriptpath = core.get_builtin_path()..DIR_DELIM +local clientpath = scriptpath.."client"..DIR_DELIM + +dofile(clientpath .. "register.lua") + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_shutdown(function() + print("shutdown client") +end) + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_receiving_chat_messages(function(message) + print("Received message " .. message) + return false +end) + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_sending_chat_messages(function(message) + print("Sending message " .. message) + return false +end) diff --git a/builtin/client/register.lua b/builtin/client/register.lua new file mode 100644 index 000000000..c793195a1 --- /dev/null +++ b/builtin/client/register.lua @@ -0,0 +1,62 @@ + +core.callback_origins = {} + +function core.run_callbacks(callbacks, mode, ...) + assert(type(callbacks) == "table") + local cb_len = #callbacks + if cb_len == 0 then + if mode == 2 or mode == 3 then + return true + elseif mode == 4 or mode == 5 then + return false + end + end + local ret + for i = 1, cb_len do + local cb_ret = callbacks[i](...) + + if mode == 0 and i == 1 or mode == 1 and i == cb_len then + ret = cb_ret + elseif mode == 2 then + if not cb_ret or i == 1 then + ret = cb_ret + end + elseif mode == 3 then + if cb_ret then + return cb_ret + end + ret = cb_ret + elseif mode == 4 then + if (cb_ret and not ret) or i == 1 then + ret = cb_ret + end + elseif mode == 5 and cb_ret then + return cb_ret + end + end + return ret +end + +-- +-- Callback registration +-- + +local function make_registration() + local t = {} + local registerfunc = function(func) + t[#t + 1] = func + core.callback_origins[func] = { + mod = core.get_current_modname() or "??", + name = debug.getinfo(1, "n").name or "??" + } + --local origin = core.callback_origins[func] + --print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func)) + end + return t, registerfunc +end + +core.registered_on_shutdown, core.register_on_shutdown = make_registration() +core.registered_on_receiving_chat_messages, core.register_on_receiving_chat_messages = make_registration() +core.registered_on_sending_chat_messages, core.register_on_sending_chat_messages = make_registration() + + -- cgit v1.2.3 From 9978f5af828550d819890fed1fc56d65838a2c4c Mon Sep 17 00:00:00 2001 From: Loïc Blot Date: Sun, 22 Jan 2017 00:20:55 +0100 Subject: [CSM] Add on_death, on_hp_modification & oh_damage_taken callbacks (#5093) * Add on_death callback * Add on_hp_modification & on_damage_taken callbacks * move preview code to preview.lua --- builtin/client/init.lua | 17 +++-------------- builtin/client/preview.lua | 24 ++++++++++++++++++++++++ builtin/client/register.lua | 3 +++ src/client.h | 2 ++ src/game.cpp | 7 +++---- src/network/clientpackethandler.cpp | 2 ++ src/script/cpp_api/s_client.cpp | 35 +++++++++++++++++++++++++++++++++++ src/script/cpp_api/s_client.h | 4 ++++ 8 files changed, 76 insertions(+), 18 deletions(-) create mode 100644 builtin/client/preview.lua (limited to 'builtin/client') diff --git a/builtin/client/init.lua b/builtin/client/init.lua index d14301ade..e06dfc995 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -3,20 +3,9 @@ local scriptpath = core.get_builtin_path()..DIR_DELIM local clientpath = scriptpath.."client"..DIR_DELIM dofile(clientpath .. "register.lua") +dofile(clientpath .. "preview.lua") --- This is an example function to ensure it's working properly, should be removed before merge -core.register_on_shutdown(function() - print("shutdown client") +core.register_on_death(function() + core.display_chat_message("You died.") end) --- This is an example function to ensure it's working properly, should be removed before merge -core.register_on_receiving_chat_messages(function(message) - print("Received message " .. message) - return false -end) - --- This is an example function to ensure it's working properly, should be removed before merge -core.register_on_sending_chat_messages(function(message) - print("Sending message " .. message) - return false -end) diff --git a/builtin/client/preview.lua b/builtin/client/preview.lua new file mode 100644 index 000000000..4b277b0c6 --- /dev/null +++ b/builtin/client/preview.lua @@ -0,0 +1,24 @@ +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_shutdown(function() + print("shutdown client") +end) + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_receiving_chat_messages(function(message) + print("[PREVIEW] Received message " .. message) + return false +end) + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_sending_chat_messages(function(message) + print("[PREVIEW] Sending message " .. message) + return false +end) + +core.register_on_hp_modification(function(hp) + print("[PREVIEW] HP modified " .. hp) +end) + +core.register_on_damage_taken(function(hp) + print("[PREVIEW] Damage taken " .. hp) +end) diff --git a/builtin/client/register.lua b/builtin/client/register.lua index c793195a1..ddaf4f424 100644 --- a/builtin/client/register.lua +++ b/builtin/client/register.lua @@ -58,5 +58,8 @@ end core.registered_on_shutdown, core.register_on_shutdown = make_registration() core.registered_on_receiving_chat_messages, core.register_on_receiving_chat_messages = make_registration() core.registered_on_sending_chat_messages, core.register_on_sending_chat_messages = make_registration() +core.registered_on_death, core.register_on_death = make_registration() +core.registered_on_hp_modification, core.register_on_hp_modification = make_registration() +core.registered_on_damage_taken, core.register_on_damage_taken = make_registration() diff --git a/src/client.h b/src/client.h index 584b13a90..21aeb0575 100644 --- a/src/client.h +++ b/src/client.h @@ -562,6 +562,8 @@ public: m_chat_queue.push(input); } + ClientScripting *getScript() { return m_script; } + private: // Virtual methods from con::PeerHandler diff --git a/src/game.cpp b/src/game.cpp index 9868142f7..2e2a8e0c1 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -41,7 +41,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiKeyChangeMenu.h" #include "guiPasswordChange.h" #include "guiVolumeChange.h" -#include "hud.h" #include "mainmenumanager.h" #include "mapblock.h" #include "nodedef.h" // Needed for determining pointing to nodes @@ -61,6 +60,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "version.h" #include "minimap.h" #include "mapblock_mesh.h" +#include "script/clientscripting.h" #include "sound.h" @@ -3240,8 +3240,7 @@ void Game::processClientEvents(CameraOrientation *cam, float *damage_flash) if (event.type == CE_PLAYER_DAMAGE && client->getHP() != 0) { - //u16 damage = event.player_damage.amount; - //infostream<<"Player damage: "<getScript()->on_damage_taken(event.player_damage.amount); *damage_flash += 95.0 + 3.2 * event.player_damage.amount; *damage_flash = MYMIN(*damage_flash, 127.0); @@ -3259,7 +3258,7 @@ void Game::processClientEvents(CameraOrientation *cam, float *damage_flash) show_deathscreen(¤t_formspec, client, texture_src, device, &input->joystick); - chat_backend->addMessage(L"", L"You died."); + client->getScript()->on_death(); /* Handle visualization */ *damage_flash = 0; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index d0642c86a..97fa93e95 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -526,6 +526,8 @@ void Client::handleCommand_HP(NetworkPacket* pkt) player->hp = hp; + m_script->on_hp_modification(hp); + if (hp < oldhp) { // Add to ClientEvent queue ClientEvent event; diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index 08af8ebdc..f0676f4c2 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -59,3 +59,38 @@ bool ScriptApiClient::on_receiving_message(const std::string &message) bool ate = lua_toboolean(L, -1); return ate; } + +void ScriptApiClient::on_damage_taken(int32_t damage_amount) +{ + SCRIPTAPI_PRECHECKHEADER + + // Get core.registered_on_chat_messages + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_damage_taken"); + // Call callbacks + lua_pushinteger(L, damage_amount); + runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); +} + +void ScriptApiClient::on_hp_modification(int32_t newhp) +{ + SCRIPTAPI_PRECHECKHEADER + + // Get core.registered_on_chat_messages + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_hp_modification"); + // Call callbacks + lua_pushinteger(L, newhp); + runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); +} + +void ScriptApiClient::on_death() +{ + SCRIPTAPI_PRECHECKHEADER + + // Get registered shutdown hooks + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_death"); + // Call callbacks + runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); +} diff --git a/src/script/cpp_api/s_client.h b/src/script/cpp_api/s_client.h index 08fdd8fc0..3155c7e67 100644 --- a/src/script/cpp_api/s_client.h +++ b/src/script/cpp_api/s_client.h @@ -32,5 +32,9 @@ public: // Chat message handlers bool on_sending_message(const std::string &message); bool on_receiving_message(const std::string &message); + + void on_damage_taken(int32_t damage_amount); + void on_hp_modification(int32_t newhp); + void on_death(); }; #endif -- cgit v1.2.3 From d7bc346981e189851e490f2417ed015a38bca79b Mon Sep 17 00:00:00 2001 From: red-001 Date: Sun, 22 Jan 2017 08:05:09 +0000 Subject: [CSM] Add client-sided chat commands (#5092) --- builtin/client/chatcommands.lua | 28 ++++++++++++++++++++++++++++ builtin/client/init.lua | 1 + builtin/client/preview.lua | 7 +++++++ builtin/common/chatcommands.lua | 30 ++++++++++++++++++++++++++++++ builtin/game/chatcommands.lua | 29 +---------------------------- builtin/game/init.lua | 1 + src/client.cpp | 6 +----- src/script/lua_api/l_client.cpp | 23 +++++++++++++++++++++++ src/script/lua_api/l_client.h | 8 ++++++++ 9 files changed, 100 insertions(+), 33 deletions(-) create mode 100644 builtin/client/chatcommands.lua create mode 100644 builtin/common/chatcommands.lua (limited to 'builtin/client') diff --git a/builtin/client/chatcommands.lua b/builtin/client/chatcommands.lua new file mode 100644 index 000000000..b49c222ef --- /dev/null +++ b/builtin/client/chatcommands.lua @@ -0,0 +1,28 @@ +-- Minetest: builtin/client/chatcommands.lua + + +core.register_on_sending_chat_messages(function(message) + if not (message:sub(1,1) == "/") then + return false + end + + core.display_chat_message("issued command: " .. message) + + local cmd, param = string.match(message, "^/([^ ]+) *(.*)") + if not param then + param = "" + end + + local cmd_def = core.registered_chatcommands[cmd] + + if cmd_def then + core.set_last_run_mod(cmd_def.mod_origin) + local success, message = cmd_def.func(param) + if message then + core.display_chat_message(message) + end + return true + end + + return false +end) \ No newline at end of file diff --git a/builtin/client/init.lua b/builtin/client/init.lua index e06dfc995..4797ac4b6 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -1,6 +1,7 @@ -- Minetest: builtin/client/init.lua local scriptpath = core.get_builtin_path()..DIR_DELIM local clientpath = scriptpath.."client"..DIR_DELIM +local commonpath = scriptpath.."common"..DIR_DELIM dofile(clientpath .. "register.lua") dofile(clientpath .. "preview.lua") diff --git a/builtin/client/preview.lua b/builtin/client/preview.lua index 4b277b0c6..c421791f5 100644 --- a/builtin/client/preview.lua +++ b/builtin/client/preview.lua @@ -22,3 +22,10 @@ end) core.register_on_damage_taken(function(hp) print("[PREVIEW] Damage taken " .. hp) end) + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_chatcommand("dump", { + func = function(name, param) + return true, dump(_G) + end, +}) \ No newline at end of file diff --git a/builtin/common/chatcommands.lua b/builtin/common/chatcommands.lua new file mode 100644 index 000000000..ef3a24410 --- /dev/null +++ b/builtin/common/chatcommands.lua @@ -0,0 +1,30 @@ +-- Minetest: builtin/common/chatcommands.lua + +core.registered_chatcommands = {} + +function core.register_chatcommand(cmd, def) + def = def or {} + def.params = def.params or "" + def.description = def.description or "" + def.privs = def.privs or {} + def.mod_origin = core.get_current_modname() or "??" + core.registered_chatcommands[cmd] = def +end + +function core.unregister_chatcommand(name) + if core.registered_chatcommands[name] then + core.registered_chatcommands[name] = nil + else + core.log("warning", "Not unregistering chatcommand " ..name.. + " because it doesn't exist.") + end +end + +function core.override_chatcommand(name, redefinition) + local chatcommand = core.registered_chatcommands[name] + assert(chatcommand, "Attempt to override non-existent chatcommand "..name) + for k, v in pairs(redefinition) do + rawset(chatcommand, k, v) + end + core.registered_chatcommands[name] = chatcommand +end \ No newline at end of file diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index 5d5955972..745b012e6 100644 --- a/builtin/game/chatcommands.lua +++ b/builtin/game/chatcommands.lua @@ -1,37 +1,10 @@ --- Minetest: builtin/chatcommands.lua +-- Minetest: builtin/game/chatcommands.lua -- -- Chat command handler -- -core.registered_chatcommands = {} core.chatcommands = core.registered_chatcommands -- BACKWARDS COMPATIBILITY -function core.register_chatcommand(cmd, def) - def = def or {} - def.params = def.params or "" - def.description = def.description or "" - def.privs = def.privs or {} - def.mod_origin = core.get_current_modname() or "??" - core.registered_chatcommands[cmd] = def -end - -function core.unregister_chatcommand(name) - if core.registered_chatcommands[name] then - core.registered_chatcommands[name] = nil - else - core.log("warning", "Not unregistering chatcommand " ..name.. - " because it doesn't exist.") - end -end - -function core.override_chatcommand(name, redefinition) - local chatcommand = core.registered_chatcommands[name] - assert(chatcommand, "Attempt to override non-existent chatcommand "..name) - for k, v in pairs(redefinition) do - rawset(chatcommand, k, v) - end - core.registered_chatcommands[name] = chatcommand -end core.register_on_chat_message(function(name, message) local cmd, param = string.match(message, "^/([^ ]+) *(.*)") diff --git a/builtin/game/init.lua b/builtin/game/init.lua index b5e2f7cca..793d9fe2b 100644 --- a/builtin/game/init.lua +++ b/builtin/game/init.lua @@ -22,6 +22,7 @@ dofile(gamepath.."deprecated.lua") dofile(gamepath.."misc.lua") dofile(gamepath.."privileges.lua") dofile(gamepath.."auth.lua") +dofile(commonpath .. "chatcommands.lua") dofile(gamepath.."chatcommands.lua") dofile(gamepath.."static_spawn.lua") dofile(gamepath.."detached_inventory.lua") diff --git a/src/client.cpp b/src/client.cpp index 0af6b4595..3a3e0673e 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1582,11 +1582,7 @@ void Client::typeChatMessage(const std::wstring &message) sendChatMessage(message); // Show locally - if (message[0] == L'/') - { - pushToChatQueue((std::wstring)L"issued command: " + message); - } - else + if (message[0] != L'/') { // compatibility code if (m_proto_ver < 29) { diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index f4c3812ac..7eb340d78 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "l_client.h" #include "l_internal.h" #include "util/string.h" +#include "cpp_api/s_base.h" int ModApiClient::l_get_current_modname(lua_State *L) { @@ -28,6 +29,26 @@ int ModApiClient::l_get_current_modname(lua_State *L) return 1; } +// get_last_run_mod() +int ModApiClient::l_get_last_run_mod(lua_State *L) +{ + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + const char *current_mod = lua_tostring(L, -1); + if (current_mod == NULL || current_mod[0] == '\0') { + lua_pop(L, 1); + lua_pushstring(L, getScriptApiBase(L)->getOrigin().c_str()); + } + return 1; +} + +// set_last_run_mod(modname) +int ModApiClient::l_set_last_run_mod(lua_State *L) +{ + const char *mod = lua_tostring(L, 1); + getScriptApiBase(L)->setOriginDirect(mod); + return 0; +} + // display_chat_message(message) int ModApiClient::l_display_chat_message(lua_State *L) { @@ -42,4 +63,6 @@ void ModApiClient::Initialize(lua_State *L, int top) { API_FCT(get_current_modname); API_FCT(display_chat_message); + API_FCT(set_last_run_mod); + API_FCT(get_last_run_mod); } diff --git a/src/script/lua_api/l_client.h b/src/script/lua_api/l_client.h index b4a57cb61..e3106e742 100644 --- a/src/script/lua_api/l_client.h +++ b/src/script/lua_api/l_client.h @@ -28,8 +28,16 @@ class ModApiClient : public ModApiBase private: // get_current_modname() static int l_get_current_modname(lua_State *L); + + // display_chat_message(message) static int l_display_chat_message(lua_State *L); + // get_last_run_mod(n) + static int l_get_last_run_mod(lua_State *L); + + // set_last_run_mod(modname) + static int l_set_last_run_mod(lua_State *L); + public: static void Initialize(lua_State *L, int top); }; -- cgit v1.2.3 From 2c19d51409ca903021e0b508e5bc15299c4e51dc Mon Sep 17 00:00:00 2001 From: Loïc Blot Date: Sun, 22 Jan 2017 11:17:41 +0100 Subject: [CSM] sound_play & sound_stop support + client_lua_api doc (#5096) * squashed: CSM: Implement register_globalstep * Re-use fatal error mechanism from server to disconnect client on CSM error * Little client functions cleanups * squashed: CSM: add core.after function * core.after is shared code between client & server * ModApiUtil get_us_time feature enabled for client --- builtin/client/init.lua | 3 +- builtin/client/preview.lua | 15 +- builtin/client/register.lua | 1 + builtin/common/after.lua | 43 +++ builtin/game/init.lua | 1 + builtin/game/misc.lua | 44 --- doc/client_lua_api.txt | 787 ++++++++++++++++++++++++++++++++++++++ src/client.cpp | 1 + src/client.h | 16 +- src/clientenvironment.cpp | 4 + src/clientenvironment.h | 3 + src/guiEngine.h | 1 + src/script/clientscripting.cpp | 2 + src/script/cpp_api/s_client.cpp | 18 + src/script/cpp_api/s_client.h | 2 + src/script/lua_api/CMakeLists.txt | 1 + src/script/lua_api/l_client.h | 3 +- src/script/lua_api/l_mainmenu.cpp | 31 -- src/script/lua_api/l_mainmenu.h | 7 +- src/script/lua_api/l_sound.cpp | 62 +++ src/script/lua_api/l_sound.h | 36 ++ src/script/lua_api/l_util.cpp | 2 + src/script/scripting_mainmenu.cpp | 4 +- 23 files changed, 996 insertions(+), 91 deletions(-) create mode 100644 builtin/common/after.lua create mode 100644 doc/client_lua_api.txt create mode 100644 src/script/lua_api/l_sound.cpp create mode 100644 src/script/lua_api/l_sound.h (limited to 'builtin/client') diff --git a/builtin/client/init.lua b/builtin/client/init.lua index 4797ac4b6..dd218aab6 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -4,9 +4,10 @@ local clientpath = scriptpath.."client"..DIR_DELIM local commonpath = scriptpath.."common"..DIR_DELIM dofile(clientpath .. "register.lua") +dofile(commonpath .. "after.lua") +dofile(commonpath .. "chatcommands.lua") dofile(clientpath .. "preview.lua") core.register_on_death(function() core.display_chat_message("You died.") end) - diff --git a/builtin/client/preview.lua b/builtin/client/preview.lua index c421791f5..22e8bb97f 100644 --- a/builtin/client/preview.lua +++ b/builtin/client/preview.lua @@ -1,6 +1,6 @@ -- This is an example function to ensure it's working properly, should be removed before merge core.register_on_shutdown(function() - print("shutdown client") + print("[PREVIEW] shutdown client") end) -- This is an example function to ensure it's working properly, should be removed before merge @@ -15,17 +15,28 @@ core.register_on_sending_chat_messages(function(message) return false end) +-- This is an example function to ensure it's working properly, should be removed before merge core.register_on_hp_modification(function(hp) print("[PREVIEW] HP modified " .. hp) end) +-- This is an example function to ensure it's working properly, should be removed before merge core.register_on_damage_taken(function(hp) print("[PREVIEW] Damage taken " .. hp) end) +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_globalstep(function(dtime) + -- print("[PREVIEW] globalstep " .. dtime) +end) + -- This is an example function to ensure it's working properly, should be removed before merge core.register_chatcommand("dump", { func = function(name, param) return true, dump(_G) end, -}) \ No newline at end of file +}) + +core.after(2, function() + print("After 2") +end) diff --git a/builtin/client/register.lua b/builtin/client/register.lua index ddaf4f424..8b60c1222 100644 --- a/builtin/client/register.lua +++ b/builtin/client/register.lua @@ -55,6 +55,7 @@ local function make_registration() return t, registerfunc end +core.registered_globalsteps, core.register_globalstep = make_registration() core.registered_on_shutdown, core.register_on_shutdown = make_registration() core.registered_on_receiving_chat_messages, core.register_on_receiving_chat_messages = make_registration() core.registered_on_sending_chat_messages, core.register_on_sending_chat_messages = make_registration() diff --git a/builtin/common/after.lua b/builtin/common/after.lua new file mode 100644 index 000000000..30a9c7bad --- /dev/null +++ b/builtin/common/after.lua @@ -0,0 +1,43 @@ +local jobs = {} +local time = 0.0 +local last = core.get_us_time() / 1000000 + +core.register_globalstep(function(dtime) + local new = core.get_us_time() / 1000000 + if new > last then + time = time + (new - last) + else + -- Overflow, we may lose a little bit of time here but + -- only 1 tick max, potentially running timers slightly + -- too early. + time = time + new + end + last = new + + if #jobs < 1 then + return + end + + -- Iterate backwards so that we miss any new timers added by + -- a timer callback, and so that we don't skip the next timer + -- in the list if we remove one. + for i = #jobs, 1, -1 do + local job = jobs[i] + if time >= job.expire then + core.set_last_run_mod(job.mod_origin) + job.func(unpack(job.arg)) + table.remove(jobs, i) + end + end +end) + +function core.after(after, func, ...) + assert(tonumber(after) and type(func) == "function", + "Invalid core.after invocation") + jobs[#jobs + 1] = { + func = func, + expire = time + after, + arg = {...}, + mod_origin = core.get_last_run_mod() + } +end diff --git a/builtin/game/init.lua b/builtin/game/init.lua index 793d9fe2b..3e192a30a 100644 --- a/builtin/game/init.lua +++ b/builtin/game/init.lua @@ -17,6 +17,7 @@ if core.setting_getbool("profiler.load") then profiler = dofile(scriptpath.."profiler"..DIR_DELIM.."init.lua") end +dofile(commonpath .. "after.lua") dofile(gamepath.."item_entity.lua") dofile(gamepath.."deprecated.lua") dofile(gamepath.."misc.lua") diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index 3419c1980..25376c180 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -4,50 +4,6 @@ -- Misc. API functions -- -local jobs = {} -local time = 0.0 -local last = core.get_us_time() / 1000000 - -core.register_globalstep(function(dtime) - local new = core.get_us_time() / 1000000 - if new > last then - time = time + (new - last) - else - -- Overflow, we may lose a little bit of time here but - -- only 1 tick max, potentially running timers slightly - -- too early. - time = time + new - end - last = new - - if #jobs < 1 then - return - end - - -- Iterate backwards so that we miss any new timers added by - -- a timer callback, and so that we don't skip the next timer - -- in the list if we remove one. - for i = #jobs, 1, -1 do - local job = jobs[i] - if time >= job.expire then - core.set_last_run_mod(job.mod_origin) - job.func(unpack(job.arg)) - table.remove(jobs, i) - end - end -end) - -function core.after(after, func, ...) - assert(tonumber(after) and type(func) == "function", - "Invalid core.after invocation") - jobs[#jobs + 1] = { - func = func, - expire = time + after, - arg = {...}, - mod_origin = core.get_last_run_mod() - } -end - function core.check_player_privs(name, ...) local arg_type = type(name) if (arg_type == "userdata" or arg_type == "table") and diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt new file mode 100644 index 000000000..8ce487bf8 --- /dev/null +++ b/doc/client_lua_api.txt @@ -0,0 +1,787 @@ +Minetest Lua Modding API Reference 0.4.15 +========================================= +* More information at +* Developer Wiki: + +Introduction +------------ +Content and functionality can be added to Minetest 0.4 by using Lua +scripting in run-time loaded mods. + +A mod is a self-contained bunch of scripts, textures and other related +things that is loaded by and interfaces with Minetest. + +Mods are contained and ran solely on the server side. Definitions and media +files are automatically transferred to the client. + +If you see a deficiency in the API, feel free to attempt to add the +functionality in the engine and API. You can send such improvements as +source code patches on GitHub (https://github.com/minetest/minetest). + +Programming in Lua +------------------ +If you have any difficulty in understanding this, please read +[Programming in Lua](http://www.lua.org/pil/). + +Startup +------- +Mods are loaded during client startup from the mod load paths by running +the `init.lua` scripts in a shared environment. + +Paths +----- +* `RUN_IN_PLACE=1` (Windows release, local build) + * `$path_user`: + * Linux: `` + * Windows: `` + * `$path_share` + * Linux: `` + * Windows: `` +* `RUN_IN_PLACE=0`: (Linux release) + * `$path_share` + * Linux: `/usr/share/minetest` + * Windows: `/minetest-0.4.x` + * `$path_user`: + * Linux: `$HOME/.minetest` + * Windows: `C:/users//AppData/minetest` (maybe) + +Mod load path +------------- +Generic: + +* `$path_share/clientmods/` +* `$path_user/clientmods/` (User-installed mods) + +In a run-in-place version (e.g. the distributed windows version): + +* `minetest-0.4.x/clientmods/` (User-installed mods) + +On an installed version on Linux: + +* `/usr/share/minetest/clientmods/` +* `$HOME/.minetest/clientmods/` (User-installed mods) + +Modpack support +---------------- +Mods can be put in a subdirectory, if the parent directory, which otherwise +should be a mod, contains a file named `modpack.txt`. This file shall be +empty, except for lines starting with `#`, which are comments. + +Mod directory structure +------------------------ + + mods + |-- modname + | |-- depends.txt + | |-- screenshot.png + | |-- description.txt + | |-- settingtypes.txt + | |-- init.lua + | |-- models + | |-- textures + | | |-- modname_stuff.png + | | `-- modname_something_else.png + | |-- sounds + | |-- media + | `-- + `-- another + + +### modname +The location of this directory can be fetched by using +`minetest.get_modpath(modname)`. + +### `depends.txt` +List of mods that have to be loaded before loading this mod. + +A single line contains a single modname. + +Optional dependencies can be defined by appending a question mark +to a single modname. Their meaning is that if the specified mod +is missing, that does not prevent this mod from being loaded. + +### `screenshot.png` +A screenshot shown in the mod manager within the main menu. It should +have an aspect ratio of 3:2 and a minimum size of 300×200 pixels. + +### `description.txt` +A File containing description to be shown within mainmenu. + +### `settingtypes.txt` +A file in the same format as the one in builtin. It will be parsed by the +settings menu and the settings will be displayed in the "Mods" category. + +### `init.lua` +The main Lua script. Running this script should register everything it +wants to register. Subsequent execution depends on minetest calling the +registered callbacks. + +`minetest.setting_get(name)` and `minetest.setting_getbool(name)` can be used +to read custom or existing settings at load time, if necessary. + +### `sounds` +Media files (sounds) that will be transferred to the +client and will be available for use by the mod. + +Naming convention for registered textual names +---------------------------------------------- +Registered names should generally be in this format: + + "modname:" ( can have characters a-zA-Z0-9_) + +This is to prevent conflicting names from corrupting maps and is +enforced by the mod loader. + +### Example +In the mod `experimental`, there is the ideal item/node/entity name `tnt`. +So the name should be `experimental:tnt`. + +Enforcement can be overridden by prefixing the name with `:`. This can +be used for overriding the registrations of some other mod. + +Example: Any mod can redefine `experimental:tnt` by using the name + + :experimental:tnt + +when registering it. +(also that mod is required to have `experimental` as a dependency) + +The `:` prefix can also be used for maintaining backwards compatibility. + +### Aliases +Aliases can be added by using `minetest.register_alias(name, convert_to)` or +`minetest.register_alias_force(name, convert_to). + +This will make Minetest to convert things called name to things called +`convert_to`. + +The only difference between `minetest.register_alias` and +`minetest.register_alias_force` is that if an item called `name` exists, +`minetest.register_alias` will do nothing while +`minetest.register_alias_force` will unregister it. + +This can be used for maintaining backwards compatibility. + +This can be also used for setting quick access names for things, e.g. if +you have an item called `epiclylongmodname:stuff`, you could do + + minetest.register_alias("stuff", "epiclylongmodname:stuff") + +and be able to use `/giveme stuff`. + +Sounds +------ +Only Ogg Vorbis files are supported. + +For positional playing of sounds, only single-channel (mono) files are +supported. Otherwise OpenAL will play them non-positionally. + +Mods should generally prefix their sounds with `modname_`, e.g. given +the mod name "`foomod`", a sound could be called: + + foomod_foosound.ogg + +Sounds are referred to by their name with a dot, a single digit and the +file extension stripped out. When a sound is played, the actual sound file +is chosen randomly from the matching sounds. + +When playing the sound `foomod_foosound`, the sound is chosen randomly +from the available ones of the following files: + +* `foomod_foosound.ogg` +* `foomod_foosound.0.ogg` +* `foomod_foosound.1.ogg` +* (...) +* `foomod_foosound.9.ogg` + +Examples of sound parameter tables: + + -- Play locationless on all clients + { + gain = 1.0, -- default + } + -- Play locationless to one player + { + to_player = name, + gain = 1.0, -- default + } + -- Play locationless to one player, looped + { + to_player = name, + gain = 1.0, -- default + loop = true, + } + -- Play in a location + { + pos = {x = 1, y = 2, z = 3}, + gain = 1.0, -- default + max_hear_distance = 32, -- default, uses an euclidean metric + } + -- Play connected to an object, looped + { + object = , + gain = 1.0, -- default + max_hear_distance = 32, -- default, uses an euclidean metric + loop = true, + } + +Looped sounds must either be connected to an object or played locationless to +one player using `to_player = name,` + +### `SimpleSoundSpec` +* e.g. `""` +* e.g. `"default_place_node"` +* e.g. `{}` +* e.g. `{name = "default_place_node"}` +* e.g. `{name = "default_place_node", gain = 1.0}` + +Representations of simple things +-------------------------------- + +### Position/vector + + {x=num, y=num, z=num} + +For helper functions see "Vector helpers". + +### `pointed_thing` +* `{type="nothing"}` +* `{type="node", under=pos, above=pos}` +* `{type="object", ref=ObjectRef}` + +Flag Specifier Format +--------------------- +Flags using the standardized flag specifier format can be specified in either of +two ways, by string or table. + +The string format is a comma-delimited set of flag names; whitespace and +unrecognized flag fields are ignored. Specifying a flag in the string sets the +flag, and specifying a flag prefixed by the string `"no"` explicitly +clears the flag from whatever the default may be. + +In addition to the standard string flag format, the schematic flags field can +also be a table of flag names to boolean values representing whether or not the +flag is set. Additionally, if a field with the flag name prefixed with `"no"` +is present, mapped to a boolean of any value, the specified flag is unset. + +E.g. A flag field of value + + {place_center_x = true, place_center_y=false, place_center_z=true} + +is equivalent to + + {place_center_x = true, noplace_center_y=true, place_center_z=true} + +which is equivalent to + + "place_center_x, noplace_center_y, place_center_z" + +or even + + "place_center_x, place_center_z" + +since, by default, no schematic attributes are set. + +Formspec +-------- +Formspec defines a menu. Currently not much else than inventories are +supported. It is a string, with a somewhat strange format. + +Spaces and newlines can be inserted between the blocks, as is used in the +examples. + +### Examples + +#### Chest + + size[8,9] + list[context;main;0,0;8,4;] + list[current_player;main;0,5;8,4;] + +#### Furnace + + size[8,9] + list[context;fuel;2,3;1,1;] + list[context;src;2,1;1,1;] + list[context;dst;5,1;2,2;] + list[current_player;main;0,5;8,4;] + +#### Minecraft-like player inventory + + size[8,7.5] + image[1,0.6;1,2;player.png] + list[current_player;main;0,3.5;8,4;] + list[current_player;craft;3,0;3,3;] + list[current_player;craftpreview;7,1;1,1;] + +### Elements + +#### `size[,,]` +* Define the size of the menu in inventory slots +* `fixed_size`: `true`/`false` (optional) +* deprecated: `invsize[,;]` + +#### `container[,]` +* Start of a container block, moves all physical elements in the container by (X, Y) +* Must have matching container_end +* Containers can be nested, in which case the offsets are added + (child containers are relative to parent containers) + +#### `container_end[]` +* End of a container, following elements are no longer relative to this container + +#### `list[;;,;,;]` +* Show an inventory list + +#### `list[;;,;,;]` +* Show an inventory list + +#### `listring[;]` +* Allows to create a ring of inventory lists +* Shift-clicking on items in one element of the ring + will send them to the next inventory list inside the ring +* The first occurrence of an element inside the ring will + determine the inventory where items will be sent to + +#### `listring[]` +* Shorthand for doing `listring[;]` + for the last two inventory lists added by list[...] + +#### `listcolors[;]` +* Sets background color of slots as `ColorString` +* Sets background color of slots on mouse hovering + +#### `listcolors[;;]` +* Sets background color of slots as `ColorString` +* Sets background color of slots on mouse hovering +* Sets color of slots border + +#### `listcolors[;;;;]` +* Sets background color of slots as `ColorString` +* Sets background color of slots on mouse hovering +* Sets color of slots border +* Sets default background color of tooltips +* Sets default font color of tooltips + +#### `tooltip[;;,]` +* Adds tooltip for an element +* `` tooltip background color as `ColorString` (optional) +* `` tooltip font color as `ColorString` (optional) + +#### `image[,;,;]` +* Show an image +* Position and size units are inventory slots + +#### `item_image[,;,;]` +* Show an inventory image of registered item/node +* Position and size units are inventory slots + +#### `bgcolor[;]` +* Sets background color of formspec as `ColorString` +* If `true`, the background color is drawn fullscreen (does not effect the size of the formspec) + +#### `background[,;,;]` +* Use a background. Inventory rectangles are not drawn then. +* Position and size units are inventory slots +* Example for formspec 8x4 in 16x resolution: image shall be sized + 8 times 16px times 4 times 16px. + +#### `background[,;,;;]` +* Use a background. Inventory rectangles are not drawn then. +* Position and size units are inventory slots +* Example for formspec 8x4 in 16x resolution: + image shall be sized 8 times 16px times 4 times 16px +* If `true` the background is clipped to formspec size + (`x` and `y` are used as offset values, `w` and `h` are ignored) + +#### `pwdfield[,;,;;